A postagem foi projetada para programadores novatos e não contém coisas super complicadas.
Convido a todos para o dia de demonstração do curso online “Desenvolvedor Java. Profissional " . Como parte do evento, contarei detalhes sobre o programa do curso, bem como responderei suas dúvidas.
Uma parte da solução como o Hibernate é usada, porque isso é muito conveniente para trabalhar com objetos aninhados.
Por exemplo, existe uma classe RecordPackage, um dos campos desta classe é uma coleção de objetos filho (ou aninhados): registros.
Se você usar Jdbc, terá que escrever muitos códigos de rotina. Poucas pessoas gostam, em parte por isso usam o Hiberhate.
No Hibernate, você pode chamar um método de uma vez para obter um RecordPackage com todos os seus registros filhos.
Por um lado, quero usar um método para obter o objeto inteiro e, por outro lado, não quero mexer com o monstro do Hibernate.
Spring Data Jdbc permite que você obtenha o melhor dos dois mundos (ou pelo menos algo aceitável).
Considere dois casos:
- relação um-para-muitos
- relacionamento um para um
São essas conexões que são encontradas com mais frequência na prática.
O código completo dos exemplos pode ser encontrado no GitHub, aqui darei apenas o mínimo.
Em primeiro lugar, é importante notar que Spring Data Jdbc não é uma ferramenta mágica para resolver nenhum problema. Certamente tem suas desvantagens e limitações.
No entanto, para uma série de tarefas típicas, esta é uma solução perfeitamente adequada.
Relacionamento um para muitos
Como um exemplo real, você pode considerar: o cabeçalho de um pacote de alguns dados e as linhas de dados incluídas neste pacote. Por exemplo, arquivo é um pacote e linhas de arquivo são linhas de dados que vão para esse pacote.
A estrutura das tabelas é a seguinte:
create table record_package
(
record_package_id bigserial not null
constraint record_package_pk primary key,
name varchar(256) not null
);
create table record
(
record_id bigserial not null
constraint record_pk primary key,
record_package_id bigint not null,
data varchar(256) not null
);
alter table record
add foreign key (record_package_id) references record_package;
duas tabelas:
record_package(cabeçalho de um determinado pacote) e record(registros incluídos no pacote).
Como essa relação é exibida no código java:
@Table("record_package")
public class RecordPackage {
@Id
private final Long recordPackageId;
private final String name;
@MappedCollection(idColumn = "record_package_id")
private final Set<Record> records;
….
}
Aqui, estamos interessados em definir uma relação um-para-muitos. Isso é codificado usando anotação
@MappedCollection.
Essa anotação tem dois parâmetros:
idColumn - o campo pelo qual a conexão é feita
; keyColumn - o campo pelo qual os registros na tabela filho são ordenados.
Vale a pena mencionar essa ordem separadamente. Neste exemplo, não importa para nós a ordem em que os registros filhos serão inseridos na tabela de registros, mas em alguns casos pode ser importante. Para tal ordenação, a tabela de registros terá um campo como record_no, é este campo que deverá ser escrito na keyColumn da anotação MappedCollection. Quando você executa a inserção, Spring Data Jdbc irá gerar os valores deste campo. Além da anotação, Set
<Record >deverá ser substituído por List<Registro >, o que é bastante lógico e compreensível. A sequência explicitamente especificada de linhas filho também será levada em consideração ao formar o select, mas voltaremos a isso mais tarde.
Portanto, identificamos as conexões e estamos prontos para testá-las.
Criamos entidades relacionadas e as obtemos da base:
var record1 = new Record("r1");
var record2 = new Record("r2");
var record3 = new Record("r3");
var recordPackage = new RecordPackage( "package", Set.of(record1, record2, record3));
var recordPackageSaved = repository.save(recordPackage);
var recordPackageLoaded = repository.findById(recordPackageSaved.getRecordPackageId());
Observe que só precisamos chamar um método
repository.findByIdpara obter uma instância RecordPackagecom uma coleção de registros preenchida.
Obviamente, estamos interessados em que tipo de consulta sql foi executada para obter os registros da coleção aninhada.
Comparado ao Hibernate, Spring Data Jdbc é bom por sua simplicidade. Ele pode ser facilmente depurado para revelar os pontos principais.
Após uma pequena investigação no pacote org.springframework.data.jdbc.core.convert , encontramos a classe DefaultDataAccessStrategy . Esta classe é responsável por gerar consultas SQL com base nas informações da classe. Agora, nesta aula, estamos interessados no método
Iterable <Object> findAllByPath
E mais precisamente a linha:
String findAllByProperty = sql(actualType)
.getFindAllByProperty(identifier, path.getQualifierColumn(), path.isOrdered());
Aqui, a consulta SQL necessária é recuperada do cache interno.
No nosso caso, é assim:
SELECT "record"."data" AS "data", "record"."record_id" AS "record_id", "record"."record_package_id" AS "record_package_id"
FROM "record"
WHERE "record"."record_package_id" = :record_package_id
Tudo é claro e previsível.
Como seria se usássemos a ordem dos registros na tabela filho? Obviamente, seria necessário fazer o pedido.
Vamos prosseguir para a classe BasicRelationalPersistentProperty do pacote org.springframework.data.relational.core.mapping. Essa classe tem um método que determina se deve ou não adicionar o pedido por à consulta.
public boolean isOrdered() {
return isListLike();
}
e
private boolean isListLike() {
return isCollectionLike() && !Set.class.isAssignableFrom(this.getType());
}
isCollectionLike verifica se realmente temos uma "coleção" (incluindo uma matriz).
E da condição ! Set.class.isAssignableFrom (this.getType ()); fica claro que Set não é usado por acaso, mas para excluir classificação desnecessária. E algum dia usaremos intencionalmente List para permitir a classificação.
Acho que um-para-muitos é mais ou menos claro, vamos passar para o próximo caso.
Relacionamento um-para-um
Digamos que temos essa estrutura.
create table info_main
(
info_main_id bigserial not null
constraint info_pk primary key,
main_data varchar(256) not null
);
create table info_additional
(
info_additional_id bigserial not null
constraint additional_pk primary key,
info_main_id bigint not null,
additional_data varchar(256) not null
);
alter table info_additional
add foreign key (info_main_id) references info_main;
Existe uma tabela com informações básicas sobre um determinado objeto (info_main) e há informações adicionais (info_additional).
Como isso pode ser representado no código:
@Table("info_main")
public class InfoMain {
@Id
private final Long infoMainId;
private final String mainData;
@MappedCollection(idColumn = "info_main_id")
private final InfoAdditional infoAdditional;
…
}
À primeira vista, parece o primeiro caso um para muitos, mas há uma diferença. Desta vez, a criança é realmente um objeto, não uma coleção como no caso anterior.
O código para teste é assim:
var infoAdditional = new InfoAdditional("InfoAdditional");
var infoMain = new InfoMain("mainData", infoAdditional);
var infoMainSaved = repository.save(infoMain);
var infoMainLoaded = repository.findById(infoMainSaved.getInfoMainId());
Vamos ver qual expressão sql é gerada desta vez. Para fazer isso, vamos cavar o método findById para o local:
Package org.springframework.data.jdbc.core.convert class DefaultDataAccessStrategy . Já estamos familiarizados com esta classe, agora estamos interessados no método.
public <T> T findById(Object id, Class<T> domainType)
Vemos que a seguinte solicitação é recuperada do cache:
SELECT "info_main"."main_data" AS "main_data", "info_main"."info_main_id" AS "info_main_id", "infoAdditional"."info_main_id" AS "infoadditional_info_main_id", "infoAdditional"."additional_data" AS "infoadditional_additional_data", "infoAdditional"."info_additional_id" AS "infoadditional_info_additional_id"
FROM "info_main"
LEFT OUTER JOIN "info_additional" "infoAdditional"
ON "infoAdditional"."info_main_id" = "info_main"."info_main_id"
WHERE "info_main"."info_main_id" = :id
No momento, a junção externa esquerda é adequada para nós, mas e se não. Como faço para obter uma junção interna?
A criação de join-s funcionais está no pacote org.springframework.data.jdbc.core.convert , classe SqlGenerator , método:
private SelectBuilder.SelectWhere selectBuilder(Collection<SqlIdentifier> keyColumns)
Estamos interessados neste fragmento:
for (Join join : joinTables) {
baseSelect = baseSelect.leftOuterJoin(join.joinTable).on(join.joinColumn).equals(join.parentId);
}
Se você precisar unir tabelas, existe uma opção apenas com uma junção externa esquerda.
Parece que a junção interna ainda não pode ser feita.
Conclusão
Cobrimos dois casos mais típicos de como você pode unir tabelas no Spring Data Jdbc.
Em princípio, a funcionalidade que temos agora é bastante adequada para resolver problemas práticos, embora haja limitações não críticas.
O texto completo do exemplo pode ser encontrado aqui .
E aqui está uma versão em vídeo deste post.
