Présentation des identifiants dans Hibernate

Présentation des identifiants dans Hibernate

1. introduction

Les identifiants dans Hibernate représentent la clé primaire d'une entité. Cela implique que les valeurs sont uniques afin qu'elles puissent identifier une entité spécifique, qu'elles ne sont pas nulles et qu'elles ne seront pas modifiées.

Hibernate propose différentes manières de définir des identificateurs. Dans cet article, nous examinerons chaque méthode de mappage des identifiants d'entité à l'aide de la bibliothèque.

2. Identifiants simples

La manière la plus simple de définir un identifiant consiste à utiliser l'annotation@Id.

Les identifiants simples sont mappés à l'aide de@Id vers une propriété unique de l'un de ces types: types de wrapper Java primitif et primitif,String, Date, BigDecimal, BigInteger.

Voyons un exemple rapide de définition d'une entité avec une clé primaire de typelong:

@Entity
public class Student {

    @Id
    private long studentId;

    // standard constructor, getters, setters
}

3. Identifiants générés

Si nous voulons que la valeur de la clé primaire soit générée automatiquement pour nous,we can add the @GeneratedValue annotation.

Cela peut utiliser 4 types de génération: AUTO, IDENTITY, SEQUENCE, TABLE.

Si nous ne spécifions pas de valeur explicitement, le type de génération par défaut est AUTO.

3.1. GénérationAUTO

Si nous utilisons le type de génération par défaut, le fournisseur de persistance déterminera les valeurs en fonction du type d'attribut de clé primaire. Ce type peut être numérique ouUUID.

Pour les valeurs numériques, la génération est basée sur un générateur de séquence ou de table, tandis que les valeursUUID utiliseront lesUUIDGenerator.

Voyons un exemple de mappage d'une clé primaire d'entité à l'aide de la stratégie de génération AUTO:

@Entity
public class Student {

    @Id
    @GeneratedValue
    private long studentId;

    // ...
}

Dans ce cas, les valeurs de clé primaire seront uniques au niveau de la base de données.

An interesting feature introduced in Hibernate 5 is the UUIDGenerator. Pour utiliser ceci, il suffit de déclarer un identifiant de typeUUID avec l'annotation@GeneratedValue:

@Entity
public class Course {

    @Id
    @GeneratedValue
    private UUID courseId;

    // ...
}

Hibernate générera un identifiant de la forme «8dd5f315-9788-4d00-87bb-10eed9eff566».

3.2. GénérationIDENTITY

Ce type de génération repose sur leIdentityGenerator qui attend des valeurs générées par une colonneidentity dans la base de données, ce qui signifie qu'elles sont auto-incrémentées.

Pour utiliser ce type de génération, il suffit de définir le paramètrestrategy:

@Entity
public class Student {

    @Id
    @GeneratedValue (strategy = GenerationType.IDENTITY)
    private long studentId;

    // ...
}

Une chose à noter est que la génération IDENTITY désactive les mises à jour par lots.

3.3. GénérationSEQUENCE

Pour utiliser un identifiant basé sur une séquence, Hibernate fournit la classeSequenceStyleGenerator.

Ce générateur utilise des séquences si elles sont prises en charge par notre base de données et passe à la génération de table si elles ne le sont pas.

Pour personnaliser le nom de la séquence, nous pouvons utiliser l'annotation@GenericGenerator avecSequenceStyleGenerator strategy:

@Entity
public class User {
    @Id
    @GeneratedValue(generator = "sequence-generator")
    @GenericGenerator(
      name = "sequence-generator",
      strategy = "org.hibernate.id.enhanced.SequenceStyleGenerator",
      parameters = {
        @Parameter(name = "sequence_name", value = "user_sequence"),
        @Parameter(name = "initial_value", value = "4"),
        @Parameter(name = "increment_size", value = "1")
        }
    )
    private long userId;

    // ...
}

Dans cet exemple, nous avons également défini une valeur initiale pour la séquence, ce qui signifie que la génération de la clé primaire commencera à 4.

SEQUENCE est le type de génération recommandé par la documentation Hibernate.

The generated values are unique per sequence. Si vous ne spécifiez pas de nom de séquence, Hibernate réutilisera les mêmeshibernate_sequence pour différents types.

3.4. Génération de TABLE

LeTableGenerator utilise une table de base de données sous-jacente qui contient des segments de valeurs de génération d'identifiant.

Personnalisons le nom de la table à l'aide de l'annotation@TableGenerator:

@Entity
public class Department {
    @Id
    @GeneratedValue(strategy = GenerationType.TABLE,
      generator = "table-generator")
    @TableGenerator(name = "table-generator",
      table = "dep_ids",
      pkColumnName = "seq_id",
      valueColumnName = "seq_value")
    private long depId;

    // ...
}

Dans cet exemple, nous pouvons voir que d'autres attributs tels que lespkColumnName etvalueColumnName peuvent également être personnalisés.

L'inconvénient de cette méthode est qu'elle ne s'adapte pas correctement et peut affecter négativement les performances.

Pour résumer, ces quatre types de génération entraîneront la génération de valeurs similaires mais utiliseront des mécanismes de base de données différents.

3.5. Générateur personnalisé

Si nous ne voulons utiliser aucune des stratégies prêtes à l'emploi,we can define our custom generator by implementing the IdentifierGenerator interface.

Créons un générateur qui construit des identifiants contenant un préfixeString et un nombre:

public class MyGenerator
  implements IdentifierGenerator, Configurable {

    private String prefix;

    @Override
    public Serializable generate(
      SharedSessionContractImplementor session, Object obj)
      throws HibernateException {

        String query = String.format("select %s from %s",
            session.getEntityPersister(obj.getClass().getName(), obj)
              .getIdentifierPropertyName(),
            obj.getClass().getSimpleName());

        Stream ids = session.createQuery(query).stream();

        Long max = ids.map(o -> o.replace(prefix + "-", ""))
          .mapToLong(Long::parseLong)
          .max()
          .orElse(0L);

        return prefix + "-" + (max + 1);
    }

    @Override
    public void configure(Type type, Properties properties,
      ServiceRegistry serviceRegistry) throws MappingException {
        prefix = properties.getProperty("prefix");
    }
}

Dans cet exemple,we override the generate() method from the IdentifierGenerator interface et trouve d'abord le nombre le plus élevé parmi les clés primaires existantes de la formeprefix-XX.

Ensuite, nous ajoutons 1 au nombre maximum trouvé et ajoutons la propriétéprefix pour obtenir la valeur id nouvellement générée.

Notre classe implémente également l'interfaceConfigurable, afin que nous puissions définir la valeur de la propriétéprefix dans la méthodeconfigure().

Ensuite, ajoutons ce générateur personnalisé à une entité. Pour cela,we can use the @GenericGenerator annotation with a strategy parameter that contains the full class name of our generator class:

@Entity
public class Product {

    @Id
    @GeneratedValue(generator = "prod-generator")
    @GenericGenerator(name = "prod-generator",
      parameters = @Parameter(name = "prefix", value = "prod"),
      strategy = "com.example.hibernate.pojo.generator.MyGenerator")
    private String prodId;

    // ...
}

Notez également que nous avons défini le paramètre de préfixe sur "prod".

Voyons un rapide test JUnit pour une meilleure compréhension des valeurs d'identifiant générées:

@Test
public void whenSaveCustomGeneratedId_thenOk() {
    Product product = new Product();
    session.save(product);
    Product product2 = new Product();
    session.save(product2);

    assertThat(product2.getProdId()).isEqualTo("prod-2");
}

Ici, la première valeur générée à l'aide du préfixe «prod» était «prod-1», suivie de «prod-2».

4. Identifiants composites

Outre les identifiants simples que nous avons vus jusqu'à présent, Hibernate nous permet également de définir des identifiants composites.

Un identifiant composite est représenté par une classe de clé primaire avec un ou plusieurs attributs persistants.

La classe de clé primaire doit remplir plusieurs conditions:

  • il doit être défini à l'aide des annotations@EmbeddedId ou@IdClass

  • il devrait être public, sérialisable et avoir un constructeur public sans argument

  • il doit implémenter les méthodesequals() ethashCode()

Les attributs de la classe peuvent être de base, composites ou ManyToOne tout en évitant les collections et les attributsOneToOne.

4.1. @EmbeddedId

Pour définir un identifiant en utilisant@EmbeddedId,, nous avons d'abord besoin d'une classe de clé primaire annotée avec@Embeddable:

@Embeddable
public class OrderEntryPK implements Serializable {

    private long orderId;
    private long productId;

    // standard constructor, getters, setters
    // equals() and hashCode()
}

Ensuite, nous pouvons ajouter un identifiant de typeOrderEntryPK à une entité en utilisant @EmbeddedId:

@Entity
public class OrderEntry {

    @EmbeddedId
    private OrderEntryPK entryId;

    // ...
}

Voyons comment nous pouvons utiliser ce type d'identifiant composite pour définir la clé primaire d'une entité:

@Test
public void whenSaveCompositeIdEntity_thenOk() {
    OrderEntryPK entryPK = new OrderEntryPK();
    entryPK.setOrderId(1L);
    entryPK.setProductId(30L);

    OrderEntry entry = new OrderEntry();
    entry.setEntryId(entryPK);
    session.save(entry);

    assertThat(entry.getEntryId().getOrderId()).isEqualTo(1L);
}

Ici, l'objetOrderEntry a un identifiant principalOrderEntryPK formé de deux attributs:orderId etproductId.

4.2. @IdClass

L'annotation@IdClass est similaire à@EmbeddedId, sauf que les attributs sont définis dans la classe d'entité principale en utilisant@Id pour chacun.

La classe de clé primaire aura le même aspect qu'avant.

Réécrivons l'exemple deOrderEntry avec un@IdClass:

@Entity
@IdClass(OrderEntryPK.class)
public class OrderEntry {
    @Id
    private long orderId;
    @Id
    private long productId;

    // ...
}

Ensuite, nous pouvons définir les valeurs id directement sur l'objetOrderEntry:

@Test
public void whenSaveIdClassEntity_thenOk() {
    OrderEntry entry = new OrderEntry();
    entry.setOrderId(1L);
    entry.setProductId(30L);
    session.save(entry);

    assertThat(entry.getOrderId()).isEqualTo(1L);
}

Notez que pour les deux types d'identificateurs composites, la classe de clé primaire peut également contenir des attributs@ManyToOne.

Hibernate permet également de définir des clés primaires constituées d'associations@ManyToOne combinées avec l'annotation@Id. Dans ce cas, la classe d'entité doit également remplir les conditions d'une classe de clé primaire.

L'inconvénient de cette méthode est qu'il n'y a pas de séparation entre l'objet entité et l'identifiant.

5. Identificateurs dérivés

Les identificateurs dérivés sont obtenus à partir de l'association d'une entité à l'aide de l'annotation@MapsId.

Tout d'abord, créons une entitéUserProfile qui dérive son identifiant d'une association un-à-un avec l'entitéUser:

@Entity
public class UserProfile {

    @Id
    private long profileId;

    @OneToOne
    @MapsId
    private User user;

    // ...
}

Ensuite, vérifions qu'une instanceUserProfile a le même identifiant que son instanceUser associée:

@Test
public void whenSaveDerivedIdEntity_thenOk() {
    User user = new User();
    session.save(user);

    UserProfile profile = new UserProfile();
    profile.setUser(user);
    session.save(profile);

    assertThat(profile.getProfileId()).isEqualTo(user.getUserId());
}

6. Conclusion

Dans cet article, nous avons vu les multiples façons dont nous pouvons définir des identifiants dans Hibernate.

Le code source complet des exemples peut être trouvéover on GitHub.