1) O documento apresenta o Java Persistence API (JPA) e como mapear classes de entidade para tabelas no banco de dados usando anotações. 2) É mostrado como fazer operações básicas como inclusão, alteração, exclusão e consulta de registros usando testes unitários com JUnit. 3) Também são demonstrados relacionamentos entre classes como herança, many-to-one e consultas.
3. Código-fonte
• O projeto de exemplo com o código-fonte está disponível
em:
–
https://github.com/masreis/e-commerce
4. Mapeamento objeto-relacional
• JPA é um modelo de persistência baseado
em POJO.
• Java é utilizada em ambientes corporativos,
com grandes bancos de dados.
• Agiliza o desenvolvimento, já que não
precisamos escrever comandos SQL.
• Fácil de mudar de banco de dados, caso
necessário.
5. JPA x Hibernate
• JPA é a especificação da Oracle para
mapenamento objeto-relacional.
• Hibernate apresentava uma maneira mais
elegante de trabalhar com a persistência do
que o J2EE.
6. Arquivo de configuração
• O arquivo persistence.xml contém os parâmetros de
configuração para acesso ao banco de dados e deve
estar no diretório META-INF.
• Persistence unit name (e-commerce-pu): indica os
parâmetros de acesso à base de dados.
• transaction-type: RESOURCE_LOCAL para servidor web
e JTA para servidor de aplicação.
• Provider: qualquer implementação do JPA. Pode ser o
Hibernate, OpenJPA ou EclipseLink, que é o produto
oficial da Oracle.
8. Entidades
• Com JPA podemos mapear as classes de entidade
diretamente para o banco de dados.
• Vamos considerar as seguintes classes:
–
Usuario
–
Cliente
–
Produto
–
Categoria
• As anotações do JPA estão no pacote
javax.persistence.
10. Classe Usuario persistente
@Entity
public class Usuario {
@Id
@GeneratedValue
private Long id;
private String email;
private String nome;
private Date ultimoLogin;
}
• Para persistir uma classe usando JPA é necessário
adicionar ao menos as seguintes anotações:
–
@Entity: a classe será armazenada em uma tabela.
–
@Id: campo da chave-primária.
–
@GeneratedValue: a chave-primária será gerada
automaticamente.
11. Teste unitário com JUnit
•
As funcionalidades serão testadas com o Junit.
•
Cada funcionalidade será validada com um teste unitário.
•
As classes de teste vão ficar no pacote:
–
net.marcoreis.ecommerce.teste
•
As classes do JUnit estão em org.junit.
•
Para rodar um teste, vá no menu Run-Run as-JUnit test.
•
Adicione a dependência do Maven no pom.xml.
•
Utilize o @Before e @After para inicializar e finalizar variáveis. Quando o teste envolver
inclusão, alteração ou exclusão de registros, é obrigatório o begin() e commit().
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
</dependency>
12. Inserindo registros
public class TesteInsereUsuario {
private EntityManager em;
@Before
public void inicializar() {
em = JPAUtil.getInstance().getEntityManager();
em.getTransaction().begin();
}
@After
public void finalizar() {
em.getTransaction().commit();
em.close();
}
@Test
public void inserirUsuario() {
Usuario usuario = new Usuario();
usuario.setEmail("ma@marcoreis.net");
usuario.setNome("Marco Reis");
Date data = new Date();
usuario.setUltimoLogin(data);
em.persist(usuario);
}
}
13. Consulta registros gravados
•
Para consultar os registros já gravados vamos usar uma Query ou
uma NamedQuery.
•
A NamedQuery fica na classe persistente, enquanto que a Query
pode estar em qualquer outra classe do sistema.
•
Os parâmetros podem usar as seguintes formas:
–
:nomeParametro1, :nomeParametro2.
–
?1, ?2, ?3.
@Entity
@NamedQuery(name = "usuario.consultaAcessoDia", query = "from Usuario where cast(ultimoLogin as
date) = :data")
public class Usuario {
@Id
@GeneratedValue
private Long id;
private String email;
private String nome;
private Date ultimoLogin;
}
14. NamedQuery
• Adicione uma NamedQuery em Usuario.
• O cast é necessário porque a data é armazenada em
timestamp, e a consulta deve considerar somente a data,
ignorando a hora/minuto/segundo.
@Entity
@NamedQuery(name = "usuario.consultaAcessoDia", query = "from Usuario where cast(ultimoLogin as
date) = :data")
public class Usuario {
@Id
@GeneratedValue
private Long id;
private String email;
private String nome;
private Date ultimoLogin;
}
15. Consulta registros gravados
• Em seguida, vamos criar o teste unitário para verificar os
usuários cadastrados.
public class TesteConsultaUsuarios {
private EntityManager em;
@Before
public void inicializar() {
em = JPAUtil.getInstance().getEntityManager();
}
@After
public void finalizar() {
em.close();
}
}
16. Consulta registros gravados
• O primeiro teste unitário utiliza uma Query, retornando
todos os usuários cadastrados.
@Test
public void consultaTodosUsuarios() {
String queryJPA = "from Usuario";
Query query = em.createQuery(queryJPA);
List<Usuario> usuarios = query.getResultList();
for (Usuario usuario : usuarios) {
System.out.println("Nome: " + usuario.getNome());
}
}
17. Consulta registros gravados
• O teste unitário abaixo acessa a NamedQuery e passa
um parâmetro, mostrando apenas os registros que têm a
data de hoje.
@Test
public void consultaUsuriosAcessoDia() {
em = JPAUtil.getInstance().getEntityManager();
Query query = em.createNamedQuery("usuario.consultaAcessoDia");
query.setParameter("data", new Date());
List<Usuario> usuarios = query.getResultList();
for (Usuario usuario : usuarios) {
System.out.println("Nome/ultimo login: " + usuario.getNome()
+ " - " + usuario.getUltimoLogin());
}
}
18. Alterando registros gravados
• Para os testes abaixo crie a classe TesteAlteraUsuario.
• Para alterar um registro, primeiro faça uma consulta pelo
ID. Dessa forma, o objeto será gerenciado pelo
EntityManager e poderá ser atualizado.
@Test
public void alterarUsuario() {
Long id = 2l;
Usuario usuario = em.find(Usuario.class, id);
Assert.assertNotNull("Usuario não cadastrado", usuario);
usuario.setEmail("diego@lucas.net");
usuario.setNome("Diego Lucas");
Date data = new Date();
usuario.setUltimoLogin(data);
em.persist(usuario);
}
19. Removendo registros gravados
• Para os testes abaixo crie a classe
TesteRemoveUsuario.
• A exclusão deve ser feita após uma consulta pelo ID,
seguindo a mesma ideia da alteração.
@Test
public void removerUsuario() {
Long id = 5l;
Usuario usuario = em.find(Usuario.class, id);
Assert.assertNotNull("Usuario não cadastrado", usuario);
em.remove(usuario);
}
20. Herança
• Há 3 estratégias de herança no JPA:
– JOINED: uma tabela para cada entidade, não repete
os campos.
– SINGLE_TABLE: apenas uma tabela para todas as
classes.
– TABLE_PER_CLASS: uma tabela para cada classe,
repetindo todos os campos.
21. Classe Cliente
• Para mostrar o funcionamento da herança no JPA, crie a
classe Cliente, subclasse de Usuario.
• Não é necessário redefinir o Id, que já está na
superclasse.
@Entity
public class Cliente extends Usuario {
@Column(unique = true, nullable = false)
private String cpfCnpj;
{sets e gets}
}
22. Teste do Cliente
• Agora, crie a classe TesteInsereCliente para testar a
nova entidade.
• Não se esqueça dos métodos de inicialização e
finalização.
• A título de teste, comente a linha do cpfCnpj e veja o
resultado.
@Test
public void inserirCliente() {
Cliente cliente = new Cliente();
cliente.setEmail("jose@oracle.com");
cliente.setNome("Jose Carlos");
// cliente.setCpfCnpj("123456");
em.persist(cliente);
Assert.assertTrue("Cliente gravado com sucesso", cliente.getId() > 0);
}
23. Como funciona
• Acompanhando o log do Hibernate podemos verificar
que foram incluídos 2 registros, um na tabela Usuario e
outro na tabela Cliente.
• Isso porque a estratégia de herança selecionada foi
JOINED.
Hibernate:
insert
into
Usuario
(id, email, nome, ultimoLogin)
values
(default, ?, ?, ?)
Hibernate:
insert
into
Cliente
(cpfCnpj, id)
values
(?, ?)
24. Atividade
• Crie as classes de teste Categoria:
– Inclusão.
– Alteração.
– Exclusão.
– Consulta todas as categorias.
25. Relacionamentos
• O JPA tem as seguintes multiplicidades, seguindo o
modelo relacional:
– many-to-one.
– one-to-one.
– one-to-many.
– many-to-many.
26. Many-to-one
• O relacionamento many-to-one está presente na classe
produto.
• Segundo o modelo, cada produto deve ter uma
categoria.
public class Produto {
@Id
@GeneratedValue
private Long id;
@ManyToOne
private Categoria categoria;
private String nome;
private String descricao;
private String especificacaoLoja;
@Lob
private byte[] especificacaoFabricante;
private Double preco;
}
27. Inserindo produtos
• Para inserir um produto precisamos escolher uma
categoria válida.
• O primeiro passo é pesquisar uma categoria já
cadastrada.
• Em seguida podemos preencher os demais atributos e
persistir a entidade.
@Test
public void inserirProduto() {
Long idCategoria = 12l;
Categoria categoria = em.find(Categoria.class, idCategoria);
Assert.assertNotNull("Categoria não cadastrada", categoria);
Produto produto = new Produto();
produto.setCategoria(categoria);
produto.setDescricao("Colcha para cama de solteiro 120cm x 210cm");
produto.setNome("Colcha para cama de solteiro");
produto.setPreco(150.00);
em.persist(produto);
}
28. Consultando produtos
• Para os exemplos seguintes crie a classe
TesteConsultaProdutos.
• O primeiro teste, consultarTodasCategorias, segue o
mesmo princípio das demais.
@Test
public void consultarTodosProdutos() {
System.out.println("Consultar todos os produtos");
List<Produto> produtos = em.createQuery("from Produto").getResultList();
for (Produto p : produtos) {
System.out.println(p.getId() + " - " + p.getNome());
}
}
29. Consultando produtos pela categoria
• O exemplo abaixo mostra os produtos de uma categoria
específica.
• Este teste usa uma Query, mas poderia ser facilmente
adaptado para utilizar NamedQuery.
@Test
public void consultarProdutosPelaCategoria() {
System.out.println("Consultar produtos pela categoria");
Long idCategoria = 12l;
List<Produto> produtos = em
.createQuery("from Produto where categoria.id = ?1")
.setParameter(1, idCategoria ).getResultList();
for (Produto p : produtos) {
System.out.println(p.getId() + " - " + p.getNome());
}
}
30. Consultando quantidades
• Adicione as NamedQueries abaixo em Produto e o teste
na classe TesteConsultaProdutos.
@NamedQueries({
@NamedQuery(name = "produto.consultaTotalPorCategoria", query = "select count(p) from
Produto p where categoria.id = :idCategoria"),
@NamedQuery(name = "produto.consultaPorIntervaloPreco", query = "from Produto where preco
>= ?1 and preco <= ?2") })
@Test
public void consultarTotalProdutoPelaCategoria() {
System.out.println("Consultar total de produtos pela categoria");
Query query = em.createNamedQuery("produto.consultaTotalPorCategoria");
Long idCategoria = 12l;
query.setParameter("idCategoria", idCategoria);
Object resultado = query.getSingleResult();
System.out.println("Total de produtos na categoria: " + resultado);
}
31. Atividades
• Criar o teste unitário para a NamedQuery
'produto.consultaPorIntervaloPreco' na classe
TesteConsultaProdutos.
• Criar as classes abaixo, com seus relacionamentos,
atributos e testes unitários.
– Venda (id, data, cliente).
– Item (id, produto, quantidade, valorUnitario, venda).