Comment mapper une clé composite avec JPA et Hibernate?

205

Dans ce code, comment générer une classe Java pour la clé composite (comment créer une clé composite en hibernation):

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;
kaaf
la source

Réponses:

416

Pour mapper une clé composite, vous pouvez utiliser le EmbeddedId ou les IdClassannotations. Je sais que cette question ne concerne pas strictement JPA mais les règles définies par la spécification s'appliquent également. Les voici donc:

2.1.4 Clés primaires et identité d'entité

...

Une clé primaire composite doit correspondre à un seul champ ou propriété persistante ou à un ensemble de ces champs ou propriétés, comme décrit ci-dessous. Une classe de clé primaire doit être définie pour représenter une clé primaire composite. Les clés primaires composites surviennent généralement lors du mappage à partir de bases de données héritées lorsque la clé de base de données est composée de plusieurs colonnes. Les annotations EmbeddedIdet IdClasssont utilisées pour désigner les clés primaires composites. Voir les sections 9.1.14 et 9.1.15.

...

Les règles suivantes s'appliquent aux clés primaires composites:

  • La classe de clé primaire doit être publique et doit avoir un constructeur sans argument public.
  • Si l'accès basé sur les propriétés est utilisé, les propriétés de la classe de clé primaire doivent être publiques ou protégées.
  • La classe de clé primaire doit être serializable.
  • La classe de clé primaire doit définir equalset les hashCode méthodes. La sémantique de l'égalité des valeurs pour ces méthodes doit être cohérente avec l'égalité de la base de données pour les types de base de données auxquels la clé est mappée.
  • Une clé primaire composite doit être représentée et mappée en tant que classe intégrable (voir Section 9.1.14, «Annotation EmbeddedId») ou doit être représentée et mappée sur plusieurs champs ou propriétés de la classe d'entité (voir Section 9.1.15, «IdClass Annotation").
  • Si la classe de clé primaire composite est mappée à plusieurs champs ou propriétés de la classe d'entité, les noms des champs ou des propriétés de clé primaire dans la classe de clé primaire et ceux de la classe d'entité doivent correspondre et leurs types doivent être les mêmes.

Avec un IdClass

La classe de la clé primaire composite pourrait ressembler (pourrait être une classe interne statique):

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
}

Et 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' IdClassannotation mappe plusieurs champs à la table PK.

Avec EmbeddedId

La classe de la clé primaire composite pourrait ressembler (pourrait être une classe interne statique):

@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
}

Et l'entité:

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

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

    //...
}

L' @EmbeddedIdannotation mappe une classe PK à la table PK.

Différences:

  • Du point de vue du modèle physique, il n'y a pas de différences
  • @EmbeddedIdcommunique en quelque sorte plus clairement que la clé est une clé composite et IMO est logique lorsque le pk combiné est soit une entité significative elle-même, soit réutilisé dans votre code .
  • @IdClass est utile pour spécifier qu'une combinaison de champs est unique mais ceux-ci n'ont pas de signification particulière .

Ils affectent également la façon dont vous écrivez des requêtes (les rendant plus ou moins verbeuses):

  • avec IdClass

    select t.levelStation from Time t
  • avec EmbeddedId

    select t.timePK.levelStation from Time t

Références

  • Spécification JPA 1.0
    • Section 2.1.4 "Clés primaires et identité d'entité"
    • Section 9.1.14 "Annotation EmbeddedId"
    • Section 9.1.15 "Annotation IdClass"
Pascal Thivent
la source
15
Il existe également une solution spécifique à Hibernate: mappez plusieurs propriétés en tant que propriétés @Id sans déclarer une classe externe comme type d'identificateur (et utilisez l'annotation IdClass). Voir 5.1.2.1. Identifiant composite dans le manuel Hibernate.
Johan Boberg
Pourriez-vous jeter un œil à cette question s'il vous plaît? J'ai des problèmes avec une clé primaire composite car le champ membre idest toujours nullgénéré et n'est pas généré: /
displayname
Pourriez-vous s'il vous plaît mettre un exemple avec un getter et un setter car j'ai du mal à voir où ils entrent en jeu dans les deux cas. Surtout l'exemple IdClass. Merci. Oh et en incluant les noms des colonnes, merci.
Jeremy
bien que la solution spécifique d'hibernation soit déconseillée.
Nikhil Sahu
De la documentation Hibernate Annotations , à propos de @IdClass: "Il a été hérité de l'âge sombre d'EJB 2 pour des raisons de compatibilité descendante et nous vous recommandons de ne pas l'utiliser (par souci de simplicité)."
Marco Ferrari
49

Vous devez utiliser @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-Dimitri Roy
la source
@ Thierry-DimitriRoy comment pourrais-je assigner timeId.levelStation et timeId.confPathID. Pourriez-vous donner un exemple s'il vous plaît?
Duc Tran
@ Thierry-DimitriRoy La classe primaire ne peut-elle pas être une classe interne statique de la classe entité?
Nikhil Sahu
Oui, cela pourrait être
Samy Omar
17

Comme je l'ai expliqué dans cet article , en supposant que vous disposez des tables de base de données suivantes:

entrez la description de l'image ici

Tout d'abord, vous devez créer le @Embeddablecontenant l'identifiant composite:

@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());
    }
}

Avec cela en place, nous pouvons mapper l' Employeeentité qui utilise l'identifiant composite en l'annotant avec @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é qui est @ManyToOneassociée à Employeedoit référencer l'identifiant composite de la classe parente via deux @JoinColumnmappages:

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

Pour plus de détails, consultez cet article .

Vlad Mihalcea
la source
Existe-t-il un outil qui peut générer EmployeeId à partir du schéma db?
Leon
Essayez les outils de mise en veille prolongée. Il dispose d'un outil d'ingénierie inverse pour cela.
Vlad Mihalcea
7

La classe de clé primaire doit définir les méthodes equals et hashCode

  1. Lorsque vous implémentez equals, vous devez utiliser instanceof pour permettre la comparaison avec les sous-classes. Si Hibernate paresseux charge une relation un à un ou plusieurs à un, vous aurez un proxy pour la classe au lieu de la classe ordinaire. Un proxy est une sous-classe. La comparaison des noms de classe échouerait.
    Plus techniquement: vous devez suivre le principe de substitution de Liskows et ignorer la symétrie.
  2. Le prochain écueil consiste à utiliser quelque chose comme name.equals (that.name) au lieu de name.equals (that.getName ()) . Le premier échouera, s'il s'agit d'un proxy.

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

Mike
la source
6

On dirait que vous faites cela à partir de zéro. Essayez d'utiliser les outils d'ingénierie inverse disponibles comme les entités Netbeans de la base de données pour au moins automatiser les bases (comme les identifiants intégrés). Cela peut devenir un énorme casse-tête si vous avez plusieurs tables. Je suggère d'éviter de réinventer la roue et d'utiliser autant d'outils disponibles que possible pour réduire le codage au minimum et le plus important, ce que vous avez l'intention de faire.

javydreamercsw
la source
5

Prenons un exemple simple. Disons que deux tables sont nommées testet customersont décrites comme suit:

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));

Une autre table est là qui garde la trace de tests et 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));

Nous pouvons voir que dans le tableau, tests_purchasedla clé primaire est une clé composite, nous allons donc utiliser la <composite-id ...>...</composite-id>balise dans le hbm.xmlfichier de mappage. Donc, PurchasedTest.hbm.xmlcela ressemblera à:

<?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>

Mais cela ne s'arrête pas là. Dans Hibernate, nous utilisons session.load ( entityClass, id_type_object) pour rechercher et charger l'entité à l'aide de la clé primaire. En cas de clés composites, l'objet ID doit être une classe ID distincte (dans le cas ci-dessus une PurchasedTestIdclasse) qui déclare simplement les attributs de clé primaire comme ci - dessous :

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

Le point important est que nous implémentons également les deux fonctions hashCode()et equals()qu'Hibernate s'appuie sur elles.

dinesh kandpal
la source
2

Une autre option consiste à mapper en tant que carte d'éléments composites dans la table ConfPath.

Ce mappage bénéficierait cependant d'un index sur (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;
    }
}

Cartographie:

<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>
Maurice Perry
la source
1

Utilisation de 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>  

Utiliser l'annotation

Classe de clé composite

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 d'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
        );
    }    
}
Mazen Embaby
la source
1
Cela n'a pas de sens, il a besoin de la clé primaire
Mazen Embaby
dans le titre, il dit clé composite, qui ne doit pas être primaire
Enerccio
veuillez vérifier ce qu'il a écrit la clé primaire
Mazen Embaby