Escolar Documentos
Profissional Documentos
Cultura Documentos
Contedo
JPA Mini Livro......................................................................................................................... 1
Primeiros passos e conceitos detalhados ............................................................................ 1
Captulo 1: Introduo .............................................................................................................. 4
Captulo 2: Quais os motivos que levaram criao do JPA ....................................................... 5
Captulo 3: O que o JPA? O que so as implementaes do JPA? ............................................ 7
Captulo 4: Para que serve o persistence.xml? E suas configuraes? ........................................ 8
Captulo 5: Definies de uma Entity. O que so anotaes Lgicas e Fsicas? ...................... 12
Captulo 6: Gerando Id: Definio, Id por Identity ou Sequence ............................................... 15
Identity ............................................................................................................................... 15
Sequence .......................................................................................................................... 16
Captulo 7: Gerando Id: TableGenerator e Auto ....................................................................... 18
TableGenerator ................................................................................................................ 18
Auto ................................................................................................................................... 19
Captulo 8: Utilizando Chave Composta Simples ...................................................................... 20
@IdClass........................................................................................................................... 20
@Embeddable .................................................................................................................. 23
Captulo 9: Utilizando Chave Composta Complexa................................................................... 25
Captulo 10: Modos para obter um EntityManager .................................................................. 30
Captulo 11: Mapeando duas ou mais tabelas em uma entitade .............................................. 31
Captulo 12: Mapeando Heranas MappedSuperclass .......................................................... 32
Captulo 13: Mapeando Heranas Single Table ..................................................................... 34
Captulo 14: Mapeando Heranas Joined .............................................................................. 36
Captulo 15: Mapeando Heranas Table Per Concrete Class .................................................. 39
Captulo 16: Prs e Contras dos mapeamentos das heranas .................................................. 42
Captulo 17: Embedded Objects .............................................................................................. 44
Captulo 18: ElementCollection Como mapear uma lista de valores em uma classe .............. 46
Captulo 19: OneToOne (Um para Um) Unidirecional e Bidirecional ........................................ 48
Unidirecional ..................................................................................................................... 48
Bidirecional ....................................................................................................................... 49
No existe auto relacionamento ................................................................................... 50
Captulo 20: OneToMany (um para muitos) e ManyToOne (muitos para um) Unidirecional e
Bidirecional ............................................................................................................................. 51
No existe auto relacionamento ................................................................................... 52
Captulo 1: Introduo
Vamos ver sobre JPA: o que JPA, para que serve o persistence.xml, criar corretamente
uma entidade, como realizar desde mapeamentos simples at os mapeamentos
complexos de chaves primrias, criar relacionamentos entre as entidades, modos para
persistncia automtica e outros.
No cdigo acima possvel ver como trabalhoso transformar os valores retornados pela
consulta em objetos. Imagine uma classe com 30 campos Para piorar imagine uma
classe com 30 campos e relacionado com outra classe que tambm tenha 30 campos? Por
exemplo, um Carro pode ter uma Lista de Pessoas e cada objeto da classe Pessoa teria
30 campos.
Outra desvantagem de uma aplicao que utiliza o JDBC puro a sua portabilidade. A
sintaxe de cada query pode variar entre bancos de dados. Para se limitar o nmero de
linhas de uma consulta na Oracle se utiliza a palavra ROWNUM, j no SQL Server
utilizado a palavra TOP.
A portabilidade de uma aplicao que utiliza JDBC fica comprometida quando as queries
nativas de cada banco so utilizadas. Existem solues para esse tipo de problema, por
exemplo, ter cada consulta utilizada salva em um arquivo .sql fora da aplicao. Para
cada banco diferente que a aplicao rodasse um arquivo .sql diferente seria utilizado.
possvel encontrar outras dificuldades ao longo do caminho: como atualizar os
relacionamentos de modo automtico, no deixar registros rfos na tabela ou como
utilizar hierarquia de um modo mais simples.
Caso voc queira ver como realizar diversos modos de query com JPA, acesse:
http://uaihebert.com/?p=1137.
O JPA ficar responsvel por traduzir cada JPQL para a sintaxe correta; o desenvolvedor
no precisar tomar conhecimento de qual a sintaxe requerida para cada banco.
Essa imagem valida para o Eclipse. Para o Netbeans necessrio verificar qual o
correto lugar na documentao. No momento no o tenho instalado para fornecer essa
informao.
Caso voc tenha a mensagem de erro: Could not find any META-INF/persistence.xml file in
the classpath voc pode verificar o seguinte:
Caso a aplicao seja JSE (desktop) verifique se no est na raiz das classes dentro da pasta
META-INF no JAR. Por default o JPA ir procurar na raiz do JAR pelo endereo METAINF/persistence.xml.
38
<property name="javax.persistence.jdbc.driver"
value="org.postgresql.Driver" />
39
<property name="javax.persistence.jdbc.user" value="postgres" />
40
<property name="javax.persistence.jdbc.password" value="postgres" />
41
<!-- <property name="eclipselink.ddl-generation" value="drop-and-createtables" /> -->
42
<property name="eclipselink.ddl-generation" value="create-tables" />
43
<!-- <property name="eclipselink.logging.level" value="FINEST" /> -->
44
</properties>
45
</persistence-unit>
46 </persistence>
arquitetura do sistema.
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider> => Define qual ser o
provider da implementao JPA utilizada. O provider na verdade qual implementao
10
Configurao
Implementao
Serve Para
eclipselink.ddl-generation
EclipseLink
hibernate.hbm2ddl.auto
Hibernate
Ativar a criao/atualizao
automtica as tabelas ou at
mesmo validar se o esquema do
banco de dados valido com
openjpa.jdbc.SynchronizeMappings
OpenJPA
eclipselink.logging.level
EclipseLink
org.hibernate.SQL.level
org.hibernate.type.level
Hibernate
openjpa.Log
OpenJPA
relao ao mapeamento
realizado com o JPA nas classes.
11
12
toda Entity tem que ter um campo que sirva como ID. Em geral esse campo um nmero
sequencial, mas pode ser uma String e outros tipos de valores
Um detalhe curioso que no existe get/set para o ID. Na viso do JPA um ID imutvel
De acordo com o cdigo exibido acima so exibidas apenas anotaes para definir uma
Entity; o JPA ir procurar por uma tabela chamada CAR no banco de dados e por campos
chamado ID e NAME. Por padro o JPA utiliza o nome da classe e o nome de seus
atributos para encontrar a tabela e sua estrutura no banco.
Segundo o livro Pro JPA 2 podemos definir as anotaes do JPA de dois modos,
anotaes fsicas e anotaes lgicas. As anotaes fsicas so aquelas que determinam
o relacionamento entre a classe e o banco de dados. As anotaes lgicas definem a
modelagem da classe com relao ao sistema. Veja o cdigo abaixo:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.util.List;
import javax.persistence.*;
@Entity
@Table(name = "TB_PERSON_02837")
public class Person {
23
24
25
26
27 }
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
@Basic(fetch = FetchType.LAZY)
@Column(name = "PERSON_NAME", length = 100, unique = true, nullable = false)
private String name;
@OneToMany
private List<Car> cars;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
13
14
Identity
Sequence
TableGenerator
necessrio entender que cada banco adota um modo de gerao automtica de id.
Atualmente o Oracle e o Postgres trabalham com Sequence, o Sql Server e o MySQL
trabalham com Identity. No possvel forar o banco de dados a trabalhar com uma
gerao de ids automtica que ele no suporta.
Os tipos permitidos para ids simples so: byte/Byte, int/Integer, short/Short, long/Long,
char/Character, String, BigInteger, java.util.Date e java.sql.Date.
Identity
Esse o tipo de gerao automtica mais simples que existe. Basta anotar o atributo id
como abaixo:
1 import javax.persistence.Entity;
2 import javax.persistence.GeneratedValue;
3 import javax.persistence.GenerationType;
4 import javax.persistence.Id;
5
6 @Entity
7 public class Person {
8
9
@Id
10
@GeneratedValue(strategy = GenerationType.IDENTITY)
11
private int id;
12
13
14
15
16
17
18
19
15
20
21
22 }
this.name = name;
}
Quem controla qual ser o prximo ID o prprio banco de dados, sem atuao do JPA.
necessrio que primeiro a entidade seja persistida, e aps a realizao o commit, uma
consulta ao banco ser realizada para descobrir qual ser o ID da entidade em questo.
Pode inclusive haver uma pequena perca de desempenho, mas nada alarmante.
Esse tipo de esquema de gerao no permite que blocos de Ids sejam alocados para
facilitar no desempenho. A alocao de ids blocos ser explicada a seguir.
Sequence
O tipo de gerao de id por Sequence funciona conforme abaixo:
1 import javax.persistence.Entity;
2 import javax.persistence.GeneratedValue;
3 import javax.persistence.GenerationType;
4 import javax.persistence.Id;
5 import javax.persistence.SequenceGenerator;
6
7 @Entity
8 @SequenceGenerator(name = Car.CAR_SEQUENCE_NAME, sequenceName = Car.CAR_SEQUENCE_NAME,
initialValue = 10, allocationSize = 53)
9 public class Car {
10
11
public static final String CAR_SEQUENCE_NAME = "CAR_SEQUENCE_ID";
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 }
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = CAR_SEQUENCE_NAME)
private int id;
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
16
A anotao @SequenceGenerator define que existir uma Sequence no banco com o nome
descrito nela (atributo sequenceName). Essa Sequence pode ser utilizada por mais classes
mas no aconselhvel. Caso a Sequence da classe Car tambm fosse utilizada pela classe
House o valor dos Ids no seriam sequenciais. Ao cadastrar o primeiro carro, o id seria um.
O valor initialValue = 10 define qual ser o valor do primeiro ID. preciso ter bastante
cuidado ao se utilizar essa configurao; aps inserir o primeiro valor e reiniciar a aplicao,
o JPA tentar novamente inserir um objeto com o mesmo initialValue.
O valor allocationSize = 53 define qual o tamanho ids que ficaro alocados na memria do
JPA. Funciona do seguinte modo: ao iniciar a aplicao o JPA ir alocar em sua memria a
quantidade de ids determinada nesse valor e ir distribuir para cada nova entidade que for
salva no banco. Nesse caso, os ids de iriam de do initialValue at + 53. E uma vez que esgote
os 53 ids em memria o JPA iria buscar mais 53 ids e guardar na memria novamente. Essa
17
1 import javax.persistence.*;
2
3 @Entity
4 public class Person {
5
6
@Id
7
@TableGenerator(name="TABLE_GENERATOR", table="ID_TABLE",
pkColumnName="ID_TABLE_NAME", pkColumnValue="PERSON_ID",
valueColumnName="ID_TABLE_VALUE")
8
@GeneratedValue(strategy = GenerationType.TABLE, generator="TABLE_GENERATOR")
9
private int id;
10
11
private String name;
12
13
public String getName() {
14
return name;
15
}
16
17
public void setName(String name) {
18
this.name = name;
19
}
20 }
O cdigo acima vai utilizar a seguinte tabela no banco de dados ( possvel que o JPA crie
a tabela automaticamente, veja a configurao na pgina sobre o persistence.xml):
18
pkColumnName =>Nome da coluna que ir conter o nome de id. No cdigo acima a coluna
com o nome id_table_name ser gerada.
pkColumnValue => Nome da tabela que ser salvo. O valor default o nome da classe +
id. Em nosso caso, PERSON_ID que o mesmo descrito no cdigo acima.
initialValue, allocationSize => Apesar de no estarem presentes no exemplo acima, possvel
utilizar essas configuraes igual ao demonstrado na gerao por Sequence, veja na pgina
anterior.
A melhor prtica para essa abordagem criar o table generator dentro do arquivo orm.xml,
esse arquivo utilizado para sobrescrever as configuraes do JPA e est fora do escopo
desse post.
Auto
O modo Auto (de automtico) permite que o JPA decida qual estratgia utilizar. Esse o
valor padro e para utiliz-lo basta fazer:
1 @Id
2 @GeneratedValue(strategy = GenerationType.AUTO) // or just @GeneratedValue
3 private int id;
19
Uma chave composta necessria quando precisamos de mais de um atributo para ser o
identificador da entidade. Existem chaves compostas que so simples ou complexas. So
chamadas de chaves compostas simples quando apenas os tipos do Java so utilizados
como id (String, int, ). Na prxima pgina se encontra modos de mapear chave
compostas complexas.
Existem dois modos de utilizar uma chave composta simples, utilizando a anotao
@IdClass ou @EmbeddedId.
@IdClass
Veja o cdigo abaixo:
1 import javax.persistence.*;
2
3 @Entity
4 @IdClass(CarId.class)
5 public class Car {
6
7
@Id
8
private int serial;
9
10
@Id
11
private String brand;
12
13
private String name;
14
15
public String getName() {
20
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38 }
return name;
}
public void setName(String name) {
this.name = name;
}
public int getSerial() {
return serial;
}
public void setSerial(int serial) {
this.serial = serial;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
@IdClass(CarId.class) => Essa anotao indica que a classe CarId contm os campos
considerados com Id.
Todos os campos que so marcados com Id devem estar descritos na classe CarId.
possvel tambm utilizar @GeneratedValue na chave composta neste tipo de chave
composta. No exemplo acima possvel adicionar @GeneratedValue ao atributo serial.
1 import java.io.Serializable;
2
3 public class CarId implements Serializable{
4
5
private static final long serialVersionUID = 343L;
6
7
private int serial;
8
private String brand;
9
10
// must have a default construcot
11
public CarId() {
12
13
}
14
15
public CarId(int serial, String brand) {
16
this.serial = serial;
21
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44 }
this.brand = brand;
}
public int getSerial() {
return serial;
}
public String getBrand() {
return brand;
}
// Must have a hashCode method
@Override
public int hashCode() {
return serial + brand.hashCode();
}
// Must have an equals method
@Override
public boolean equals(Object obj) {
if (obj instanceof CarId) {
CarId carId = (CarId) obj;
return carId.serial == this.serial && carId.brand.equals(this.brand);
}
return false;
}
A classe CarId contm os campos listados na entidade Car que foram marcados como
@Id.
Para que uma classe possa ser utilizada como id, ela deve:
E para buscar um carro no banco de dados utilizando chave composta, basta fazer como
abaixo:
22
@Embeddable
O outro modo de mapear uma chave composta :
1 import javax.persistence.*;
2
3 @Entity
4 public class Car {
5
6
@EmbeddedId
7
private CarId carId;
8
9
private String name;
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 }
1 import java.io.Serializable;
2
3 import javax.persistence.Embeddable;
4
5 @Embeddable
6 public class CarId implements Serializable{
7
8
private static final long serialVersionUID = 343L;
9
10
private int serial;
11
private String brand;
23
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47 }
}
public CarId(int serial, String brand) {
this.serial = serial;
this.brand = brand;
}
public int getSerial() {
return serial;
}
public String getBrand() {
return brand;
}
// Must have a hashCode method
@Override
public int hashCode() {
return serial + brand.hashCode();
}
// Must have an equals method
@Override
public boolean equals(Object obj) {
if (obj instanceof CarId) {
CarId carId = (CarId) obj;
return carId.serial == this.serial && carId.brand.equals(this.brand);
}
return false;
}
A anotao @Embeddable utilizada para habilitar a classe para ser chave composta.
Os campos dentro da classe j sero considerados como ids.
Para que uma classe possa ser utilizada como id, ela deve:
Para realizar consultas possvel utilizar o mesmo mtodo de consulta que no exemplo do
@IdClass.
24
1 import javax.persistence.*;
2
3 @Entity
4 public class DogHouse {
5
6
@Id
7
@OneToOne
8
@JoinColumn(name = "DOG_ID")
9
private Dog dog;
10
11
12
13
14
15
25
16
17
18
19
20
21
22
23
24
25
26
27
28 }
A anotao @Id foi utilizada na entidade Dog para informar ao JPA que o id da entidade
DogHouse devem ser o mesmo.
Junto com a anotao do @Id encontra-se a anotao @OneToOne para indicar que alm do
id em comum, existe um relacionamento entre as classes. Mais informaes sobre a anotao
@OneToOne ainda nesse post.
Imagine um caso onde seja necessrio acessar o id da classe DogHouse sem precisar
passar pela classe Dog (dogHouse.getDog().getId()). O prprio JPA j providencia um
modo de fazer sem a necessidade de utilizar da Lei de Demeter:
http://uaihebert.com/?p=62
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import javax.persistence.*;
@Entity
public class DogHouseB {
@Id
private int dogId;
@MapsId
@OneToOne
@JoinColumn(name = "DOG_ID")
private Dog dog;
private String brand;
public Dog getDog() {
return dog;
}
public void setDog(Dog dog) {
this.dog = dog;
}
public String getBrand() {
26
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39 }
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public int getDogId() {
return dogId;
}
public void setDogId(int dogId) {
this.dogId = dogId;
}
Para finalizar esse assunto, como seria uma entidade com id composto com mais de uma
entidade? Veja o cdigo abaixo:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import javax.persistence.*;
@Entity
@IdClass(DogHouseId.class)
public class DogHouse {
@Id
@OneToOne
@JoinColumn(name = "DOG_ID")
private Dog dog;
@Id
@OneToOne
@JoinColumn(name = "PERSON_ID")
private Person person;
private String brand;
// get and set
}
27
Veja que as duas entidades (Dog e Person) foram marcadas com a anotao @Id.
utilizada a anotao @IdClass para determinar uma classe de chave composta.
1 import java.io.Serializable;
2
3 public class DogHouseId implements Serializable{
4
5
private static final long serialVersionUID = 1L;
6
7
private int person;
8
private int dog;
9
10
public int getPerson() {
11
return person;
12
}
13
14
public void setPerson(int person) {
15
this.person = person;
16
}
17
18
public int getDog() {
19
return dog;
20
}
21
22
public void setDog(int dog) {
23
this.dog = dog;
24
}
25
26
@Override
27
public int hashCode() {
28
return person + dog;
29
}
30
31
@Override
32
public boolean equals(Object obj) {
33
if(obj instanceof DogHouseId){
34
DogHouseId dogHouseId = (DogHouseId) obj;
35
return dogHouseId.dog == dog && dogHouseId.person == person;
36
}
37
38
return false;
39
}
40 }
Veja que a classe contm dois atributos inteiros apontando para as entidades do
relacionamento.
Note que o nome dos atributos so exatamente iguais aos nomes dos atributos. Se o atributo
na entidade DogHouse fosse Person dogHousePerson, o nome dentro da classe de id teria que
ser dogHousePerson ao invs de person.
28
Para que uma classe possa ser utilizada como id, ela deve:
29
S
foi
preciso
utilizar
a
anotao
@PersistenceContext(unitName
=
PERSISTENCE_UNIT_MAPPED_IN_THE_PERSISTENCE_XML). Entenda que para que a
injeo funcione necessrio que sua aplicao rode em um ambiente JEE, ou seja, em
um servidor de aplicativos como JBoss, Glassfish Para que um Entity Manager seja
injetado corretamente o arquivo persistence.xml deve estar no local correto, e deve-se ter
(se necessrio) um datasource corretamente configurado.
A injeo de EntityManager, atualmente, s funciona em servidores que suportam EJB.
Tomcat e outros no iro realizar a injeo.
Para aplicaes desktop ou quando se deseja controlar a transao manualmente mesmo
em servidores JEE basta fazer conforme abaixo:
1 EntityManagerFactory emf =
Persistence.createEntityManagerFactory("PERSISTENCE_UNIT_MAPPED_IN_THE_PERSISTENCE_XML")
;
2 EntityManager entityManager = emf.createEntityManager();
3
4
5
6
7
8
9
entityManager.getTransaction().begin();
// do something
entityManager.getTransaction().commit();
entityManager.close();
30
1 import javax.persistence.*;
2
3 @Entity
4 @Table(name="DOG")
5 @SecondaryTables({
6
@SecondaryTable(name="DOG_SECONDARY_A",
pkJoinColumns={@PrimaryKeyJoinColumn(name="DOG_ID")}),
7
@SecondaryTable(name="DOG_SECONDARY_B",
pkJoinColumns={@PrimaryKeyJoinColumn(name="DOG_ID")})
8 })
9 public class Dog {
10
@Id
11
@GeneratedValue(strategy = GenerationType.AUTO)
12
private int id;
13
14
private String name;
15
private int age;
16
private double weight;
17
18
// get and set
19
20 }
31
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import javax.persistence.MappedSuperclass;
@MappedSuperclass
public abstract class DogFather {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
1 @Entity
2 @Table(name = "DOG")
3 public class Dog extends DogFather {
4
5
@Id
6
@GeneratedValue(strategy = GenerationType.AUTO)
7
private int id;
8
9
private String color;
10
11
public int getId() {
12
return id;
13
}
14
15
public void setId(int id) {
16
this.id = id;
17
}
18
19
public String getColor() {
20
return color;
21
}
22
32
23
24
25
26 }
Na classe DogFather foi definida a anotao @MappedSuperclass. Com essa anotao toda a
classe que herdar da classe DogFather herdar todos seus atributos. Esses atributos sero
refletidos no banco de dados.
Uma MappedSuperclass no pode ser anotada com @Entity\@Table. Ela no uma classe
que ser persistida. Seus atributos/mtodos sero refletidos nas classes filhas.
boa prtica sempre defini-la como abstrata. Ela uma classe que no ser consultada
diretamente por JPQL ou Queries.
No podem ser persistidas, elas no so Entities.
Quando utilizar?
Se a classe no tiver a necessidade de ser acessada diretamente nas consultas ao banco
de dados, pode-se usar a MappedSuperclass. Caso essa classe venha ter seu acesso
direto por pesquisas, aconselhvel utilizar herana de Entity (veja nas prximas
pginas).
33
1
2
3
4
5
6
7
8
9
10
import javax.persistence.*;
@Entity
@Table(name = "DOG")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "DOG_CLASS_NAME")
public abstract class Dog {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
11
12
13
14
15 }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1
2
3
4
5
import javax.persistence.Entity;
@Entity
@DiscriminatorValue("SMALL_DOG")
public class SmallDog extends Dog {
private String littleBark;
public String getLittleBark() {
return littleBark;
}
public void setLittleBark(String littleBark) {
this.littleBark = littleBark;
}
}
import javax.persistence.*;
@Entity
@DiscriminatorValue("HUGE_DOG")
public class HugeDog extends Dog {
34
6
7
8
9
10
11
12
13
14
15 }
SINGLE_TABLE.
@DiscriminatorColumn(name = DOG_CLASS_NAME) => define qual o nome da coluna
que ir conter a descrio de qual tabela a linha da tabela no banco de dados ir pertencer.
Veja a imagem abaixo para ver como ficar a estrutura da tabela.
@DiscriminatorValue => Define qual o valor a ser salvo na coluna descrita na anotao
@DiscriminatorColumn. Veja a imagem abaixo para ver como ficar a estrutura da tabela.
Note que o ID definido apenas na classe que est mais acima da hierarquia. No permitido
reescrever o id de uma classe na hierarquia.
@DiscriminatorColumn(name
=
DOG_CLASS_NAME,
discriminatorType
=
DiscriminatorType.INTEGER) => basta definir o tipo da coluna como Inteiro.
@DiscriminatorValue(1) => O valor a ser salvo deve ser alterado na Entity tambm. O
nmero ser salvo ao invs do texto.
35
1
2
3
4
5
6
7
import javax.persistence.*;
@Entity
@Table(name = "DOG")
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name = "DOG_CLASS_NAME")
public abstract class Dog {
8
9
10
11
12
13
14
15
16 }
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String name;
// get and set
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import javax.persistence.*;
1
2
3
4
import javax.persistence.*;
@Entity
@DiscriminatorValue("HUGE_DOG")
public class HugeDog extends Dog {
private int hugePooWeight;
public int getHugePooWeight() {
return hugePooWeight;
}
public void setHugePooWeight(int hugePooWeight) {
this.hugePooWeight = hugePooWeight;
}
}
@Entity
@DiscriminatorValue("SMALL_DOG")
36
JOINED.
Veja abaixo como ficaram as tabelas criadas no banco de dados:
37
Note nas imagens como foram persistidos os dados. Cada entidade teve sua informao
distribuda em tabelas distintas; para essa estratgia o JPA utilizar uma tabela para cada
classe sendo a classe concreta ou abstrata.
Na tabela Dog que contm os dados em comum de todas as classes; na tabela Dog existe
um campo para descrever para qual entidade pertence cada linha.
38
1
2
3
4
5
6
7
8
9
10
12
13
14
15
16
import javax.persistence.*;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import javax.persistence.Entity;
1
2
3
4
5
import javax.persistence.Entity;
@Entity
@Table(name = "DOG")
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Dog {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String name;
// get and set
}
@Entity
public class HugeDog extends Dog {
private int hugePooWeight;
public int getHugePooWeight() {
return hugePooWeight;
}
public void setHugePooWeight(int hugePooWeight) {
this.hugePooWeight = hugePooWeight;
}
}
@Entity
public class SmallDog extends Dog {
private String littleBark;
39
6
7
8
9
10
11
12
13
14 }
anotao:
@DiscriminatorColumn(name
DOG_CLASS_NAME). Cada classe concreta ter todos os seus dados, com isso no
existir mais uma tabela com dados que no pertenam a ela.
Tabela HugeDog
Tabela SmallDog
40
Note que os dados da classe abstrata Dog foram salvas dentro da tabela HugeDog e
SmallDog.
41
Abordagem
Prs
Contras
No pode ter campos no
nulos. Imagine um caso
onde a classe SmallDog
tivesse o atributo no null
hairColor no banco de
dados. Ao persistir HugeDog
JOINED
42
TABLE_PER_CLASS
43
Como possvel ver, existem dados relacionados para as classes pessoa e casa. Veja
como ficar a entidade Person (pessoa) e Address (endereo) ao utilizar o conceito de
Embedded Objects:
1 import javax.persistence.*;
2
3 @Embeddable
4 public class Address {
5
@Column(name = "house_address")
6
private String address;
7
8
@Column(name = "house_color")
9
private String color;
10
11
@Column(name = "house_number")
13
private int number;
14
15
16
17
18
19
20
21
22
23
24 }
44
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import javax.persistence.*;
@Entity
@Table(name = "person")
public class Person {
@Id
private int id;
private String name;
private int age;
@Embedded
private Address address;
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
// get and set
}
A anotao @Embeddable (class Address) indica que essa classe ser utilizada dentro de uma
entidade, note que Address no uma entidade. apenas uma classe java que ir refletir
dados do banco de dados de um modo organizado.
Foi utilizada a anotao @Column dentro da classe ADDRESS para indicar qual o nome da
coluna dentro da tabela person.
A anotao @Embedded (da classe Person) indica que o JPA deve mapear os campos que
45
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.util.List;
import java.util.Set;
import javax.persistence.*;
@Entity
@Table(name = "person")
public class Person {
@Id
@GeneratedValue
private int id;
private String name;
@ElementCollection
@CollectionTable(name = "person_has_emails")
private Set<String> emails;
@ElementCollection(targetClass = CarBrands.class)
@Enumerated(EnumType.STRING)
private List<CarBrands> brands;
// get and set
}
46
direcionado para uma Entidade, mas para atributos simples (um String e um Enum).
A anotao @ElementCollection utilizada para marcar um atributo como um item que
poder se repetir diversas vezes.
47
Unidirecional
O relacionamento um para um o mais simples. Vamos imagina a situao onde uma
pessoa (Person) tem um celular (Cellular), e apenas a entidade Person ter acesso ao
Cellular. Veja a imagem abaixo:
import javax.persistence.*;
2
3 @Entity
4 public class Person {
5
6
@Id
7
@GeneratedValue
8
private int id;
9
10
private String name;
11
12
@OneToOne
13
@JoinColumn(name="cellular_id")
14
private Cellular cellular;
15
16
// get and set
17 }
1
2
import javax.persistence.*;
48
3 @Entity
4 public class Cellular {
5
6
@Id
7
@GeneratedValue
8
private int id;
9
10
private int number;
11
12
// get and set
13 }
Todo o relacionamento necessita que umas das entidades seja a dona desse
relacionamento. Ser dona do relacionamento nada mais do ter a chave estrangeira na
tabela do banco de dados. No exemplo acima possvel ver na classe Person a utilizao
da anotao @JoinColumn. Essa anotao indica que a chave estrangeira ficar dentro da
tabela person, fazendo com que a entidade Person seja a dona do relacionamento.
Bidirecional
Para deixar esse relacionamento bidirecional necessrio alterar apenas a classe Cellular.
Veja abaixo com ela ficou:
1 import javax.persistence.*;
2
3 @Entity
4 public class Cellular {
5
6
@Id
7
@GeneratedValue
8
private int id;
9
10
private int number;
11
12
@OneToOne(mappedBy="cellular")
49
13
14
15
16 }
Foi utilizado o atributo mappedBy na anotao @OneToOne. Esse atributo indica que a
entidade Person a dona do relacionamento; a chave estrangeira deve ficar na tabela Person e
no na tabela Cellular.
1
2
person.setCellular(cellular);
cellular.setPerson(person);
O JPA usa o princpio do Java de referncia, uma classe precisa fazer referncia para
outra. O JPA no criar um relacionamento de modo automtico para que o
relacionamento exista dos dois lados, necessrio fazer o processo acima.
50
import javax.persistence.*;
@Entity
public class Call {
@Id
@GeneratedValue
private int id;
@ManyToOne
@JoinColumn(name = "cellular_id")
private Cellular cellular;
private long duration;
// get and set
}
import javax.persistence.*;
51
2
3
4
@Entity
public class Cellular {
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 }
@Id
@GeneratedValue
private int id;
@OneToOne(mappedBy = "cellular")
private Person person;
@OneToMany(mappedBy = "cellular")
private List<Call> calls;
private int number;
// get and set
Foi utilizada a anotao @OneToMany. Essa anotao deve ser colocada sobre uma coleo.
Foi utilizado o mappedBy para indicar que esse relacionamento no o lado dominante.
Todo o relacionamento necessita que umas das entidades seja a dona desse
relacionamento. Ser dona do relacionamento nada mais do ter a chave estrangeira na
tabela do banco de dados. No exemplo acima possvel ver na classe Call a utilizao da
anotao @JoinColumn. Essa anotao indica que a chave estrangeira ficar dentro da
tabela Call. O atributo mappedBy deve apontar para o nome do atributo e no para o nome
da classe.
1
2
call.setCellular(cellular);
cellular.setCalls(calls);
52
Tabela dog
Tabela person_dog
53
1
2
3
4
5
6
7
8
9
10
11
12
import java.util.List;
import javax.persistence.*;
@Entity
public class Person {
@Id
@GeneratedValue
private int id;
private String name;
13
14
@ManyToMany
15
@JoinTable(name = "person_dog", joinColumns = @JoinColumn(name = "person_id"),
inverseJoinColumns = @JoinColumn(name = "dog_id"))
16
private List<Dog> dogs;
17
18
@OneToOne
19
@JoinColumn(name = "cellular_id")
20
private Cellular cellular;
21
22
// get and set
23 }
A entidade Person tem o relacionamento com Dog unidirecional. Veja como ficar a classe
Dog caso o relacionamento seja bidirecional:
import java.util.List;
54
2
3 import javax.persistence.*;
4
5 @Entity
6 public class Dog {
7
8
@Id
9
@GeneratedValue
10
private int id;
11
12
private String name;
13
14
15
16
17
18 }
@ManyToMany(mappedBy="dogs")
private List<Person> persons;
// get and set
1
2
person.setDog(dogs);
dog.setPersons(persons);
55
Para realizar esse mapeamento para entidades, basta fazer como abaixo:
1
2
3
4
5
6
7
import java.util.List;
import javax.persistence.*;
@Entity
public class Person {
56
8
9
10
@Id
@GeneratedValue
private int id;
11
12
13
14
15
16
17
18 }
1
2
3
import java.util.List;
import javax.persistence.*;
4
5 @Entity
6 public class Dog {
7
8
@Id
9
@GeneratedValue
10
private int id;
11
12
private String name;
13
14
@OneToMany(mappedBy = "dog")
15
private List<PersonDog> persons;
16
17
// get and set
18 }
1
2
3
4
5
6
7
8
9
import java.util.Date;
import javax.persistence.*;
@Entity
@IdClass(PersonDogId.class)
public class PersonDog {
@Id
57
10
11
12
@ManyToOne
@JoinColumn(name="person_id")
private Person person;
13
14
15
16
17
18
19
20
@Id
@ManyToOne
@JoinColumn(name="dog_id")
private Dog dog;
@Temporal(TemporalType.DATE)
private Date adoptionDate;
21
22
23 }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import java.io.Serializable;
public class PersonDogId implements Serializable {
private static final long serialVersionUID = 1L;
private int person;
private int dog;
public int getPerson() {
return person;
}
public void setPerson(int person) {
this.person = person;
}
public int getDog() {
return dog;
}
public void setDog(int dog) {
this.dog = dog;
}
@Override
public int hashCode() {
return person + dog;
}
@Override
public boolean equals(Object obj) {
if(obj instanceof PersonDogId){
PersonDogId personDogId = (PersonDogId) obj;
return personDogId.dog == dog && personDogId.person == person;
}
58
38
39
40 }
return false;
}
59
1
2
3
car.setColor(Color.RED);
car.setOwner(newPerson);
car.setSoundSystem(newSound);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
60
Mas o que significa dizer que um objeto transiente? Ou que esse relacionamento no
est marcado com cascade PERSIST?
O JPA funciona como um rastreador de toda entidade que for envolvida em uma
transao. Uma entidade envolvido em uma transao uma entidade que ser salva,
alterada, apagada. O JPA precisa saber quem aquela entidade, de onde veio e para
onde vai. Ao abrir uma transao toda entidade que carregada do banco de dados est
attached. O attached nada mais quer dizer que aquela entidade est dentro da
transao, sendo monitorado pelo JPA. Uma entidade estar attached enquanto a
transao estiver aberta (regra para aplicaes J2SE ou aplicaes que no utilizem EJB
Stateful com Persistence Scope Extended); para ser considerada attached a entidade
precisa ter vindo do banco de dados (por query, ou mtodo find do entity manager, ), ou
receber algum comando dentro de transao (merge, refresh).
Observe o cdigo abaixo:
1
2
3
4
entityManager.getTransaction().begin();
Car myCar = entityManager.find(Car.class, 33);
myCar.setColor(Color.RED);
entityManager. getTransaction().commit();
61
62
Note na imagem acima que aps o carro ser localizado no banco de dados ele foi
adicionado ao Persistence Context. Toda alterao realizada na entidade ser monitorada
pelo JPA. Aps a transao ser finalizada (ou comando flush), o prprio JPA ir refletir
essa alterao no banco de dados.
A exceo acontece quando relacionamos uma entidade com a outra. Veja o cdigo e a
imagem abaixo que ir explicar melhor a situao:
1
2
3
4
5
6
entityManager.getTransaction().begin();
Person newPerson = new Person();
newPerson.setName("Mary");
Car myCar = entityManager.find(Car.class, 33);
myCar.setOwner(newPerson);
entityManager. getTransaction().commit();
A entidade Car agora est relacionada com a entidade Person. O problema que a
pessoa se encontra fora do Persistence Context, note que a entidade Person no foi
localizada no banco de dados ou attached transao. Ao realizar o commit o JPA no
consegue reconhecer que a pessoa uma entidade nova, que ainda no existe no banco.
Mesmo que o objeto pessoa fosse um objeto j salvo, ao vir de fora da transao (de um
ManagedBean do JSF ou de uma Action do Struts por exemplo), ele ainda assim no seria
reconhecido nesse Persistence Context. Um objeto fora do Persistence Context
chamado de detached.
Essa situao de uma entidade detached pode ocorrer em qualquer tipo de ao do JPA:
INSERT, UPDATE, DELETE
63
Para ajudar o desenvolvedor nessas situaes o JPA criou a opo Cascade. Essa opo
pode ser definidas anotaes @OneToOne, @OneToMany e @ManyToMany. Existe um
enum que contm todas as opes de cascade possvel: javax.persistence.CascadeType.
O cascade tem as opes abaixo:
CascadeType.DETACH
CascadeType.MERGE
CascadeType.PERSIST
CascadeType.REFRESH
CascadeType.REMOVE
CascadeType.ALL
1 import javax.persistence.*;
2
3 @Entity
4 public class Car {
5
6
@Id
7
@GeneratedValue
8
private int id;
9
10
private String name;
11
12
13
14
15
16 }
@OneToOne(cascade = CascadeType.PERSIST)
private Person person;
// get and set
No cdigo acima foi definido que o relacionamento @OneToOne com Person sofreria a
ao Cascade.PERSIST toda vez que o comando entityManager.persist(car) for
executado; toda vez que um carro for persistido o JPA realizar o persist tambm no
relacionamento com a entidade Person.
A vantagem do Cascade a propagao automaticamente da ao configurada nos
relacionamentos da entidade.
Veja na tabela abaixo todas as opes de Cascade possveis at hoje:
64
Tipo
Ao
Disparado por
CascadeType.DETACH
especfico:
entityManager.persist().
CascadeType.MERGE
CascadeType.PERSIST
nos relacionamentos.
entityManager.refresh().
CascadeType.ALL
Uma vez que o cascade fosse definido, o cdigo abaixo seria executado sem mensagem
de erro:
1
2
3
4
5
6
entityManager.getTransaction().begin();
Person newPerson = new Person();
newPerson.setName("Mary");
Car myCar = entityManager.find(Car.class, 33);
myCar.setOwner(newPerson);
entityManager. getTransaction().commit();
65
CascadeType.ALL (ou opes nicas) pode causar lentido em todas as aes realizadas na
entidade. Caso a entidade tenha muitas listas um merge() poderia causar todas as listas a
receberem merge().
car.setOwner(personFromDB) => se a pessoa atribuda ao relacionamento for uma pessoa
que j exista no banco mas est detached, o Cascade no ir ajudar nesse caso. Ao executar o
comando entityManager.persist(car) o JPA tentar executar o persist nos objeto do
relacionamento marcado com CascadeType.PERSIST (entityManager.persist(person)). Uma
vez que a pessoa j existe no banco o JPA tentar inserir novamente e uma mensagem de erro
ser exibida. Seria necessrio um objeto atualizado do banco de dados e o melhor modo de se
fazer utilizando o mtodo getReferenceOnly(): http://uaihebert.com/?p=1137.
Para que o Cascade seja disparado pelo JPA preciso sempre executar a ao na
entidade que contm a opo do cascade configurada. Veja o cdigo abaixo:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import javax.persistence.*;
1
2
3
4
5
6
7
8
9
10
11
11
12
13
14
import javax.persistence.*;
@Entity
public class Car {
@Id
@GeneratedValue
private int id;
private String name;
@OneToOne(cascade = CascadeType.PERSIST)
private Person person;
// get and set
}
@Entity
public class Person {
@Id
private int id;
private String name;
@OneToOne(mappedBy="person")
private Car car;
// get and set
}
66
entityManager.persist(car);
Desse modo o JPA ir analisar a classe carro e procurar por relacionamentos onde o
cascade deve ser aplicado. Caso o comando abaixo fosse executado o erro de objeto
transiente seria exibido:
1
entityManager.persist(person);
Lembre-se: o cascade s acionado pelo JPA quando a ao for executada pela entidade em que o
Cascade foi definido. No caso acima, apenas a classe Car definiu Cascade para Person, apenas a
classe Car ir disparar a funo de Cascade.
OrphanRemoval
Outra opo parecida ao CascadeType.REMOVE o OrphanRemoval. A opo
OrphanRemoval conceitualmente aplicada em casos onde uma entidade usada apenas
em outra.
Imagine a situao onde a entidade Address s vai existir dentro de um Person:
Caso uma pessoa seja salva no banco de dados uma entidade endereo tambm ser
criada. Conceitualmente a entidade Address s existe quando uma Person criada; ao
excluir uma pessoa seu endereo deve ser removido tambm. possvel perceber que
bastaria adicionar o CascadeType.REMOVE no relacionamento que o endereo seria
removido juntamente com a pessoa, mas ainda existe outra diferena.
67
Para casos como Person e Address que foi criado o a opo OrphanRemoval. O
OrphanRemoval tem quase a mesma funo do CascadeType.REMOVE, mas
conceitualmente deve ser aplicado nos casos de Composio.
Veja o exemplo abaixo:
1
2
3
4
5
6
7
8
9
10
11
12
13
1
2
3
4
5
6
7
import javax.persistence.*;
@Entity
public class Address {
@Id
@GeneratedValue
private int id;
private String name;
// get and set
}
import javax.persistence.*;
@Entity
public class Person {
8
9
10
11
12
13
14
15 }
@Id
private int id;
private String name;
@OneToMany(orphanRemoval=true)
private Address address;
// get and set
Imagine que a entidade Address ser utilizada com Person apenas e nenhuma outra
entidade utiliza essa classe. Conceitualmente essa o local ideal para se aplicar o
OrphanRemoval.
Outro fator que difere o OrphanRemoval do CascadeType.REMOVE que ao colocar o
relacionamento para null a entidade ser removida do banco de dados. Veja o cdigo
abaixo:
68
person.setAddress(null);
Quando a entidade Person fosse atualizada no banco de dados a entidade Address seria
excluda do banco de dados.
Essa opo est disponvel apenas os relacionamentos: @OneToOne e @OneToMany.
69
entityManager.remove(person);
1
2
CascadeType.REMOVE => Uma vez que a entidade for apagada o JPA ficar responsvel de
eliminar essa dependncia. Na pgina anterior desse post mostra como fazer isso.
OrphanRemoval => Uma soluo que pode ser aplicada, mas com algumas restries de
person.setCar(null);
entityManager.remove(person);
Existe um grande problema em localizar qual o relacionamento est pendente. O que pode
ser feito capturar, na mensagem de erro qual a classe com padro. Essa soluo no
70
uma soluo precisa pois ser necessrio capturar a mensagem de erro (em String) e
localizar o nome da tabela. Infelizmente essa mensagem pode variar de implementao do
JPA, JDBC driver e a linguagem em que foi desenvolvido.
71
import javax.persistence.*;
2
3 public abstract class ConnectionFactory {
4
private ConnectionFactory() {
5
}
6
7
private static EntityManagerFactory entityManagerFactory;
8
9
public static EntityManager getEntityManager(){
10
if (entityManagerFactory == null){
11
entityManagerFactory =
Persistence.createEntityManagerFactory("MyPersistenceUnit");
12
}
13
14
return entityManagerFactory.createEntityManager();
15
}
16 }
72
1 import javax.persistence.*;
2
3 @Entity
4 public class Car {
5
6
@Id
7
@GeneratedValue
8
private int id;
9
10
private String name;
11
12
@ManyToOne
13
private Person person;
14
15
16 }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.util.List;
import javax.persistence.*;
@Entity
public class Person {
@Id
private int id;
private String name;
@OneToMany(mappedBy = "person", fetch = FetchType.LAZY)
private List<Car> cars;
@Lob
@Basic(fetch = FetchType.LAZY)
private Byte[] hugePicture;
73
20
21 }
Para alterar algum relacionamento padro, basta fazer como exibido no cdigo acima.
Ao utilizar o Lazy por default o erro chamado Lazy Initialization Exception poder
acontecer. Esse erro acontece quando o atributo Lazy acessado sem nenhuma conexo
ativa. Veja esse post para mais detalhes sobre esse problema e como resolv-lo :
http://uaihebert.com/?p=1367
O problema de utilizar EAGER em todas as listas/atributos da entidade que o custo da
consulta no banco de dados pode aumentar muito. Caso todas as entidades Person
tenham uma lista de 40.000 aes no log, imagina o custo para trazer 100.000 pessoas do
banco de dados? necessrio ter bastante cautela ao escolher qual o tipo de fetch que
ser utilizado no atributo/relacionamento.
74
@OneToMany(fetch = FetchType.EAGER)
private List<Dog> dogs;
// get and set
Ao executar uma consulta para buscar uma pessoa da entidade acima a seguinte
mensagem
de
erro
aparecer
:
javax.persistence.PersistenceException:
org.hibernate.HibernateException: cannot simultaneously fetch multiple bags. Uma lista
(List, Collection) tambm pode ser conhecida por bag.
Esse erro acontece pois o Hibernate tenta igualar o nmero de resultados vindo em uma
consulta. Caso o SQL gerado retorne como resultado 2 linhas para a lista de dogs, e uma
para car, o Hibernate ir repetir esse resultado para igualar as linhas da consulta no
resultado. Veja a imagem abaixo para entender melhor o que acontece caso o seguinte
cdigo fosse executado entityManager.find(Person.class, 33):
75
Utilizar java.util.Set ao invs das outras colees => Com essa simples alterao esse erro
poder ser evitado.
Utilizar EclipseLink = > uma soluo bastante radical, mas para os usurios de JPA que
utilizam apenas as anotaes do JPA a mudana ser de baixo impacto.
Utilizar FetchType.LAZY ao invs de EAGER => Essa soluo parcial, pois caso uma
consulta seja feita e seja utiliza join fetch nas colees, o problema volta a acontecer. Uma
consulta que poderia gerar esse erro : select p from Person p join fetch p.dogs d join fetch
p.cars c. Ao optar por essa abordagem o erro de LazyInitializationException
(http://uaihebert.com/?p=1367) poder acontecer.
Utilizar @LazyCollection ou @IndexColumn do prprio Hibernate na coleo =>
necessrio conhecer bem a anotao @IndexColumn e suas implicaes quando utilizada,
pois seu comportamento varia se colocado em uma ponta do relacionamento ou na outra (no
vem ao caso desse post explicar essa anotao).
76