Alura > Cursos de Programação > Cursos de Java > Conteúdos de Java > Primeiras aulas do curso Java e JPA: consultas avançadas, performance e modelos complexos

Java e JPA: consultas avançadas, performance e modelos complexos

Mais relacionamentos - Apresentação

Olá, pessoal. Boas-vindas ao segundo treinamento de JPA. Meu nome é Rodrigo Ferreira e eu vou ser o instrutor que vai acompanhar vocês durante esse curso de Java e JPA: Consultas avançadas, performance e modelos complexos da Alura.

No primeiro treinamento de JPA, aprendemos o básico da JPA, o que é a JPA, qual foi a sua motivação, porque veio para substituir o JDBC, criamos um projeto, aprendemos a fazer as configurações no Persistence XML, fazer mapeamentos e usar o EntityManager para fazer a integração com o banco de dados.

Nesse segundo treinamento, continuaremos mexendo no mesmo projeto e estudaremos alguns assuntos que não foram cobertos no primeiro treinamento. Então, vamos focar ainda em mais mapeamentos, continuaremos modelando a nossa aplicação com novas classes, novos mapeamentos e mapeamento de relacionamentos. Por exemplo, o relacionamento many-to-many.

E também como lidar com relacionamento bidirecional, nesses casos, e quais são as boas práticas. Vamos também estudar um pouco mais a fundo a parte de consultas, de queries. Então vamos ver como eu faço para executar funções de agregações nas consultas, utilizar named queries para deixar um pouco mais organizado e separado, o código, das consultas.

E também o select new, para fazer consultas mais elaboradas, por exemplo, em um caso de um relatório. Também vamos estudar um pouco sobre performance em consultas, que é algo extremamente importante quando você trabalha com JPA, já que não vemos muito bem o que está acontecendo por baixo dos panos, pois a JPA abstrai bastante as coisas do JDBC.

Então tem muita query que ela gera e, se não tomarmos cuidado, podemos ter problemas de performance em uma aplicação. Então vamos estudar o que é esse negócio de carregamento lazy, eager, join fetch, fazer consultas planejadas e tópicos relacionados. Também vamos estudar um pouco da API de Criteria, que é uma alternativa ao JPQL.

Vamos entender como funciona, fazer uma consulta com Criteria e discutir as vantagens e desvantagens em relação ao JPQL. Também, por fim, vamos estudar alguns outros assuntos, por exemplo, como eu faço para mapear no caso de uma herança? Como que fica isso em um banco de dados, como que eu mapeio herança na JPA, a JPA suporta isso?

Como fazemos para trabalhar com chaves compostas? Às vezes temos um banco de dados que tem uma tabela que a chave é composta, como eu faço para mapear da maneira mais apropriada? Isso na JPA. Dentre outros assuntos. Então esse é o foco aqui desse segundo treinamento, essa aqui é a ementa dos assuntos que vamos discutir aqui. Espero que vocês gostem desse treinamento e eu vejo vocês no primeiro vídeo. Um abraço.

Mais relacionamentos - Mapeando novas entidades

Olá, pessoal! Vamos começar o nosso segundo treinamento? Eu estou aqui com o Eclipse aberto, você pode usar a IDE de sua preferência, e eu estou com aquele projeto que nós finalizamos no primeiro curso, da JPA.

É o mesmo projeto da última aula, o finalizado. Só recapitulando, é uma aplicação Maven, tem o arquivo "pom.xml". Aqui, no "pom.xml", basicamente só tem a dependência do org.hibernate e do com.h2database, que é o nosso banco de dados em memória.

Você pode usar o banco de dados de sua preferência, isso também não vai fazer muita diferença. E aqui, no Eclipse, no "src/main/resources", na pasta "META-INF", o nosso "persistence.xml".

Bem simples, só com as configurações básicas da JPA. Tem um único persistence-unit, estamos usando apenas um banco de dados.

E aqui as configurações da JPA, do JDBC, do banco de dados, URL, driver do banco de dados, login e senha, e algumas configurações do hibernate, o dialeto do H2 para exibir e formatar o SQL toda vez que ele for acessar o banco de dados no console e para gerar as tabelas automaticamente. Bem simples também.

De código, temos aqui alguns pacotes. No pacote "modelo", acabamos ficando com duas classes de modelo, duas entidades da JPA. Tem aquela que representa um produto, que está mapeada aqui, para uma tabela chamada de produtos.

O produto tem um nome, uma descrição, um preço, uma data de cadastro e uma categoria, um relacionamento @ManyToOne com uma categoria. Categoria é outra entidade, que está mapeada pela tabela de categorias.

Tem um ID e um nome. De resto, nada de mais aqui, só construtor, getter e setter. Bem simples. Criamos também as classes "dao", seguindo o padrão Data Access Object para isolar o acesso ao banco de dados.

Aqui a classe Dao utilizando um EntityManager, tem só um construtor, que recebe o EntityManager para nos preocupar com a infra do EntityManager, e os métodos de persistência: cadastrar, atualizar, remover, utilizando o EntityManager com os métodos específicos e alguns métodos de consulta. Bem tranquilo.

Além disso, temos a classe "JPAUtil.Java", a classe utilitária só para isolar a criação do EntityManager e do EntityManagerFactory, bem simples também.

Como essa aplicação não é uma aplicação web, não estamos utilizando nenhum framework, é JPA puro, estamos fazendo os testes em uma classe utilitária, em uma classe com o método name.

Então bem simples, só para ficar bem focado na JPA e não ter interferência de frameworks, só alguns métodos para criar o EntityManager, instanciar a nossa classe Dao, criar uns objetos, as nossas entidades, preencher os atributos, persistir e fazer algum system outs para ver se tudo está funcionando corretamente.

Esse foi o projeto que nós finalizamos no primeiro treinamento. Vamos dar continuidade em cima desse projeto. Nesse primeiro vídeo, o que vamos fazer? Vamos aprender, continuaremos fazendo novos mapeamentos. Temos essas duas novas tabelas. Eu coloquei esse diagrama aqui, que é a tabela de "clientes" e de "pedidos". Até então não tínhamos essa necessidade e agora surgiu essa necessidade.

Teremos que mapear duas novas tabelas. Nós já sabemos: toda tabela, no banco de dados, é mapeada para entidades no mundo Java, no lado da orientação a objetos. Teremos uma classe "cliente", com ID, nome e CPF, e uma classe "pedido", com ID, data, o ID do cliente, o cliente em si, e o valor total.

Bem tranquilo, a princípio nada de novo. E o relacionamento de "pedidos" para "clientes", no caso, many to one. Então um pedido está vinculado com um único cliente, mas um cliente pode ter múltiplos pedidos. De "pedidos" para "clientes", many to one. Vamos fazer o mapeamento dessas duas entidades.

De volta ao Eclipse, no pacote de "modelo", vou criar uma nova classe, "Ctrl + N", "Java Class", "Cliente" será a minha primeira classe. É bem simples, vou até abrir aqui a classe de "Produto.java", vou copiar essas duas anotações, @Entity e @Table, só para não ter que digitar na mão.

Vou colar em "Clientes". Ao invés de produtos, o nome da tabela é @Tabel(name = "clientes"). Só relembrando que, por padrão, o nome da tabela é o mesmo nome da entidade. No nosso caso, como a tabela é no plural, clientes, então tem que colocar o @Table.

Vou copiar o @Id e o @GeneratedValue do "Produto", copiar o private Long id; e o private String nome;, que cliente tem nome. Deixa eu maximizar a tela. Então Cliente tem um private Long id, um private String nome e um CPF. Vou dar um "Ctrl + C", vou dar um "Ctrl + V", private String cpf;.

Vou fazer aquele mesmo esquema que tínhamos feito nas outras classes, vou gerar aqui um construtor. Vou mandar ele gerar um construtor com todos os atributos menos o ID, o ID é gerado pelo banco. Só que lembre que a JPA exige um construtor default, padrão, sem nenhum argumento, então eu vou gerar aqui também.

E vou gerar os métodos getters e setters, "Source > Generate Getters and Setters". No caso, vou gerar com todas as opções, dar um "Ctrl + Shift + F" e está pronto. Aqui, bem simples, um novo mapeamento de uma entidade.

Agora vou mapear - já mapeei a tabela de "clientes", agora eu tenho que mapear "pedidos". A "pedidos" têm data, valor total, e um relacionamento com "clientes", então é bem tranquilo também. Vou copiar a classe "Produto". Vou dar um "Ctrl + C" e "Ctrl + V" mesmo, e é a classe de "Pedido".

Colei o código. Agora tenho que adaptar. Em @Table, é @Table(name = "pedidos")- deixa eu só maximizar a tela. Pedido eu tenho um id, Pedido tem uma data, então não é dataCadastro. A data é a data de criação, quando foi instanciado o pedido, eu já instancio a data atual.

Além disso, no Pedido eu tenho um valorTotal e tenho o que mais? Data, valor total e o relacionamento com o "Cliente". Vou apagar esse nome, vou apagar essa descricao. E aqui, em @ManyToOne, será um relacionamento com Cliente e vou chamar de private Cliente cliente;, e é ManyToOne, então aqui eu não preciso mexer.

Está mapeado, agora eu só preciso corrigir aqui, apagar esse código que não vamos usar. Vou gerar aqui o construtor com os atributos, para ficar parecido com o que fizemos nos outros projetos, facilitar na hora de criar um objeto. Mas esse construtor só recebe o valor total - aliás, esse construtor só recebe o cliente.

Porque a data será a data atual, o valor total, nós vamos mexer nele posteriormente, quando formos trabalhar com os itens do pedido, então eu só preciso passar quem é o cliente desse pedido. Getters e setters, posso apagar esses daqui, apagar esses daqui que eu já tinha.

Apaguei tudo. Agora vou gerar os getters e setters dos outros atributos: cliente, data e valor total. "Ctrl + Shift + F", está formatado. Está mapeada a nossa entidade Pedido. Tem o ID, valor total, data e cliente.

ID, valor total, data e cliente. A coluna "valor_total", ela é separada por underline. No Java, nós usamos o camel case. Por padrão, o hibernate sabe que quando tem camel case é para separar com underline, então não precisamos configurar, esse já é o comportamento padrão.

Então duas novas tabelas, duas novas entidades mapeadas, bem simples, bem tranquilo. Mas vem essa questão: como eu vou relacionar o pedido com um produto? Porque no pedido eu tenho o cliente, eu tenho a data, eu tenho o valor total. Esse valor é baseado nos produtos. Quais são os produtos desse pedido?

Então precisamos também fazer esse mapeamento do pedido com os produtos. Só que, na realidade, não é um mapeamento direto com a entidade "Produto", será um pouco diferente, porém, isso nós vamos discutir no próximo vídeo. Nós vamos ver como podemos fazer esse mapeamento e aprender outras coisas da JPA. Vejo vocês lá.

Mais relacionamentos - Relacionamentos many-to-many

Olá, pessoal! Continuando, agora nós precisamos relacionar o pedido com o produto. Aqui nós temos o mapeamento já da classe do Cliente, da entidade Cliente. A de Pedido.

E Pedido está relacionado com Cliente, um many to one, todo pedido pertence a um cliente, um cliente pode estar vinculado a mais de um pedido. E agora nós precisamos relacionar o pedido com o produto, em um pedido eu terei vários produtos. Se formos analisar, pensando no banco de dados, precisaremos ter essa tabela aqui.

Uma nova tabela, que é justamente a tabela que faz essa junção entre um pedido e um produto. É aquela famosa tabela de join, de relacionamento de muitos para muitos, porque um pedido pode ter vários produtos, mas um mesmo produto pode estar presente em vários pedidos, então é um relacionamento muitos para muitos.

Se fosse um relacionamento muito para muitos simples, conforme está demonstrado nesse diagrama, nós teríamos essa tabela "itens_pedido", essa tabela seria apenas a tabela de join, então teria apenas o ID do pedido e o ID do produto. Ela seria populada, por exemplo, o pedido 1 está vinculado com o produto 1, o pedido 1 está vinculado com o produto 2, o pedido 1 está vinculado ao produto 3, e por aí vai.

Porém, se fosse fazer esse mapeamento na JPA, seria algo simples, bastaria vir na entidade Pedido, por exemplo, e colocaríamos aqui um private List<>, já que são vários, private List <Produto>, que é a nossa entidade Produto. Chamaria aqui de private List <Produto> produtos;.

Em cima desse atributo, como Produto é uma outra entidade, lembre, precisamos colocar a anotação da cardinalidade, nós colocaríamos @ManyToMany. Eu estaria falando: JPA, de Pedido para Produto, é muitos para muitos. A JPA já ia assumir que teria uma tabela de join.

A JPA tem um padrão de nomenclatura, mas se quiséssemos trocar o padrão dessa tabela, não é o @Table, o @Table é só em cima de entidade. Nós teríamos que colocar aqui uma outra anotação, que é o @JoinTable(). Aqui nós conseguimos personalizar qual é o nome da tabela, quais os nomes das colunas de join, o "produto_id", "pedido_id", enfim.

Então esse é o mapeamento many to many simples, onde a tabela tem apenas as duas colunas, o ID das duas tabelas que ela referencia. Porém, no nosso caso, o relacionamento, ele é many to many, ele é muitos para muitos, só que a nossa tabela, ela vai precisar de mais colunas.

Por exemplo, eu preciso saber a quantidade do produto. Então, nesse pedido eu estou comprando o produto XPTO, mas em qual quantidade? É uma, são 2, são 5, são 30? Eu preciso saber isso. Outra coisa que eu preciso saber também é qual é o preço do produto.

Você pode perguntar, “Mas Rodrigo, já não tem aqui o ID do produto? E na tabela do produto já não tem o preço?” Verdade, só que esse preço pode sofrer um reajuste, futuramente ele pode aumentar, mas na data daquele pedido, ele tinha um determinado valor, em um determinado pedido ele custava 100 reais, hoje foi reajustado, custa 180, mas quando eu fiz o pedido, cada unidade custava 100 reais.

Eu preciso ter meio que um histórico do preço do produto naquela venda, naquele pedido. Percebe? Como eu terei mais atributos, mais colunas, mais informações nesse relacionamento, a nossa tabela, ela fica dessa maneira.

Ela muda um pouco de figura. Ela terá um ID próprio, e terá que ter um relacionamento com o pedido e com o produto. Eu preciso saber: esse item pedido, de qual produto ele se referencia, e a qual pedido ele se referencia. E tem as outras colunas, no caso, o preço unitário e a quantidade. Por conta disso, o mapeamento não vai ficar dessa maneira.

Não será um @ManyToMany, vamos mudar um pouco aqui. Precisamos ter uma nova entidade. Sempre que você tiver um relacionamento muitos para muitos que precise de mais colunas, o ideal é você criar uma nova entidade para representar essa tabela de uma maneira mais apropriada. Aqui eu vou apagar. Teremos uma outra entidade, chamada <ItemPedido>, que é justamente aqui a ideia, "itens_pedidos".

Eu preciso criar essa nova entidade. "Ctrl + 1", "Create Class", no pacote "modelo" mesmo. Vou copiar o @Entity, o @Table, o mesmo esquema, mapear a entidade agora, você já sabe como funciona. O nome da tabela é @Table(name = "itens_pedido"), conforme nos foi passado. Aqui dentro, vamos trazer, vai ter um @Id, vou copiar de "Pedido".

O ID desta tabela também será gerado automaticamente. O item pedido, ele tem o preço unitário, então private BigDecimal precoUnitario;, que é o preço na data da venda do produto. Ele vai ter também o que mais? Uma quantidade, private int quantidade;. É um inteiro a quantidade dos produtos.

E o relacionamento para produto e para pedido, que nada mais é do que um relacionamento many to one. Então, essa tabela "itens_pedido", embora ela seja uma tabela de muitos para muitos, como ela tem essa característica, se formos parar para pensar, ela é muitos para um para produto e muitos para um para pedido.

Então nós vamos mapear exatamente dessa forma aqui, na entidade. Deixa eu copiar aqui, de "Pedido", aquele relacionamento. Aliás, em "ItemPedido" são novos relacionamentos: private Pedido pedido, vou chamar de pedido, e private Produto produto. Então a nossa ItemPedido está relacionada com Pedido e com um Produto. Apaguei sem querer, "Ctrl + Z".

E many to one, @ManyToOne. Então esse relacionamento, em si, será many to one, porque virou uma nova entidade. Se não precisasse, seria um many to many. Por padrão, o nome da coluna será "pedido_id", "produto_id", conforme eu desejo aqui. Vamos fazer aquele mesmo esquema, criar o construtor, criar os getters e setters, conforme já fizemos aqui várias vezes.

Botão direito, "Source > Generate Constructor". Nesse caso, quando eu criar um "ItemPedido", eu vou passar o ID, a quantidade, o pedido e o produto. Eu não vou passar o preço unitário, porque o preço unitário eu pego do produto, já que, na hora que eu estou instanciando o produto, eu pego o preço dele, naquele momento.

Vou gerar aqui. Lembre que a JPA necessita do construtor default. Agora botão direito, "Source > Generate Getters and Setters". Vou gerar os getters e setters. Na teoria, do jeito que eu estou fazendo aqui no projeto, não precisaria dos setters, porque eu já estou recebendo no construtor, mas vou gerar aqui, que é o comum do mercado.

Mapeei aqui a minha entidade e agora na entidade "Pedido", ele vai ter um list de itens, então vou chamar aqui de private List<ItemPedido> itens;, e será um @OneToMany. Olha que legal, uma nova anotação, um novo mapeamento. Então o "ItemPedido", ele conhece quem é o pedido, quem é o produto, e o "Pedido" conhece essa lista.

Entretanto, daqui para lá é um para muitos, então aqui, no "ItemPedidos", nós usamos essa anotação @OneToMany. Porém, aqui tem um detalhe: com isso aqui, eu estou mapeando agora os dois lados do relacionamento. De "ItemPedido" eu estou mapeando o "Pedido", e de "Pedido" eu estou mapeando o "ItemPedido".

Então isso se tornou um relacionamento bidirecional, onde eu estou mapeando os dois lados. Porém, no próximo vídeo, vamos discutir um pouco como funciona essa questão do relacionamento bidirecional e algumas boas práticas para lidarmos com ele. Vejo vocês lá, um abraço.

Sobre o curso Java e JPA: consultas avançadas, performance e modelos complexos

O curso Java e JPA: consultas avançadas, performance e modelos complexos possui 160 minutos de vídeos, em um total de 45 atividades. Gostou? Conheça nossos outros cursos de Java em Programação, ou leia nossos artigos de Programação.

Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:

Aprenda Java acessando integralmente esse e outros cursos, comece hoje!

Conheça os Planos para Empresas