Tutorial JPA Chave Primária Composta
EXISTE DOIS NOVOS POSTS SOBRE ESSE ASSUNTO:
CHAVE COMPOSTA SIMPLES: http://uaihebert.com/?p=1622&page=8
CHAVE COMPOSTA COMPLEXA: http://uaihebert.com/?p=1622&page=9
CHAVE COMPOSTA SIMPLES: http://uaihebert.com/?p=1622&page=8
CHAVE COMPOSTA COMPLEXA: http://uaihebert.com/?p=1622&page=9
Bom dia.
Vamos falar hoje sobre Chave Primária Composta (Composite Key)?
Imagine que apenas o ID não seria suficiente para definir a chave primária da sua classe. Como fazer com que o JPA entenda esse mapeamento?
Vou utilizar como exemplo uma classe Car (Carro). Imagine um sistema onde para se identificar um carro, é necessário o número do chassi. Vamos supor que um novo requisito chegou e além do chassi será necessário o código de identificação do motor.
Iremos continuar exatamente do último post sobre JPA (JPA SequenceGenerator). Você poderá ver os outros posts sobre JPA caso precise de alguma ajuda para montar o ambiente necessário para rodar o código desse post: JPA TableGenerator – Chave Primária Simples, Auto Create Schema Script com: Ant, Hibernate 3 e JPA 2,Tutorial Hibernate 3 com JPA 2.
Será necessário criar uma classe para fazer o trabalho da chave composta. Vamos ver como ficará o código da classe CarPK:
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
| package com;import java.io.Serializable;public class CarPK implements Serializable { private String chassisSerialNumber; private String engineSerialNumber; public CarPK(){ // Your class must have a no-arq constructor } @Override public boolean equals(Object obj) { if(obj instanceof CarPK){ CarPK carPk = (CarPK) obj; if(!carPk.getChassisSerialNumber().equals(chassisSerialNumber)){ return false; } if(!carPk.getEngineSerialNumber().equals(engineSerialNumber)){ return false; } return true; } return false; } @Override public int hashCode() { return chassisSerialNumber.hashCode() + engineSerialNumber.hashCode(); } public String getChassisSerialNumber() { return chassisSerialNumber; } public void setChassisSerialNumber(String chassisSerialNumber) { this.chassisSerialNumber = chassisSerialNumber; } public String getEngineSerialNumber() { return engineSerialNumber; } public void setEngineSerialNumber(String engineSerialNumber) { this.engineSerialNumber = engineSerialNumber; }} |
Existem algumas normas que sua classe de chave composta deve seguir:
- Deve ter um construtor sem argumentos
- Deve implementar a interface java.io.Serializable
- Deve sobrescrever os métodos equals e hashCode
Agora vamos criar nossa classe Car:
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
38
39
40
41
42
43
44
45
| package com;import javax.persistence.Column;import javax.persistence.Entity;import javax.persistence.Id;import javax.persistence.IdClass;import javax.persistence.Table;@Entity@Table(name="CAR")@IdClass(value=CarPK.class)public class Car { @Id private String chassisSerialNumber; @Id private String engineSerialNumber; @Column private String name; // Yes, some people like to give name to theirs cars. public String getChassisSerialNumber() { return chassisSerialNumber; } public void setChassisSerialNumber(String chassisSerialNumber) { this.chassisSerialNumber = chassisSerialNumber; } public String getEngineSerialNumber() { return engineSerialNumber; } public void setEngineSerialNumber(String engineSerialNumber) { this.engineSerialNumber = engineSerialNumber; } public String getName() { return name; } public void setName(String name) { this.name = name; }} |
Repare que em nossa classe Car, apenas adicionamos a anotação @Id sem nos preocupar em definir qualquer outro tipo de anotação. Uma observação é que no atributo “name” existe a anotação @Column mas ela não é obrigatória.
Veja o código da classe Main que irá inserir um objeto da classe Car (carro) em nosso banco de dados:
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
| package com;import javax.persistence.EntityManager;import javax.persistence.EntityManagerFactory;import javax.persistence.Persistence;public class Main { public static void main(String[] args) { EntityManagerFactory emf = Persistence.createEntityManagerFactory("Hello"); EntityManager em = emf.createEntityManager(); try { em.getTransaction().begin(); Car car = new Car(); car.setChassisSerialNumber("9BW DA05X6 1 T050136"); car.setEngineSerialNumber("ABC123"); car.setName("Thunder"); em.persist(car); em.getTransaction().commit(); } catch (Exception e) { em.getTransaction().rollback(); e.printStackTrace(); } finally{ emf.close(); } System.out.println("It is over"); }} |
Vamos executar a classe Main e ver o resultado.
E como consultar uma entidade que tem a chave primária composta? Vamos alterar nossa classe Main para executar essa pesquisa:
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
| package com;import javax.persistence.EntityManager;import javax.persistence.EntityManagerFactory;import javax.persistence.Persistence;public class Main { public static void main(String[] args) { EntityManagerFactory emf = Persistence.createEntityManagerFactory("Hello"); EntityManager em = emf.createEntityManager(); try { em.getTransaction().begin(); CarPK carPK = new CarPK(); carPK.setChassisSerialNumber("9BW DA05X6 1 T050136"); carPK.setEngineSerialNumber("ABC123"); Car car = em.find(Car.class, carPK); System.out.println(car.getName()); em.getTransaction().commit(); } catch (Exception e) { em.getTransaction().rollback(); e.printStackTrace(); } finally{ emf.close(); } System.out.println("It is over"); }} |
E após executar a classe:
Para consultar uma entidade que tem sua chave primaria (primary-key) composta é necessário se criar uma instância da dessa chave e passá-la como parâmetro na consulta.
Vou mostrar outro modo de se mapear uma chave primária composta. Repare que atualmente nós temos os mesmo campos dentro da classe CarPK e Car. Ambos contêm o código do chassi e o código do motor. Podemos fazer uma pequena alteração no código para solucionar isso.
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
| package com;import java.io.Serializable;import javax.persistence.Column;import javax.persistence.Embeddable;@Embeddablepublic class CarPK implements Serializable { @Column private String chassisSerialNumber; @Column private String engineSerialNumber; public CarPK(){ // Your class must have a no-arq constructor } @Override public boolean equals(Object obj) { if(!(obj instanceof CarPK)){ CarPK carPk = (CarPK) obj; if(!carPk.getChassisSerialNumber().equals(chassisSerialNumber)){ return false; } if(!carPk.getEngineSerialNumber().equals(engineSerialNumber)){ return false; } return true; } return false; } @Override public int hashCode() { return chassisSerialNumber.hashCode() + engineSerialNumber.hashCode(); } public String getChassisSerialNumber() { return chassisSerialNumber; } public void setChassisSerialNumber(String chassisSerialNumber) { this.chassisSerialNumber = chassisSerialNumber; } public String getEngineSerialNumber() { return engineSerialNumber; } public void setEngineSerialNumber(String engineSerialNumber) { this.engineSerialNumber = engineSerialNumber; }} |
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
| package com;import javax.persistence.Column;import javax.persistence.EmbeddedId;import javax.persistence.Entity;import javax.persistence.Table;@Entity@Table(name = "CAR")public class Car { @EmbeddedId private CarPK carPK; @Column private String name; // Yes, some people like to give name to theirs cars. public String getName() { return name; } public void setName(String name) { this.name = name; } public CarPK getCarPK() { return carPK; } public void setCarPK(CarPK carPK) { this.carPK = carPK; }} |
Pequenas alterações foram feitas.
- Na classe CarPK agora existe a anotação @Column. Esses serão a partir de agora os atributos mapeados no banco de dados. Foi adicionada também a anotação @Embeddeable informando que essa classe pode ser incorporada em outra classe.
- Na classe Car foram retirados os atributos e substituídos por um atributo da classe CarPK. Foi retirado também a anotação @IdClass. Agora colocamos uma outra anotação chamada @EmbeddedId que informa que ao JPA que os campos a serem utilizados virão de dentro da classe CarPK.
Caso você queria, execute a classe Main novamente e verá que a consulta será realizada normalmente.
Atenção: Ao utilizar do recurso de chave primária composta não será possível utilizar uma “sequence” para gerar o ID automaticamente. Você terá que gerar o ID por código.
Atenção: Ao utilizar do recurso de chave primária composta não será possível utilizar uma “sequence” para gerar o ID automaticamente. Você terá que gerar o ID por código.
Alguma dúvida? Sugestão? Basta colocar abaixo.
Até mais.