Spring 2, JPA e Hibernate – Parte II

 

Aprofundando na JPA

 

Entity

 

Conforme dito anteriormente, para definir que seu POJO é uma entidade persistente, é necessário anotar a classe com @Entity:

@Entity
public class Entidade {

...

}      

A classe Entidade deve conter um método construtor (public ou protected) sem argumentos, e não pode ser final, assim como seus métodos e atributos persistentes.

Interfaces não podem ser entidades, porém as classes abstratas podem.

Esses atributos seguem o padrão Java Beans e, por isso, devem ser private ou protected, e acessados por meio dos métodos get e set, como boa prática.

A persistência dos dados pode ser feita por meio dos atributos, ou por meio dos seus métodos get. Depende de como as annotations são utilizadas.

Atributos definidos com a annotation @Transient não são persistentes

No exemplo abaixo o valor do atributo persistente id será acessado diretamente por meio da variável.

@Entity
public class Entidade {

    @Id
    private int id;

    public int getId(){

        return this.id;

    }

 

    public void setId(int id){

        this.id = id;

    }

 

}

Porém, esse atributo também poderia ser definido da outra forma (método get):

    ...
  
private int id;

 

 @Id
   public int
getId()
        return this.id
;
   

 

Toda entidade deve conter uma única  primary key definida pela annotation @Id. No caso de primary Keys compostas deve ser utilizado a annotation @EmbeddedId para o atributo, cuja classe contém as primary Keys, ou a annotation @IdClass, mapeando as primary Keys pelos atributos definidos na classe IdClass.

@EmbeddedId
private EntidadePK entidadePK;

 

ou

@Entity
@IdClass
(com.acme.EntidadePK.class)

public class Entidade {

  @Id
  String entidadePkAttribute1;

  @Id
    
String entidadePkAttribute2;

  ...

}

 

Relacionamentos

Assim como as tabelas no banco de dados, as Entities também se relacionam. Essas associações podem ser unidirecionais ou bidirecionais: OneToOne, OneToMany, ManyToOne, ManyToMany.

Cada uma dessas annotations (além da @Basic) possui o atributo fetch, que define duas maneiras distintas de se carregar os dados:

·         EAGER: Carrega todos os dados da Entity no momento em que é acessada;

·         LAZY: Carrega os dados da Entity de forma enxuta, quando é acessada.

 

Geralmente use o lazy quando você sabe que não precisará do atributo da Entity quando ela for carregada, pois no caso dos relacionamentos que contém uma Collection, ela pode ser carregada de forma incompleta. Por isso é recomendado o uso de LAZY apenas no container, onde existe um contexto de transação, pois quando o dado é solicitado, o Provider poderá buscá-lo. No próximo capítulo (Spring+JPA) explicaremos o uso do pattern Open Session In View, para uso de LAZY em aplicações WEB.

 

OneToOne Bidirecional

A Entity1 contém uma referência a uma instância única da Entity2, e vice-versa.


A Entity2 contém a annotation mappedBy, utilizada para referenciar a Entity1, dona do relacionamento. A tabela mapeada pela Entity1 contém uma foreign key para a tabela mapeada pela Entity2.

@Entity

public class Entity1 {

    private Entity2 entity2;

 

    @OneToOne

    public Entity2 getEntity2() {

        return entity2;

    }

    public void setEntity2(Entity2 entity2) {

        this.entity2 = entity2;

    }

    ...

}

 

@Entity

public class Entity2 {

    private Entity1 entity1;

 

    @OneToOne(mappedBy="entity2")

    public Entity1 getEntity1() {

        return entity1;

    }

    public void setEntity1(Entity1 entity1) {

        this.entity1 = entity1;

    }

    ...
}

No relacionamento OneToOne unidirecional, a Entity2 não contém uma referência a Entity1.

ManytoOne e OnetoMany Bidirecional

A Entity1 contém uma referência a uma instância única da Entity2.


A Entity2 contém uma referência a uma coleção de objetos da Entity1.


A Entity2 contém a annotation mappedBy, utilizada para referenciar a Entity1, dona do relacionamento. A tabela mapeada pela Entity1 contém uma foreign key para a tabela mapeada pela Entity2.

@Entity

public class Entity1 {

    private Entity2 entity2;

 

  @ManyToOne

     public Entity2 getEntity2() {

        return entity2;

    }

     public void setEntity2(Entity2 entity2) {

        this.entity2 = entity2;

  }

    ...

}

 

@Entity

public class Entity2 {

    private Collection<Entity1> entity1;

 

    @OneToMany(mappedBy="entity2")

    public Collection<Entity1> getEntity1() {

        return entity1;

    }

     public void setEntity1(Collection<Entity1> entity1) {

        this.entity1 = entity1;

  }

    ...
}

 

No relacionamento ManyToOne e OneToMany unidirecional, a Entity2 não contém uma referência a Entity1.

ManyToMany Bidirecional

A Entity1 contém uma referência a uma coleção de objetos da Entity2.


A Entity2 contém uma referência a uma coleção de objetos da Entity1.


A Entity2 contém a annotation mappedBy, utilizada para referenciar a Entity1, dona do relacionamento. A Entity1 e Entity2 contêm uma foreign key para a tabela (n para n) que contém o relacionamento entre Entity1 e Entity2.

@Entity

public class Entity1 {

    private Collection<Entity2> entity2;

 

    @ManyToMany

     public Collection<Entity2> getEntity2() {

        return entity2;

     }

     public void setEntity2(Collection<Entity2> entity2) {

        this.entity2 = entity2;

  }

    ...

}

 

@Entity

public class Entity2 {

  private Collection<Entity1> entity1;

 

    @ManyToMany(mappedBy="entity2")

    public Collection<Entity1> getEntity1() {

        return entity1;

   }

    public void setEntity1(Collection<Entity1> entity1) {

        this.entity1 = entity1;

   }

 

No relacionamento ManyToMany unidirecional, a Entity2 não contém uma referência a coleção de objetos de Entity1.

EntityManager e PersistenceContext

Um contexto de persistência é responsável por gerenciar as Entities e o seu ciclo de vida. A EntityManager  é a API que se comunica com o PersistenceContext. Com ela é possível remover, criar, atualizar e pesquisar as Entities.

As Entities a serem gerenciadas pela EntityManager estão definidas no arquivo persistence.xml , por meio da persistence-unit. Nela estão mapeadas as classes associadas às tabelas de um único banco de dados. Esse arquivo deve estar no diretório /classes/META-INF.

O persistence.xml pode conter várias persistence-unit, cada uma configurando um datasource. Um container J2EE, quando encontra um arquivo persistence.xml em tempo de deploy, é responsável por processar as persistence-unit e criar a EntityManager. Porém, em um ambiente JavaSE, é necessário executar o método Persistence.createEntityManagerFactory(String persistenceUnitName).

Abaixo segue um exemplo de um persistence.xml

 

...

<persistence>

        <persistence-unit name="unit">

               <jta-data-source>java:/datasource</jta-data-source>

               <class>example.pojo.Entity1</class>

<class>example.pojo.Entity2</class>

        </persistence-unit>

</persistence>
...

 

name: atributo da tag persistence-unit define o nome a ser referenciado pela annotation ou pelo lookup JNDI.

transaction-type: não aparece no arquivo, porém, define se a transação do datasource é do tipo JTA ou RESOURCE_LOCAL. O default é JTA.

Um JTA EntityManager participa da transação controlada pelo container, que foi propagada para datasource. É geralmente utilizado em aplicações J2EE.

Em um Resource-Local EntityManager a transação é controlada pelo próprio datasource, por meio da EntityTransaction. Ele é geralmente utilizado em aplicações JavaSE.

 

jta-data-source: é utilizado se o transaction-type for do tipo JTA. Caso seja RESOURCE_LOCAL o atributo non-jta-data-source deve ser utilizado.

provider: indica se a aplicação depende de algum mecanismo de persistência externo. No caso desse artigo, estaremos utilizando Hibernate Entity Manager.

mapping-file: configura o arquivo XML aonde estarão mapeadas as Entitie. Geralmente esse arquivo é criado com o nome orm.xml.

jar-file indica o arquivo .jar que conterá as Entities. Já o atributo class mapeia as Entities que estão no mesmo jar do persistence.xml.

exclude-unlisted-classes: inclui todas as classes com a annotation @Entity, no respectivo persistence-unit.

properties: define as propriedades específicas do mecanismo de persistência definido no atributo provider.

Ciclo de vida da Entity

 

Uma Entity quando é criada não está associada a nenhum contexto de persistência. A partir do momento em que a instância é persistida, por meio do método persist da EntityManager, ela passa a ser gerenciada pelo contexto de persistência.

Cada Entity possui um Id único em um contexto de persistência.

 

As Entities que já contém um Id, porém não estão associadas a nenhum contexto de persistência são consideradas detached. Esse caso acontece, geralmente, quando se devem passar as Entities para outra camada da sua aplicação, após ter realizado o commit da transação ou ter invocado o método close da EntityManager.

No caso de se utilizar EJBs Remote, as Entities se tornam detached, pois são passadas por valor para o cliente.

Os SELECTS e find realizados sem um contexto de persistência também retornam Entities detacheds.

Porém, uma Entity pode passar do estado detached para o estado persistente, por meio do método merge da EntityManager.

Entities detached que possuem atributos com fetch=LAZY poderão conter problemas caso o dado desejado não tenha sido carregado.

 

A mudança de estados de uma Entity é chamada de sincronização com o banco de dados. Ela é realizada no momento do commit da transação, ou através do método flush da EntityManager.

Leia todos os artigos da série