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:

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:

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:

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.

Imagem do Console.
Imagem Banco de Dados.

E como consultar uma entidade que tem a chave primária composta? Vamos alterar nossa classe Main para executar essa pesquisa:

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:

Imagem do Console.

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.

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;
    }
}
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.

22 thoughts on “Tutorial JPA Chave Primária Composta

  1. Caramba Hebert, tranquilo? Cara, do ponto de vista da orientação a objeto e dos conceitos de DDD, esse tipo de entidade PK só para satisfazer uma necessidade de banco de dados não é estranho não? Tem como eu fazer isso de forma mais elegante? Com entidades do domínio mesmo? Obrigado!

  2. Deixa eu te fazer uma perguta,
    se por acaso eu tivesse que mapear uma tabela que tenha 4 chaves primarias, bastaria na classe CarPK adicionar elas ?

  3. Ótimo post, parabéns.

    Tenho uma entidade que possui chaves composta(IdEstado, NomeCidade). O problema é na hora do merge, quando alterado o nome da cidade é inserido um novo registro e não alterado. Obrigado.

    • Alexandre, boa tarde.

      Verifique se hashCode/equals está corretamente implementado.

      Verifique também se antes do merge se a chave está corretamente preenchida.

      Obrigado pela visita.

  4. Amigão, e se dentro da classe CarPK tiver um atributo que o tipo seja outra entidade como eu faço esse mapeamento? Ou seja, um dos atributos que compoe o @Id da entidade Car é outra entidade.

  5. Herbert, estou com problema em fazer um relacionamento ternário JPA, tenho uma tabela que faz a join de três tabelas, PROCESSO, ENVOLVIDO E TIPO_ENVOLVIDO, tem alguma dica.

    Se puder ajudar sou grato.

    Cedric

    • Olá Cimara, boa tarde.

      Eu te aconselharia a usar a abordagem do IdClass e colocá-lo como @GeneratedValue.

      Nesse post tem o link para o Mini Livro de JPA que contém mais dicas que nesse post.

      Obrigado pela visita.

  6. Bom dia.
    Eu tenho uma duvida como fica o mapemento N:N quando uma das tabelas ja e chave composta.
    Ex

    tabela_a
    @id
    int id

    tabela_b
    @id
    int id
    @id
    int Objeto objeto_id

    ai quero fazer um n:n dessa tabela como ficaria ?

    grato pela atenção.

  7. Saudações Hebert.
    Estou tendo problemas em remover um objeto com chave composta (@IdClass), sendo que esta chave é formada por dois objetos. Consigo salvar, e listar numa boa, mas o excluir não funciona, mas também não dá erro.
    Dá uma ajuda, amigo.
    Ótima semana.

    • Kleber, boa tarde.

      Honestamente não sei falar o que pode ser.

      Um jeito de forçar o erro acontecer é após o comando de delete você chamar o método entityManager.flush()

      Desse modo se estiver algum erro acontecendo será exibido imediatamente.

      Obrigado pela visita

  8. Primeiramente gostaria de agradecer pelo tutorial escrito.
    Porem estou com uma duvida, digamos que tenho uma tabela com um id composto, e outra tabela com um id simples:

    Tabela 1
    Carro
    id_carro
    id_marca

    Tabela 2
    Locadora
    Id_locadora

    Tabela N:N
    Carro_locadora
    Id_carro
    Id_marca
    Id_locadora

    • Jean, boa tarde.

      Me desculpe a demora, estive muito ocupado nesse último mês.

      A chave composta de JPA não varia de JEE para JSE. O código é o mesmo.

      Obrigado por tudo e desculpe a demora.

Leave a Comment