Eine Übersicht der Bezeichner im Ruhezustand

Eine Übersicht der Bezeichner im Ruhezustand

1. Einführung

Bezeichner im Ruhezustand stellen den Primärschlüssel einer Entität dar. Dies bedeutet, dass die Werte eindeutig sind, damit sie eine bestimmte Entität identifizieren können, dass sie nicht null sind und dass sie nicht geändert werden.

Der Ruhezustand bietet verschiedene Möglichkeiten zum Definieren von Bezeichnern. In diesem Artikel werden alle Methoden zum Zuordnen von Entitäts-IDs mithilfe der Bibliothek beschrieben.

2. Einfache Kennungen

Der einfachste Weg, einen Bezeichner zu definieren, ist die Verwendung der Annotation@Id.

Einfache IDs werden mit@Id einer einzelnen Eigenschaft eines dieser Typen zugeordnet: Java-Primitiv- und Primitiv-Wrapper-Typen,String, Date, BigDecimal, BigInteger.

Sehen wir uns ein kurzes Beispiel für die Definition einer Entität mit einem Primärschlüssel vom Typlong: an

@Entity
public class Student {

    @Id
    private long studentId;

    // standard constructor, getters, setters
}

3. Generierte Kennungen

Wenn der Primärschlüsselwert automatisch für uns generiert werden soll,we can add the @GeneratedValue annotation.

Dies kann 4 Generierungstypen verwenden: AUTO, IDENTITY, SEQUENCE, TABLE.

Wenn wir keinen expliziten Wert angeben, ist der Generierungstyp standardmäßig AUTO.

3.1. AUTO Erzeugung

Wenn wir den Standardgenerierungstyp verwenden, ermittelt der Persistenzanbieter die Werte basierend auf dem Typ des Primärschlüsselattributs. Dieser Typ kann numerisch oderUUID. sein

Für numerische Werte basiert die Generierung auf einem Sequenz- oder Tabellengenerator, währendUUID Werte dieUUIDGenerator. verwenden

Sehen wir uns ein Beispiel für die Zuordnung eines Entitätsprimärschlüssels mithilfe der AUTO-Generierungsstrategie an:

@Entity
public class Student {

    @Id
    @GeneratedValue
    private long studentId;

    // ...
}

In diesem Fall sind die Primärschlüsselwerte auf Datenbankebene eindeutig.

An interesting feature introduced in Hibernate 5 is the UUIDGenerator. Um dies zu verwenden, müssen wir lediglich eine ID vom TypUUID mit@GeneratedValue Annotation deklarieren:

@Entity
public class Course {

    @Id
    @GeneratedValue
    private UUID courseId;

    // ...
}

Der Ruhezustand generiert eine ID der Form "8dd5f315-9788-4d00-87bb-10eed9eff566".

3.2. IDENTITY Erzeugung

Diese Art der Generierung basiert aufIdentityGenerator, die Werte erwarten, die von eineridentity-Spalte in der Datenbank generiert werden, was bedeutet, dass sie automatisch inkrementiert werden.

Um diesen Generierungstyp zu verwenden, müssen wir nur den Parameterstrategy festlegen:

@Entity
public class Student {

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

    // ...
}

Zu beachten ist, dass die IDENTITY-Generierung Stapelaktualisierungen deaktiviert.

3.3. SEQUENCE Erzeugung

Um eine sequenzbasierte ID zu verwenden, stellt Hibernate die KlasseSequenceStyleGeneratorbereit.

Dieser Generator verwendet Sequenzen, wenn sie von unserer Datenbank unterstützt werden, und wechselt zur Tabellengenerierung, wenn dies nicht der Fall ist.

Um den Sequenznamen anzupassen, können wir die Annotation@GenericGenerator mitSequenceStyleGenerator strategy: verwenden

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

    // ...
}

In diesem Beispiel haben wir auch einen Anfangswert für die Sequenz festgelegt. Dies bedeutet, dass die Primärschlüsselgenerierung bei 4 beginnt.

SEQUENCE ist der in der Hibernate-Dokumentation empfohlene Generierungstyp.

The generated values are unique per sequence. Wenn Sie keinen Sequenznamen angeben, verwendet Hibernate dieselbenhibernate_sequence für verschiedene Typen erneut.

3.4. Tabellengenerierung

TableGenerator verwendet eine zugrunde liegende Datenbanktabelle, die Segmente von Bezeichnergenerierungswerten enthält.

Passen wir den Tabellennamen mithilfe der Annotation@TableGeneratoran:

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

    // ...
}

In diesem Beispiel sehen wir, dass auch andere Attribute wiepkColumnName undvalueColumnName angepasst werden können.

Der Nachteil dieser Methode ist, dass sie nicht gut skaliert werden kann und die Leistung beeinträchtigen kann.

Zusammenfassend lässt sich sagen, dass diese vier Generierungstypen dazu führen, dass ähnliche Werte generiert werden, jedoch unterschiedliche Datenbankmechanismen verwendet werden.

3.5. Benutzerdefinierter Generator

Wenn wir keine der sofort einsatzbereiten Strategien verwenden möchten,we can define our custom generator by implementing the IdentifierGenerator interface.

Erstellen wir einen Generator, der Bezeichner erstellt, die das PräfixStringund eine Zahl enthalten:

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

In diesem Beispiel werdenwe override the generate() method from the IdentifierGenerator interface und zuerst die höchste Zahl aus den vorhandenen Primärschlüsseln der Formprefix-XX. ermittelt

Dann addieren wir 1 zu der maximal gefundenen Anzahl und hängen die Eigenschaftprefixan, um den neu generierten ID-Wert zu erhalten.

Unsere Klasse implementiert auch die SchnittstelleConfigurable, sodass wir den Eigenschaftswertprefixin der Methodeconfigure() festlegen können.

Als Nächstes fügen wir diesen benutzerdefinierten Generator einer Entität hinzu. Dazuwe 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;

    // ...
}

Beachten Sie auch, dass wir den Präfix-Parameter auf "prod" gesetzt haben.

Sehen wir uns einen kurzen JUnit-Test an, um die generierten ID-Werte besser zu verstehen:

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

Hier war der erste Wert, der mit dem Präfix "prod" generiert wurde, "prod-1", gefolgt von "prod-2".

4. Zusammengesetzte Kennungen

Neben den einfachen Bezeichnern, die wir bisher gesehen haben, können wir mit Hibernate auch zusammengesetzte Bezeichner definieren.

Eine zusammengesetzte ID wird durch eine Primärschlüsselklasse mit einem oder mehreren beständigen Attributen dargestellt.

Die Primärschlüsselklasse muss mehrere Bedingungen erfüllen:

  • Es sollte mit@EmbeddedId oder@IdClass Annotationen definiert werden

  • Es sollte öffentlich, serialisierbar und mit einem öffentlichen Konstruktor ohne Argumente versehen sein

  • Es sollte die Methodenequals() undhashCode() implementieren

Die Attribute der Klasse können Basic, Composite oder ManyToOne sein, wobei Sammlungen undOneToOne-Attribute vermieden werden.

4.1. @EmbeddedId

Um eine ID zuerst mit@EmbeddedId, zu definieren, benötigen wir eine mit@Embeddable: annotierte Primärschlüsselklasse

@Embeddable
public class OrderEntryPK implements Serializable {

    private long orderId;
    private long productId;

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

Als Nächstes können wir einer Entität mit @EmbeddedId eine ID vom TypOrderEntryPK hinzufügen:

@Entity
public class OrderEntry {

    @EmbeddedId
    private OrderEntryPK entryId;

    // ...
}

Lassen Sie uns sehen, wie wir diese Art von zusammengesetzter ID verwenden können, um den Primärschlüssel für eine Entität festzulegen:

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

Hier hat dasOrderEntry-Objekt eineOrderEntryPK primäre ID, die aus zwei Attributen besteht:orderId undproductId.

4.2. @IdClass

Die Annotation@IdClass ähnelt der Annotation@EmbeddedId,, außer dass die Attribute in der Hauptentitätsklasse unter Verwendung von@Id für jede definiert sind.

Die Primärschlüsselklasse sieht genauso aus wie zuvor.

Schreiben wir das Beispiel vonOrderEntrymit@IdClass: um

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

    // ...
}

Dann können wir die ID-Werte direkt für das ObjektOrderEntryfestlegen:

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

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

Beachten Sie, dass die Primärschlüsselklasse für beide Arten von zusammengesetzten IDs auch@ManyToOne-Attribute enthalten kann.

Im Ruhezustand können auch Primärschlüssel definiert werden, die aus@ManyToOne-Zuordnungen in Kombination mit@Id-Anmerkungen bestehen. In diesem Fall sollte die Entitätsklasse auch die Bedingungen einer Primärschlüsselklasse erfüllen.

Der Nachteil dieser Methode besteht darin, dass zwischen dem Entitätsobjekt und dem Bezeichner keine Trennung besteht.

5. Abgeleitete Kennungen

Abgeleitete Bezeichner werden aus der Zuordnung einer Entität unter Verwendung der Annotation@MapsIdabgerufen.

Zunächst erstellen wir eineUserProfile-Entität, die ihre ID aus einer Eins-zu-Eins-Zuordnung mit derUser-Entität ableitet:

@Entity
public class UserProfile {

    @Id
    private long profileId;

    @OneToOne
    @MapsId
    private User user;

    // ...
}

Als Nächstes überprüfen wir, ob die Instanz vonUserProfiledieselbe ID wie die zugehörige Instanz vonUserhat:

@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. Fazit

In diesem Artikel haben wir die verschiedenen Möglichkeiten gesehen, wie wir Bezeichner im Ruhezustand definieren können.

Der vollständige Quellcode der Beispiele istover on GitHub.