235 votes

Comment mapper une clé composite avec JPA et Hibernate ?

Dans ce code, comment générer une classe Java pour la clé composite (comment faire une clé composite dans hibernate) :

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;

0 votes

1 votes

Une très bonne série d'exemples : vladmihalcea.com/2016/08/01/

459voto

Pascal Thivent Points 295221

Pour mettre en correspondance une clé composite, vous pouvez utiliser la fonction EmbeddedId o el IdClass annotations. Je sais que cette question n'est pas strictement liée à 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é des entités

...

Une clé primaire composite doit correspondre soit à une seule unique ou à un ensemble de champs ou de ou à un ensemble de tels champs ou propriétés, comme décrites ci-dessous. Une classe de clé primaire doit être définie pour représenter une clé primaire composite. Composite composites se produisent généralement lorsque mappage à partir de bases de données anciennes, lorsque la base de données est composée de plusieurs colonnes. El EmbeddedId et IdClass Les annotations sont utilisées pour pour désigner les clés primaires composites. Voir 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 public sans argument. public sans argument.
  • Si l'accès basé sur les propriétés est utilisé, les propriétés de la clé primaire doivent être publiques ou protégées.
  • La classe de la clé primaire doit être serializable .
  • La classe de clé primaire doit définir equals y hashCode méthodes. La sémantique de l'égalité des valeurs pour ces méthodes doit être cohérente avec l'égalité des bases de données pour les types de base de données auxquels la clé est mappée.
  • Une clé primaire composite doit soit ê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 mise en correspondance avec plusieurs champs ou propriétés de la classe (voir la section 9.1.15, "Annotation IdClass Annotation").
  • Si la classe de clé primaire composite est mise en correspondance avec plusieurs champs ou propriétés de la classe d'entité, les noms des champs de clé primaire ou des propriétés de la classe de clé primaire et ceux de la classe d'entité doivent correspondre et leurs types doivent être les même.

Avec un IdClass

La classe pour 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
}

El IdClass fait correspondre plusieurs champs à la table PK.

Avec EmbeddedId

La classe pour 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;

    //...
}

El @EmbeddedId fait correspondre une classe PK à une table PK.

Différences :

  • Du point de vue du modèle physique, il n'y a pas de différences
  • @EmbeddedId communique d'une certaine manière plus clairement que la clé est une clé composite et que l'OMI a du sens. lorsque le pk combiné est soit une entité significative en soi soit réutilisé dans votre code .
  • @IdClass est utile pour spécifier qu'une certaine combinaison de champs est unique, mais ceux-ci n'ont pas de signification particulière. .

Ils affectent également la manière dont vous écrivez les requêtes (en 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" (en anglais)
    • Section 9.1.15 "IdClass Annotation" (Annotation de classe d'identité)

22 votes

Il existe également une solution spécifique à Hibernate : Mapper plusieurs propriétés en tant que propriétés @Id sans déclarer une classe externe comme étant le type d'identifiant (et utiliser l'annotation IdClass). Voir 5.1.2.1. Identifiant composite dans le manuel Hibernate.

0 votes

Pourriez-vous jeter un coup d'œil à cette question s'il vous plaît ? J'ai des problèmes avec une clé primaire composite car le champ membre id est toujours null et n'est pas généré :/

0 votes

Pourriez-vous 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 de la classe IdClass. Oh et y compris les noms de colonnes, merci.

53voto

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

0 votes

@Thierry-DimitriRoy comment pourrais-je assigner le timeId.levelStation et le timeId.confPathID. Pouvez-vous me donner un exemple ?

0 votes

@Thierry-DimitriRoy La classe primaire ne peut-elle pas être une classe interne statique de la classe entité ?

0 votes

Oui, ça pourrait être

7voto

Mykhaylo Adamovych Points 1907

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

  1. Lors de la mise en œuvre des égaux, vous devez utilisation de l'instanceof pour permettre la comparaison avec les sous-classes. Si Hibernate charge paresseusement 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 piège est d'utiliser quelque chose comme name.equals(that.name) au lieu de name.equals(that.getName()) . Le premier échouera, si c'est un proxy.

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

6voto

javydreamercsw Points 730

On dirait que vous faites ça à partir de rien. Essayez d'utiliser les outils de rétro-ingénierie disponibles comme Netbeans Entities from Database pour au moins automatiser les bases (comme les identifiants intégrés). Cela peut devenir un énorme casse-tête si vous avez beaucoup de 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 à la partie la plus importante, ce que vous avez l'intention de faire.

2voto

Maurice Perry Points 18154

Une autre option consiste à le mapper comme une 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>

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X