Fagno Alves Fonseca <[email protected]> Mestre em Modelagem Computacional de Sistemas – UFT.
A Java Persistence API (JPA) é uma especificação para persistência de dados em Java, que oferece uma API de mapeamento objeto-relacional e soluções para integrar persistência em sistemas corporativos. Ela foi criada com o intuito de ter uma padronização no desenvolvimento de soluções com ORM em Java.
Mapeamento objeto relacional (object-relational mapping, ORM) é uma técnica de programação para conversão de dados entre banco de dados relacionais e linguagens de programação orientada a objetos.
Em nossos projetos estaremos utilizando a JPA com Hibernate. O Hibernate é um framework ORM para persistência de dados, sendo uma das referências entre os desenvolvedores.
No vídeo é apresentado como criar um novo projeto web com maven e também como criar o arquivo persistence.xml que é detalhado no tópico 1.5.
Após criar o projeto web com maven, adicione ao arquivo pom.xml as dependências necessárias do Mysql e Hibernate.
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.48</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>4.3.1.Final</version>
</dependency>
Para efetuar o mapeamento objeto/relacional de uma classe, precisamos inserir a anotação @Entity em uma classe.
import javax.persistence.Entity;
@Entity
public class Pessoa{
}
A anotação @Entity diz que a classe é uma entidade, que representa uma tabela do banco de dados.
Precisamos inserir também as anotações @Id, que declara o identificador no banco de dados e a anotação @GeneratedValue, que determina esse identificador sendo do tipo auto-incremento, neste caso, o atributo 'id'.
import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "tb_pessoa")
public class Pessoa implements Serializable{
@Id
@GeneratedValue
private int id;
private String nome;
private int idade;
//getters e setters
}
A classe possui a anotação @Table(name = "tb_pessoa"), que define o nome da tabela ao persistir os dados no banco. Caso você não utilize a anotação a tabela terá o mesmo nome da classe, ou seja, 'Pessoa'.
Em JPA é uma boa pratica sempre implementar a interface Serializable. Quando o objeto é serializado, todas as variáveis de instância referentes a classe deste objeto serão serializadas. A serialização significa salvar o estado atual dos objetos em arquivos em formato binário para o seu computador, sendo assim esse estado poderá ser recuperado posteriormente recriando o objeto em memória assim como ele estava no momento da sua serialização.
O persistence.xml é um arquivo de configuração padrão da JPA. Ele deve ser criado no diretório src/main/resources/META-INF da aplicação.
link:../codigo/persistence.xml[role=include]
No arquivo 'persistence.xml' é indicado o nome da unidade de persistência e o o provider diz qual é a implementação que será usada como provedor de persistência.
As informações de conexão com o banco de dados são definidas utilizando as principais propriedades abaixo:
-
javax.persistence.jdbc.url: descrição da URL de conexão com o banco de dados
-
javax.persistence.jdbc.driver: nome completo da classe do driver JDBC
-
javax.persistence.jdbc.user: nome do usuário do banco de dados senha do usuário do banco de dados
Outras propriedades do Hibernate são inseridas no arquivos conforme descrito abaixo.
-
hibernate.dialect: dialeto a ser usado na construção de comandos SQL
-
hibernate.show_sql: informa se os comandos SQL devem ser exibidos na console (importante para debug, mas deve ser desabilitado em ambiente de produção)
-
hibernate.format_sql: indica se os comandos SQL exibidos na console devem ser formatados (facilita a compreensão, mas pode gerar textos longos na saída)
-
hibernate.hbm2ddl.auto: cria ou atualiza automaticamente a estrutura das tabelas no banco de dados.
Para gerar a tabela no banco de dados, basta apenas criar um EntityManagerFactory, que todas as tabelas mapeadas serão criadas ou atualizadas.
Abaixo, é apresentado o método main() que foi definido para carregar o arquivo de persistence. O método createEntityManagerFactory() da classe Persistence retorna uma instância de EntityManagerFactory.
import javax.persistence.Persistence;
public class CriaTabelas {
public static void main(String[] args) {
Persistence.createEntityManagerFactory("aulawebii");
}
}
Important
|
No parâmetro do método createEntityManagerFactory("aulawebii") foi informado o name definido no arquivo 'persistence.xml'. |
Um EntityManager é responsável por gerenciar entidades no contexto de persistência. Através dos métodos dessa interface, é possível persistir, pesquisar e excluir objetos do banco de dados.
Qualquer projeto web que usa JPA necessita de apenas uma instância de EntityManagerFactory, que pode ser criada durante a inicialização da aplicação. Esta única instância será utilizada para criar um EntityManager através do método criarEntityManager().
A classe JpaUtil foi definida para complementar o exemplo anterior e substituir a classe 'CriaTabelas'.
link:../codigo/JpaUtil.java[role=include]
Criaremos a classe PessoaDAO para acesso aos dados da entidade Pessoa. Observe que no exemplo a seguir, a classe utilizar a classe 'JpaUtil' para criar um EntityManager.
link:../codigo/PessoaDao.java[role=include]
Coomplemente o projeto anterior do tópico sobre Vraptor, e faça as adequações necessária para utilzação da JPA.
O relacionamento um-para-um, também conhecido como one-to-one. Neste relacionamento teremos os atributos das entidades relacionadas que serão persistidas na mesma tabela. Como exemplo, iremos utilizar uma classe Pessoa que tem um relacionamento One-to-One com a classe endereço.
A implementação do exemplo acima é descrito a seguir. Na classe Pessoa, adicionamos o atributo endereco e e mapeamos com @OneToOne. Adicionamos também a anotação @JoinColumn para definir o nome da coluna que faz referência ao id da tabela tb_endereco na tabela tb_pessoa.
Por padrão, o nome da coluna é definido com o nome do atributo da associação, mais underscore, mais o nome do atributo do identificador da entidade destino caso você não utilize o @JoinColumn.
...
@Entity
@Table(name = "tb_pessoa")
public class Pessoa{
@Id
@GeneratedValue
private int id;
private String nome;
private int idade;
@OneToOne
@JoinColumn(name = "id_endereco")
private Endereco endereco;
//getters e setters
}
...
@Entity
@Table(name = "tb_endereco")
public class Endereco{
@Id
@GeneratedValue
private int id;
private String logradouro;
private String bairro;
private String cep;
//getters e setters
}
Important
|
Não esqueça que precisamos de uma instância persistida de Endereço para atribuir a Pessoa. |
A associação do exemplo entre pessoa e endereço é unidirecional, ou seja, podemos obter o endereço a partir de uma pessoa, mas não conseguimos obter a pessoa a partir de um endereco.
Para tornar a associação um-para-um bidirecional, precisamos apenas incluir o atributo de Pessoa na classe Endereco e mapearmos com @OneToOne usando o atributo mappedBy.
...
@Entity
@Table(name = "tb_endereco")
public class Endereco{
@Id
@GeneratedValue
private int id;
private String logradouro;
private String bairro;
private String cep;
@OneToOne(mappedBy="endereco")
private Pessoa pessoa;
//getters e setters
}
Important
|
O valor do mappedBy deve ser igual ao nome do atributo de endereço definido na classe Pessoa. |
Em relacionamentos OneToOne, qualquer um dos lados pode ser o dominante. O mappedBy deve ser colocado na classe que não é dona do relacionamento, ou seja, o lado fraco. Neste exemplo, definimos Pessoa como classe dominante. Na tabela tb_endereco não terá id da tabela tb_pessoa.
Neste exemplo, vamos alterar nosso relacionamento para que uma pessoa tenha vários endereços e um endereço só tenha uma pessoa. A anotação @OneToMany deve ser utilizada para mapear coleções, neste caso, inserida do lado não dominante (fraco).
...
@Entity
@Table(name = "tb_pessoa")
public class Pessoa{
@Id
@GeneratedValue
private int id;
private String nome;
private int idade;
@OneToMany(mappedBy = "pessoa")
private List<Endereco> enderecos;
//getters e setters
}
...
@Entity
@Table(name = "tb_endereco")
public class Endereco{
@Id
@GeneratedValue
private int id;
private String logradouro;
private String bairro;
private String cep;
@ManyToOne
@JoinColumn(name = "id_pessoa")
private Pessoa pessoa;
//getters e setters
}
Neste exemplo, vamos alterar nosso relacionamento para que uma pessoa tenha vários endereços e um endereço tenha várias pessoas. Usamos a anotação @ManyToMany para mapear a propriedade de coleção.
No relacionamento muitos para muitos é criada uma tabela de associação com os nomes das entidades relacionadas, separados por underscore, com duas colunas, com nomes dos identificadores gerados automaticamente.
Podemos customizar o nome da tabela de associação e das colunas com a anotação @JoinTable, conforme exeplo a seguir.
...
@Entity
public class Pessoa implements Serializable{
@Id
@GeneratedValue
private int id;
private String nome;
private int idade;
@ManyToMany
@JoinTable(name = "pessoas_enderecos",
joinColumns = @JoinColumn(name = "id_pessoa"),
inverseJoinColumns = @JoinColumn(name = "id_endereco"))
private List<Endereco> enderecos=new ArrayList();
//getters e setters
...
No exemplo acima, foi definido com a anotação @JoinTable o nome da tabela que será gerada e também com a anotação @JoinColumn o nome das colunas que representam os identificadores da associação entre pessoa e endereço.
Na classe endereço vamos utilizar a anotação @ManyToMany, porém apontando apenas o "mappedby" conforme a seguir.
...
@Entity
public class Endereco implements Serializable{
@Id
@GeneratedValue
private int id;
private String logradouro;
private String bairro;
private String cep;
@ManyToMany(mappedBy = "enderecos")
private List<Pessoa> pessoas=new ArrayList();
//getters e setters
...
Um nova tabela será gerada de acordo a nomeclatura definida nas anotações e apresentada na figura a seguir.
A JPA define 3 formas de se fazer o mapeamento de herança:
-
Tabela Única por Hierarquia de Classes (single table)
-
Uma tabela para cada classe da hierarquia (joined)
-
Uma tabela para cada classe concreta (table per class)
Essa estratégia de mapeamento define uma unica tabela para toda a hierarquia de classes. Por exemplo, considere a Classe Pessoa como Superclasse, e outras 2 classes PessoaFisica e PessoaJuridica estendendo de Pessoa, teremos uma tabela com os dados de toda a hierarquia.
Pontos importantes:
-
As classes filhas precisam aceitar valores nulos. A falta da constraint NOT NULL pode ser um problema.
A identificaçao de quem é Pessoa Física ou Jurídica é feita por um atributo chamado discriminator, ou seja, um campo que identifica PessoaFisica(PF) e PessoaJuridica (PJ).
...
@Entity
@Table(name = "tb_pessoa")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "tipo")
public abstract class Pessoa {
@Id
@GeneratedValue
private Long id;
private String nome;
//getters e setters
...
A estratégia é definida com a anotação @Inheritance, neste caso SINGLE_TABLE. A anotação @DiscriminatorColumn foi usada para definir o nome da coluna do discriminator e identificar se é PF ou PJ.
A seguir vamos definir a classe PessoaFisica com a anotação @DiscriminatorValue indicando o discriminador como "F".
...
@Entity
@DiscriminatorValue("F")
public class PessoaFisica extends Pessoa{
private String cpf;
//getters e setters
...
A seguir vamos definir a classe PessoaJuridica com a anotação @DiscriminatorValue indicando o discriminador como "J".
...
@Entity
@DiscriminatorValue("J")
public class PessoaJuridica extends Pessoa{
private String cnpj;
//getters e setters
...
Essa estratégia de mapeamento define uma tabela para cada classe da hierarquia. No nosso exemplo, teremos uma tabelas para a classe Pessoa, uma tabela para PessoaFisica e uma para PessoaJuridica.
Alteramos o exemplo anterior, definir a estratégia de herança para JOINED na entidade Pessoa.
...
@Entity
@Table(name = "tbpessoa")
@Inheritance(strategy = InheritanceType.JOINED)
public abstract class Pessoa {
@Id
@GeneratedValue
private Long id;
private String nome;
//getters e setters
...
Nas subclasses, vamos adicionar a anotação @PrimaryKeyJoinColumn para informar o nome da coluna que faz referência à superclasse.
...
@Entity
@Table(name = "tb_pessoafisica")
@PrimaryKeyJoinColumn(name = "id_pessoa")
public class PessoaFisica extends Pessoa{
private String cpf;
//getters e setters
...
...
@Entity
@Table(name = "tb_pessoajuridica")
@PrimaryKeyJoinColumn(name = "id_pessoa")
public class PessoaJuridica extends Pessoa{
private String cnpj;
//getters e setters
...
Essa estratégia de mapeamento define tabelas apenas para classes concretas (subclasses). Cada tabela deve possuir as propriedades da subclasse incluindo as da superclasse.
Para utilizar essa forma de mapeamento, devemos anotar a classe conforme a seguir.
...
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Pessoa {
@Id
@GeneratedValue(generator = "inc")
@GenericGenerator(name = "inc", strategy = "increment")
private Long id;
private String nome;
//getters e setters
...
Tivemos que mudar a estratégia de geração de identificadores. Não podemos usar a geração automática de chaves nativa do banco de dados.
Nas subclasses, definimos conforme a seguir.
...
@Entity
@Table(name = "tb_pessoafisica")
public class PessoaFisica extends Pessoa{
private String cpf;
//getters e setters
...
...
@Entity
@Table(name = "tb_pessoajuridica")
public class PessoaJuridica extends Pessoa{
private String cnpj;
//getters e setters
...