Come mappare una chiave composita con JPA e Hibernate?


204

In questo codice, come generare una classe Java per la chiave composita (come comporre la chiave in ibernazione):

create table Time (
     levelStation int(15) not null,
     src varchar(100) not null,
     dst varchar(100) not null,
     distance int(15) not null,
     price int(15) not null,
     confPathID int(15) not null,
     constraint ConfPath_fk foreign key(confPathID) references ConfPath(confPathID),
     primary key (levelStation, confPathID)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


Risposte:


415

Per mappare una chiave composita, è possibile utilizzare le annotazioni EmbeddedId oIdClass . So che questa domanda non riguarda esclusivamente l'APP ma si applicano anche le regole definite dalla specifica. Quindi eccoli qui:

2.1.4 Chiavi primarie e identità dell'entità

...

Una chiave primaria composita deve corrispondere a un singolo campo o proprietà persistente o a un insieme di tali campi o proprietà come descritto di seguito. Una classe di chiave primaria deve essere definita per rappresentare una chiave primaria composita. Le chiavi primarie composite in genere sorgono quando si esegue il mapping dai database legacy quando la chiave del database è composta da più colonne. Le annotazioni EmbeddedIde IdClasssono usate per indicare chiavi primarie composite. Vedere le sezioni 9.1.14 e 9.1.15.

...

Le seguenti regole si applicano per le chiavi primarie composite:

  • La classe di chiave primaria deve essere pubblica e deve avere un costruttore no-arg pubblico.
  • Se si utilizza l'accesso basato su proprietà, le proprietà della classe di chiave primaria devono essere pubbliche o protette.
  • La classe di chiave primaria deve essere serializable.
  • La classe chiave primaria deve definire equalse hashCode metodi. La semantica dell'uguaglianza di valore per questi metodi deve essere coerente con l'uguaglianza del database per i tipi di database a cui è mappata la chiave.
  • Una chiave primaria composita deve essere rappresentata e mappata come classe incorporabile (vedere Sezione 9.1.14, "Annotazione EmbeddedId") oppure deve essere rappresentata e mappata su più campi o proprietà della classe entità (vedere Sezione 9.1.15, "IdClass Annotazione").
  • Se la classe di chiave primaria composita è mappata su più campi o proprietà della classe di entità, i nomi dei campi o delle proprietà della chiave primaria nella classe di chiave primaria e quelli della classe di entità devono corrispondere e i loro tipi devono essere gli stessi.

Con un IdClass

La classe per la chiave primaria composita potrebbe apparire (potrebbe essere una classe interna statica):

public class TimePK implements Serializable {
    protected Integer levelStation;
    protected Integer confPathID;

    public TimePK() {}

    public TimePK(Integer levelStation, Integer confPathID) {
        this.levelStation = levelStation;
        this.confPathID = confPathID;
    }
    // equals, hashCode
}

E l'entità:

@Entity
@IdClass(TimePK.class)
class Time implements Serializable {
    @Id
    private Integer levelStation;
    @Id
    private Integer confPathID;

    private String src;
    private String dst;
    private Integer distance;
    private Integer price;

    // getters, setters
}

L' IdClassannotazione associa più campi alla tabella PK.

Con EmbeddedId

La classe per la chiave primaria composita potrebbe apparire (potrebbe essere una classe interna statica):

@Embeddable
public class TimePK implements Serializable {
    protected Integer levelStation;
    protected Integer confPathID;

    public TimePK() {}

    public TimePK(Integer levelStation, Integer confPathID) {
        this.levelStation = levelStation;
        this.confPathID = confPathID;
    }
    // equals, hashCode
}

E l'entità:

@Entity
class Time implements Serializable {
    @EmbeddedId
    private TimePK timePK;

    private String src;
    private String dst;
    private Integer distance;
    private Integer price;

    //...
}

L' @EmbeddedIdannotazione associa una classe PK alla tabella PK.

differenze:

  • Dal punto di vista del modello fisico, non ci sono differenze
  • @EmbeddedIdcomunica in qualche modo più chiaramente che la chiave è una chiave composita e che IMO ha senso quando la pk combinata è o un'entità significativa stessa o viene riutilizzata nel codice .
  • @IdClass è utile specificare che alcune combinazioni di campi sono uniche ma non hanno un significato speciale .

Influiscono anche sul modo in cui scrivi le query (rendendole più o meno dettagliate):

  • con IdClass

    select t.levelStation from Time t
  • con EmbeddedId

    select t.timePK.levelStation from Time t

Riferimenti

  • Specifica JPA 1.0
    • Sezione 2.1.4 "Chiavi primarie e identità dell'entità"
    • Sezione 9.1.14 "Annotazione EmbeddedId"
    • Sezione 9.1.15 "Annotazione IdClass"

15
Esiste anche una soluzione specifica di Hibernate: mappare più proprietà come proprietà @Id senza dichiarare una classe esterna come tipo identificativo (e utilizzare l'annotazione IdClass). Vedi 5.1.2.1. Identificatore composito nel manuale di Hibernate.
Johan Boberg,

Potresti dare un'occhiata a questa domanda per favore? Sto riscontrando problemi con una chiave primaria composita poiché il campo membro idè sempre nulle non viene generato: /
displayname

Potresti per favore fare un esempio con un getter e un setter poiché ho difficoltà a vedere dove entrano in gioco in entrambi i casi. Soprattutto l'esempio IdClass. Grazie. Oh, compresi i nomi delle colonne, grazie.
Jeremy,

sebbene la soluzione specifica di ibernazione sia deprecata.
Nikhil Sahu,

Dai documenti Hibernate Annotations , su @IdClass: "È stato ereditato dai secoli bui di EJB 2 per compatibilità con le versioni precedenti e ti consigliamo di non usarlo (per semplicità)".
Marco Ferrari,

49

Devi usare @EmbeddedId:

@Entity
class Time {
    @EmbeddedId
    TimeId id;

    String src;
    String dst;
    Integer distance;
    Integer price;
}

@Embeddable
class TimeId implements Serializable {
    Integer levelStation;
    Integer confPathID;
}

@ Thierry-DimitriRoy come posso assegnare timeId.levelStation e timeId.confPathID. Potresti fornire un esempio per favore?
Duc Tran,

@ Thierry-DimitriRoy La classe primaria non può essere una classe interna statica della classe entità?
Nikhil Sahu,

Sì, potrebbe essere
Samy Omar

17

Come ho spiegato in questo articolo , supponendo che tu abbia le seguenti tabelle del database:

inserisci qui la descrizione dell'immagine

Innanzitutto, è necessario creare il @Embeddabletrattenimento dell'identificatore composito:

@Embeddable
public class EmployeeId implements Serializable {

    @Column(name = "company_id")
    private Long companyId;

    @Column(name = "employee_number")
    private Long employeeNumber;

    public EmployeeId() {
    }

    public EmployeeId(Long companyId, Long employeeId) {
        this.companyId = companyId;
        this.employeeNumber = employeeId;
    }

    public Long getCompanyId() {
        return companyId;
    }

    public Long getEmployeeNumber() {
        return employeeNumber;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof EmployeeId)) return false;
        EmployeeId that = (EmployeeId) o;
        return Objects.equals(getCompanyId(), that.getCompanyId()) &&
                Objects.equals(getEmployeeNumber(), that.getEmployeeNumber());
    }

    @Override
    public int hashCode() {
        return Objects.hash(getCompanyId(), getEmployeeNumber());
    }
}

Con questo in atto, possiamo mappare l' Employeeentità che utilizza l'identificatore composito annotandolo con @EmbeddedId:

@Entity(name = "Employee")
@Table(name = "employee")
public class Employee {

    @EmbeddedId
    private EmployeeId id;

    private String name;

    public EmployeeId getId() {
        return id;
    }

    public void setId(EmployeeId id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

L' Phoneentità a cui è @ManyToOneassociata un'associazione Employeedeve fare riferimento all'identificatore composito dalla classe padre tramite due @JoinColumnmapping:

@Entity(name = "Phone")
@Table(name = "phone")
public class Phone {

    @Id
    @Column(name = "`number`")
    private String number;

    @ManyToOne
    @JoinColumns({
        @JoinColumn(
            name = "company_id",
            referencedColumnName = "company_id"),
        @JoinColumn(
            name = "employee_number",
            referencedColumnName = "employee_number")
    })
    private Employee employee;

    public Employee getEmployee() {
        return employee;
    }

    public void setEmployee(Employee employee) {
        this.employee = employee;
    }

    public String getNumber() {
        return number;
    }

    public void setNumber(String number) {
        this.number = number;
    }
}

Per maggiori dettagli, consulta questo articolo .


Esiste uno strumento in grado di generare EmployeeId dallo schema db?
Leon,

Prova gli strumenti di ibernazione. Ha uno strumento di reverse engineering per questo.
Vlad Mihalcea,

7

La classe di chiave primaria deve definire i metodi equals e hashCode

  1. Quando si implementa uguale è necessario utilizzare instanceof per consentire il confronto con le sottoclassi. Se Hibernate lazy carica una relazione uno a uno o più a uno, avrai un proxy per la classe anziché la classe semplice. Un proxy è una sottoclasse. Il confronto dei nomi delle classi fallirebbe.
    Più tecnicamente: dovresti seguire il principio di sostituzione di Liskows e ignorare la simmetria.
  2. Il prossimo insulto sta usando qualcosa come name.equals (that.name) invece di name.equals (that.getName ()) . Il primo fallirà, se quello è un proxy.

http://www.laliluna.de/jpa-hibernate-guide/ch06s06.html


6

Sembra che tu lo stia facendo da zero. Prova a utilizzare gli strumenti di reverse engineering disponibili come entità Netbeans dal database per almeno automatizzare le basi (come gli ID incorporati). Questo può diventare un grosso mal di testa se hai molti tavoli. Suggerisco di evitare di reinventare la ruota e di utilizzare quanti più strumenti possibili per ridurre la codifica al minimo e nella parte più importante, ciò che si intende fare.


5

Facciamo un semplice esempio. Diciamo che due tabelle hanno nome teste customersono descritte come:

create table test(
  test_id int(11) not null auto_increment,
  primary key(test_id));

create table customer(
  customer_id int(11) not null auto_increment,
  name varchar(50) not null,
  primary key(customer_id));

Un altro tavolo è lì che tiene la traccia di tests e customer:

create table tests_purchased(
  customer_id int(11) not null,
  test_id int(11) not null,
  created_date datetime not null,
  primary key(customer_id, test_id));

Possiamo vedere che nella tabella tests_purchasedla chiave primaria è una chiave composita, quindi useremo il <composite-id ...>...</composite-id>tag nel hbm.xmlfile di mappatura. Quindi PurchasedTest.hbm.xmlsembrerà:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
  "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
  "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>
  <class name="entities.PurchasedTest" table="tests_purchased">

    <composite-id name="purchasedTestId">
      <key-property name="testId" column="TEST_ID" />
      <key-property name="customerId" column="CUSTOMER_ID" />
    </composite-id>

    <property name="purchaseDate" type="timestamp">
      <column name="created_date" />
    </property>

  </class>
</hibernate-mapping>

Ma non finisce qui. In Hibernate utilizziamo session.load ( entityClass, id_type_object) per trovare e caricare l'entità utilizzando la chiave primaria. Nel caso di chiavi composite, l'oggetto ID dovrebbe essere una classe ID separata (nel caso precedente una PurchasedTestIdclasse) che dichiara semplicemente gli attributi della chiave primaria come di seguito :

import java.io.Serializable;

public class PurchasedTestId implements Serializable {
  private Long testId;
  private Long customerId;

  // an easy initializing constructor
  public PurchasedTestId(Long testId, Long customerId) {
    this.testId = testId;
    this.customerId = customerId;
  }

  public Long getTestId() {
    return testId;
  }

  public void setTestId(Long testId) {
    this.testId = testId;
  }

  public Long getCustomerId() {
    return customerId;
  }

  public void setCustomerId(Long customerId) {
    this.customerId = customerId;
  }

  @Override
  public boolean equals(Object arg0) {
    if(arg0 == null) return false;
    if(!(arg0 instanceof PurchasedTestId)) return false;
    PurchasedTestId arg1 = (PurchasedTestId) arg0;
    return (this.testId.longValue() == arg1.getTestId().longValue()) &&
           (this.customerId.longValue() == arg1.getCustomerId().longValue());
  }

  @Override
  public int hashCode() {
    int hsCode;
    hsCode = testId.hashCode();
    hsCode = 19 * hsCode+ customerId.hashCode();
    return hsCode;
  }
}

Il punto importante è che implementiamo anche le due funzioni hashCode()e equals()poiché Hibernate fa affidamento su di esse.


2

Un'altra opzione è quella di mappare è come una mappa di elementi compositi nella tabella ConfPath.

Questa mappatura trarrebbe beneficio da un indice su (ConfPathID, levelStation).

public class ConfPath {
    private Map<Long,Time> timeForLevelStation = new HashMap<Long,Time>();

    public Time getTime(long levelStation) {
        return timeForLevelStation.get(levelStation);
    }

    public void putTime(long levelStation, Time newValue) {
        timeForLevelStation.put(levelStation, newValue);
    }
}

public class Time {
    String src;
    String dst;
    long distance;
    long price;

    public long getDistance() {
        return distance;
    }

    public void setDistance(long distance) {
        this.distance = distance;
    }

    public String getDst() {
        return dst;
    }

    public void setDst(String dst) {
        this.dst = dst;
    }

    public long getPrice() {
        return price;
    }

    public void setPrice(long price) {
        this.price = price;
    }

    public String getSrc() {
        return src;
    }

    public void setSrc(String src) {
        this.src = src;
    }
}

Mappatura:

<class name="ConfPath" table="ConfPath">
    <id column="ID" name="id">
        <generator class="native"/>
    </id>
    <map cascade="all-delete-orphan" name="values" table="example"
            lazy="extra">
        <key column="ConfPathID"/>
        <map-key type="long" column="levelStation"/>
        <composite-element class="Time">
            <property name="src" column="src" type="string" length="100"/>
            <property name="dst" column="dst" type="string" length="100"/>
            <property name="distance" column="distance"/>
            <property name="price" column="price"/>
        </composite-element>
    </map>
</class>

1

Utilizzando hbm.xml

    <composite-id>

        <!--<key-many-to-one name="productId" class="databaselayer.users.UserDB" column="user_name"/>-->
        <key-property name="productId" column="PRODUCT_Product_ID" type="int"/>
        <key-property name="categoryId" column="categories_id" type="int" />
    </composite-id>  

Usando l'annotazione

Classe chiave composita

public  class PK implements Serializable{
    private int PRODUCT_Product_ID ;    
    private int categories_id ;

    public PK(int productId, int categoryId) {
        this.PRODUCT_Product_ID = productId;
        this.categories_id = categoryId;
    }

    public int getPRODUCT_Product_ID() {
        return PRODUCT_Product_ID;
    }

    public void setPRODUCT_Product_ID(int PRODUCT_Product_ID) {
        this.PRODUCT_Product_ID = PRODUCT_Product_ID;
    }

    public int getCategories_id() {
        return categories_id;
    }

    public void setCategories_id(int categories_id) {
        this.categories_id = categories_id;
    }

    private PK() { }

    @Override
    public boolean equals(Object o) {
        if ( this == o ) {
            return true;
        }

        if ( o == null || getClass() != o.getClass() ) {
            return false;
        }

        PK pk = (PK) o;
        return Objects.equals(PRODUCT_Product_ID, pk.PRODUCT_Product_ID ) &&
                Objects.equals(categories_id, pk.categories_id );
    }

    @Override
    public int hashCode() {
        return Objects.hash(PRODUCT_Product_ID, categories_id );
    }
}

Classe di entità

@Entity(name = "product_category")
@IdClass( PK.class )
public  class ProductCategory implements Serializable {
    @Id    
    private int PRODUCT_Product_ID ;   

    @Id 
    private int categories_id ;

    public ProductCategory(int productId, int categoryId) {
        this.PRODUCT_Product_ID = productId ;
        this.categories_id = categoryId;
    }

    public ProductCategory() { }

    public int getPRODUCT_Product_ID() {
        return PRODUCT_Product_ID;
    }

    public void setPRODUCT_Product_ID(int PRODUCT_Product_ID) {
        this.PRODUCT_Product_ID = PRODUCT_Product_ID;
    }

    public int getCategories_id() {
        return categories_id;
    }

    public void setCategories_id(int categories_id) {
        this.categories_id = categories_id;
    }

    public void setId(PK id) {
        this.PRODUCT_Product_ID = id.getPRODUCT_Product_ID();
        this.categories_id = id.getCategories_id();
    }

    public PK getId() {
        return new PK(
            PRODUCT_Product_ID,
            categories_id
        );
    }    
}

1
Non ha senso, ha bisogno della chiave primaria
Mazen Embaby il

nel titolo dice chiave composita, che non deve essere primaria
Enerccio il

controlla cosa ha scritto chiave primaria
Mazen Embaby il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.