quarta-feira, 13 de julho de 2016

JPA: Chave Primária Composta

Fonte: http://uaihebert.com/tutorial-jpa-chave-primaria-composta/


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
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;
 
@Embeddable
public 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.
Alguma dúvida? Sugestão? Basta colocar abaixo.
Até mais.