Dynamisches Mapping mit Ruhezustand

Dynamische Zuordnung mit Ruhezustand

1. Einführung

In diesem Artikel werden einige dynamische Zuordnungsfunktionen von Hibernate mit den Anmerkungen@Formula,@Where,@Filter und@Any erläutert.

Beachten Sie, dass, obwohl Hibernate die JPA-Spezifikation implementiert, die hier beschriebenen Anmerkungen nur in Hibernate verfügbar und nicht direkt auf andere JPA-Implementierungen übertragbar sind.

2. Projektaufbau

Um die Funktionen zu demonstrieren, benötigen wir nur die Hibernate-Core-Bibliothek und eine unterstützende H2-Datenbank:


    org.hibernate
    hibernate-core
    5.2.12.Final


    com.h2database
    h2
    1.4.194

Für die aktuelle Version derhibernate-core-Bibliothek gehen Sie zuMaven Central.

3. Berechnete Spalten mit@Formula

Angenommen, wir möchten einen Entitätsfeldwert basierend auf einigen anderen Eigenschaften berechnen. Eine Möglichkeit besteht darin, ein berechnetes schreibgeschütztes Feld in unserer Java-Entität zu definieren:

@Entity
public class Employee implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    private long grossIncome;

    private int taxInPercents;

    public long getTaxJavaWay() {
        return grossIncome * taxInPercents / 100;
    }

}

Der offensichtliche Nachteil ist, dasswe’d have to do the recalculation each time we access this virtual field by the getter.

Es wäre viel einfacher, den bereits berechneten Wert aus der Datenbank abzurufen. Dies kann mit der Annotation@Formulaerfolgen:

@Entity
public class Employee implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    private long grossIncome;

    private int taxInPercents;

    @Formula("grossIncome * taxInPercents / 100")
    private long tax;

}

Mit@Formula können wir Unterabfragen verwenden, native Datenbankfunktionen und gespeicherte Prozeduren aufrufen und grundsätzlich alles tun, was die Syntax einer SQL-Auswahlklausel für dieses Feld nicht verletzt.

Der Ruhezustand ist intelligent genug, um die von uns bereitgestellte SQL zu analysieren und korrekte Aliase für Tabellen und Felder einzufügen. Beachten Sie, dass der Wert der Annotation unformatiertes SQL ist und unsere Zuordnungsdatenbank daher möglicherweise abhängig macht.

Beachten Sie auch, dassthe value is calculated when the entity is fetched from the database. Wenn wir also die Entität beibehalten oder aktualisieren, wird der Wert erst dann neu berechnet, wenn die Entität aus dem Kontext entfernt und erneut geladen wird:

Employee employee = new Employee(10_000L, 25);
session.save(employee);

session.flush();
session.clear();

employee = session.get(Employee.class, employee.getId());
assertThat(employee.getTax()).isEqualTo(2_500L);

4. Entitäten mit@Where filtern

Angenommen, wir möchten der Abfrage eine zusätzliche Bedingung hinzufügen, wenn wir eine Entität anfordern.

Zum Beispiel müssen wir "soft delete" implementieren. Dies bedeutet, dass die Entität niemals aus der Datenbank gelöscht wird, sondern nur mit einemboolean-Feld als gelöscht markiert wird.

Wir müssen bei allen vorhandenen und zukünftigen Abfragen in der Anwendung sehr vorsichtig sein. Wir müssten diese zusätzliche Bedingung für jede Abfrage angeben. Glücklicherweise bietet Hibernate eine Möglichkeit, dies an einem Ort zu tun:

@Entity
@Where(clause = "deleted = false")
public class Employee implements Serializable {

    // ...
}

Die Annotation@Wherefür eine Methode enthält eine SQL-Klausel, die zu jeder Abfrage oder Unterabfrage dieser Entität hinzugefügt wird:

employee.setDeleted(true);

session.flush();
session.clear();

employee = session.find(Employee.class, employee.getId());
assertThat(employee).isNull();

Wie im Fall der Annotation@Formula istsince we’re dealing with raw SQL, the @Where condition won’t be reevaluated until we flush the entity to the database and evict it from the context.

Bis zu diesem Zeitpunkt bleibt die Entität im Kontext und kann mit Abfragen und Suchvorgängen vonid aufgerufen werden.

Die Annotation@Where kann auch für ein Sammlungsfeld verwendet werden. Angenommen, wir haben eine Liste löschbarer Telefone:

@Entity
public class Phone implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    private boolean deleted;

    private String number;

}

Dann könnten wir von der Seite vonEmployee eine Sammlung von löschbarenphones wie folgt abbilden:

public class Employee implements Serializable {

    // ...

    @OneToMany
    @JoinColumn(name = "employee_id")
    @Where(clause = "deleted = false")
    private Set phones = new HashSet<>(0);

}

Der Unterschied besteht darin, dass die Sammlung vonEmployee.phonesimmer gefiltert wird, aber wir könnten trotzdem alle Telefone, einschließlich der gelöschten, per direkter Abfrage erhalten:

employee.getPhones().iterator().next().setDeleted(true);
session.flush();
session.clear();

employee = session.find(Employee.class, employee.getId());
assertThat(employee.getPhones()).hasSize(1);

List fullPhoneList
  = session.createQuery("from Phone").getResultList();
assertThat(fullPhoneList).hasSize(2);

5. Parametrisierte Filterung mit@Filter

Das Problem mit der Annotation von@Wherebesteht darin, dass wir nur eine statische Abfrage ohne Parameter angeben können und diese bei Bedarf nicht deaktiviert oder aktiviert werden kann.

Die Annotation@Filter funktioniert genauso wie@Where, kann jedoch auch auf Sitzungsebene aktiviert oder deaktiviert und auch parametrisiert werden.

5.1. @Filter definieren

Um zu demonstrieren, wie@Filter funktioniert, fügen wir der EntitätEmployeezunächst die folgende Filterdefinition hinzu:

@FilterDef(
    name = "incomeLevelFilter",
    parameters = @ParamDef(name = "incomeLimit", type = "int")
)
@Filter(
    name = "incomeLevelFilter",
    condition = "grossIncome > :incomeLimit"
)
public class Employee implements Serializable {

Die Annotation@FilterDefdefiniert den Filternamen und eine Reihe seiner Parameter, die an der Abfrage teilnehmen. Der Typ des Parameters ist der Name eines der Ruhezustandstypen (Type,UserType oderCompositeUserType), in unserem Fall einint.

Die Annotation vonThe @FilterDefkann entweder auf Typ- oder auf Paketebene platziert werden. Beachten Sie, dass die Filterbedingung selbst nicht angegeben wird (obwohl wir den ParameterdefaultCondition angeben könnten).

Dies bedeutet, dass wir den Filter (seinen Namen und seinen Parametersatz) an einer Stelle definieren und dann die Bedingungen für den Filter an mehreren anderen Stellen unterschiedlich definieren können.

Dies kann mit der Annotation@Filtererfolgen. In unserem Fall haben wir es der Einfachheit halber in dieselbe Klasse eingeordnet. Die Syntax der Bedingung ist eine unformatierte SQL mit Parameternamen, denen Doppelpunkte vorangestellt sind.

5.2. Zugriff auf gefilterte Entitäten

Ein weiterer Unterschied zwischen@Filter und@Where besteht darin, dass@Filter standardmäßig nicht aktiviert ist. Wir müssen es auf Sitzungsebene manuell aktivieren und die Parameterwerte dafür bereitstellen:

session.enableFilter("incomeLevelFilter")
  .setParameter("incomeLimit", 11_000);

Angenommen, wir haben die folgenden drei Mitarbeiter in der Datenbank:

session.save(new Employee(10_000, 25));
session.save(new Employee(12_000, 25));
session.save(new Employee(15_000, 25));

Wenn der Filter wie oben gezeigt aktiviert ist, werden bei der Abfrage nur zwei von ihnen angezeigt:

List employees = session.createQuery("from Employee")
  .getResultList();
assertThat(employees).hasSize(2);

Beachten Sie, dass sowohl der aktivierte Filter als auch seine Parameterwerte nur innerhalb der aktuellen Sitzung angewendet werden. In einer neuen Sitzung ohne aktivierten Filter werden alle drei Mitarbeiter angezeigt:

session = HibernateUtil.getSessionFactory().openSession();
employees = session.createQuery("from Employee").getResultList();
assertThat(employees).hasSize(3);

Wenn Sie die Entität direkt über die ID abrufen, wird der Filter nicht angewendet:

Employee employee = session.get(Employee.class, 1);
assertThat(employee.getGrossIncome()).isEqualTo(10_000);

5.3. @Filter und Caching der zweiten Ebene

Wenn wir eine Anwendung mit hoher Last haben, möchten wir auf jeden Fall den Cache der zweiten Ebene im Ruhezustand aktivieren, was ein enormer Leistungsvorteil sein kann. Wir sollten bedenken, dassthe @Filter annotation does not play nicely with caching.

The second-level cache only keeps full unfiltered collections. Wenn dies nicht der Fall ist, können wir eine Sammlung in einer Sitzung mit aktiviertem Filter lesen und dann in einer anderen Sitzung dieselbe zwischengespeicherte gefilterte Sammlung erhalten, auch wenn der Filter deaktiviert ist.

Aus diesem Grund deaktiviert die Annotation@Filterim Wesentlichen das Caching für die Entität.

6. Zuordnen einer beliebigen Entitätsreferenz mit@Any

Manchmal möchten wir einen Verweis auf einen von mehreren Entitätstypen zuordnen, auch wenn diese nicht auf einem einzelnen@MappedSuperclassbasieren. Sie können sogar auf verschiedene unabhängige Tabellen abgebildet werden. Wir können dies mit der Annotation@Anyerreichen.

In unserem Beispielwe’ll need to attach some description to every entity in our persistence unit, nämlichEmployee undPhone. Es wäre unvernünftig, alle Entitäten von einer einzigen abstrakten Oberklasse zu erben, um dies zu tun.

6.1. Zuordnungsbeziehung mit@Any

So können wir einen Verweis auf eine Entität definieren, dieSerializable implementiert (d. H. Auf eine Entität überhaupt):

@Entity
public class EntityDescription implements Serializable {

    private String description;

    @Any(
        metaDef = "EntityDescriptionMetaDef",
        metaColumn = @Column(name = "entity_type"))
    @JoinColumn(name = "entity_id")
    private Serializable entity;

}

Die EigenschaftmetaDef ist der Name der Definition, undmetaColumn ist der Name der Spalte, die zur Unterscheidung des Entitätstyps verwendet wird (ähnlich wie die Diskriminatorspalte in der Zuordnung der einzelnen Tabellenhierarchie).

Wir geben auch die Spalte an, die aufid der Entität verweist. Es ist erwähnenswert, dassthis column will not be a foreign key ist, da es auf jede gewünschte Tabelle verweisen kann.

Die Spalteentity_id kann im Allgemeinen auch nicht eindeutig sein, da verschiedene Tabellen wiederholte Bezeichner haben können.

Das Paarentity_type /entity_id sollte jedoch eindeutig sein, da es die Entität, auf die wir uns beziehen, eindeutig beschreibt.

6.2. @Any definieren Zuordnung mit@AnyMetaDef

Im Moment weiß Hibernate nicht, wie verschiedene Entitätstypen unterschieden werden sollen, da wir nicht angegeben haben, was die Spalteentity_typeenthalten könnte.

Damit dies funktioniert, müssen wir die Meta-Definition des Mappings mit der Annotation@AnyMetaDefhinzufügen. Der beste Ort, um es zu platzieren, wäre die Paketebene, so dass wir es in anderen Zuordnungen wiederverwenden könnten.

So würde die Dateipackage-info.javamit der Anmerkung@AnyMetaDefaussehen:

@AnyMetaDef(
    name = "EntityDescriptionMetaDef",
    metaType = "string",
    idType = "int",
    metaValues = {
        @MetaValue(value = "Employee", targetEntity = Employee.class),
        @MetaValue(value = "Phone", targetEntity = Phone.class)
    }
)
package com.example.hibernate.pojo;

Hier haben wir den Typ der Spalteentity_type (string), den Typ der Spalteentity_id (int) und die akzeptablen Werte inentity_typeangegeben. s Spalte (“Employee” und“Phone”) und die entsprechenden Entitätstypen.

Angenommen, wir haben einen Mitarbeiter mit zwei Telefonen, die wie folgt beschrieben sind:

Employee employee = new Employee();
Phone phone1 = new Phone("555-45-67");
Phone phone2 = new Phone("555-89-01");
employee.getPhones().add(phone1);
employee.getPhones().add(phone2);

Jetzt können wir allen drei Entitäten beschreibende Metadaten hinzufügen, auch wenn sie unterschiedliche Typen haben, die nichts miteinander zu tun haben:

EntityDescription employeeDescription = new EntityDescription(
  "Send to conference next year", employee);
EntityDescription phone1Description = new EntityDescription(
  "Home phone (do not call after 10PM)", phone1);
EntityDescription phone2Description = new EntityDescription(
  "Work phone", phone1);

7. Fazit

In diesem Artikel haben wir einige Annotationen von Hibernate untersucht, mit denen die Entitätszuordnung mithilfe von Raw-SQL optimiert werden kann.

Der Quellcode für den Artikel istover on GitHub verfügbar.