Was gibt’s Neues in JPA 2.2

#JavaEE #JPA

Für die beliebte Persistenzspezifikation gab es im Rahmen von Java EE 8 bislangerst ein Maintenance-Release. Aber auch darin verbergen sich ein paar interessante Features, auf die viele Entwickler bereits gewartet haben.

Java EE 8 brachte einige Neuerungen in die Java-Enterprise-Welt. Für die Java-Persistence-API -Spezifikation (JSR 338) blieb allerdings nicht viel Zeit übrig und es reichte nur für ein kleines Maintenance-Release. Darin wurde die Spezifikation vor allem an Java 8 angepasst. Dies war erforderlich, da die Java-Persistence-API (JPA) 2.1 bereits vor der Veröffentlichung von Java 8
released wurde und somit die neuen Datentypen und Programmierkonzepte nicht unterstützt.

Mit JPA 2.2 können nun einige Klassen des Date-and-Time-API verwendet und Abfrageergebnisse als Stream verarbeitet werden. Einige Annotationen wurden als @Repeatable deklariert, sodass die Verwendung von Container-Annotationen nun nicht mehr erforderlich ist. Außerdem wird nun CDI (Injection in AttributeConverter) unterstützt, wodurch u.a. Konvertierungsalgorithmen
leichter wiederverwendet werden können.
Die meisten Anwendungsentwickler haben auf die Anpassungen an Java 8 bereits seit einiger Zeit gewartet und diese werden voraussichtlich auch die größten Auswirkungen auf bestehende und zukünftige Anwendungen haben.

Wiederholbare Annotationen

Bis zur Einführung von @Repeatable Annotationen in Java 8 konnte jede Klasse, Methode und Eigenschaft zwar mit verschiedenen Annotationen, jedoch mit jeder Annotation nur einmal annotiert werden.

In allen JPA-basierten Anwendungen gibt es allerdings zahlreiche Fälle in denen dies nicht ausreicht. Ein typisches Beispiel ist die @NamedQuery Annotation, mit der Datenbankabfragen  definiert werden können. Für die meisten Entitäten werden mehrere Abfragen definiert, sodass auch mehrere @NamedQuery Annotationen benötigt werden.

Um die in älteren Java-Versionen bestehende Einschränkung zu umgehen, definiert die JPA-Spezifikation eine Reihe von Container-Annotationen, wie die bekannte @NamedQueries Annotation. Die einzige Aufgabe dieser Annotationen ist es, ein Array anderer Annotationen als Parameter zu verwalten und somit das mehrfache Annotieren mit derselben Annotation zu ermöglichen.

(Listing 1)
@NamedQueries({
  @NamedQuery(name = MyEntity.findByUser, query = „SELECT e FROM
MyEntity e WHERE e.user = :user“),
  @NamedQuery(name = MyEntity.findByPublishingDate, query = „SELECT e
FROM MyEntity e WHERE e.date = :date“)})
public class MyEntity { … }

Seit Java 8 besteht diese Einschränkung nicht mehr. Als @Repeatable gekennzeichnete Annotationen können nun mehrfach für dieselbe Klasse, Methode oder Eigenschaft verwendet werden. Der Compiler fügt dabei automatisch die umschließende Container-Annotation hinzu. Container-Annotationen werden also weiterhin benötigt. Allerdings müssen sie nur noch bei der Implementierung der Annotation deklariert und nicht mehr bei der Anwendungsentwicklung verwendet werden.

Mit JPA 2.2 wurden nun alle Annotationen, für die es in der Spezifikation eine Container-Annotationen gibt, als @Repeatable gekennzeichnet. Dies sind:

• javax.persistence.AssociationOverride,
• javax.persistence.AttributeOverride,
• javax.persistence.JoinColumn,
• javax.persistence.MapKeyJoinColumn,
• javax.persistence.NamedEntityGraph,
• javax.persistence.NamedNativeQuery,
• javax.persistence.NamedQuery,
• javax.persistence.NamedStoredProcedureQuery,
• javax.persistence.PersistenceContext,
• javax.persistence.PersistenceUnit,
• javax.persistence.PrimaryKeyJoinColumn,
• javax.persistence.SecondaryTable,
• javax.persistence.SqlResultSetMapping,
• javax.persistence.SequenceGenerator,
• javax.persistence.TableGenerator.

Somit können die genannten Annotationen nun ohne Container-Annotationen verwendet werden.

(Listing 2)
@NamedQuery(name = MyEntity.findByUser, query = „SELECT e FROM MyEntity e WHERE e.user = :user“)
@NamedQuery(name = MyEntity.findByPublishingDate, query = „SELECT e FROM MyEntity e WHERE e.date = :date“)
public class MyEntity { … }

Abfrageergebnisse als Stream verarbeiten

Mit JPA wurde das Query Interface um die getResultStream Methode erweitert. Die neue Methode stellt das Ergebnis einer Abfrage als Stream zur Verfügung.
Die Methode mag auf den ersten Blick überflüssig erscheinen, da dies über einen kleinen Umweg auch bereits mit JPA 2.1 möglich war (Listing 3). Es musste lediglich das Abfrageergebnis mit Hilfe der getResultList Methode als List abgerufen werden. Das List Interface bietet mit der stream Methode eine Möglichkeit, einen Stream auf Basis der Liste zu erstellen. Anschließend kann dann mit der Stream-API durch die Datensätze des Abfrageergebnisses iteriert und diese verarbeitet werden.

(Listing 3)
TypedQuery<MyEntity> q = em.createQuery(„SELECT e FROM MyEntity e“, MyEntity.class);
Stream<MyEntity> s = q.getResultList().stream();

Dieser Ansatz hat bei größeren Ergebnismengen jedoch einen Nachteil. Beim Aufruf der getResultList Methode müssen alle Datensätze des Abfrageergebnisses gelesen und in die entsprechende Datenstruktur überführt werden. In (Listing 3) bedeutet dies, dass zuerst alle Datensätze der durch die MyEntity abgebildeten Datenbanktabelle ausgelesen und anschließend in
managed Entitäten überführt werden müssen. Im Anschluss werden die Entitäten dann als Stream zur Verfügung gestellt und einzeln verarbeitet.
Bei größeren Datenmengen ist es häufig allerdings sinnvoller durch die Ergebnismenge zu iterieren und nicht alle Datensätze auf einmal zu laden. Dadurch können bereits verarbeitete und somit nicht mehr benötigte Datensätze vom aktuellen Persistenzkontext detacht und dem Garbage-Collector übergeben werden. Das reduziert den Speicherbedarf der Anwendung und den internen Verwaltungsaufwand der durch den Persistenzkontext verwalteten Entitäten.
Die JDBC-API bietet bereits die Möglichkeit durch das ResultSet zu iterieren. Mit der neuen getResultStream Methode haben JPA-Implementierungen nun die Möglichkeit eine für die Stream-Verarbeitung optimierte Methode zu implementieren und dieses JDBC-Feature zu nutzen.

(Listing 4)
TypedQuery<MyEntity> q = em.createQuery(„SELECT e FROM MyEntity e“, MyEntity.class);
Stream<MyEntity> s = q.getResultStream();

Das bedeutet allerdings nicht, dass alle JPA-Implementierungen eine auf die Stream-Verarbeitung optimierte Implementierung der getResultStream Methode bieten müssen. Die Spezifikation
stellt eine Default-Implementierung der Methode bereit, die den in (Listing 3) gezeigten Ansatz implementiert. Wenn diese nicht überschrieben wird, bietet die getResultStream Methode lediglich einen einfacheren Zugriff auf den Stream. Es hängt daher von der jeweiligen JPA-Implementierung ab, ob die neue getResultStream Methode einen echten Mehrwert oder lediglich eine Vereinfachung der API bietet.
Unabhängig von der Implementierung der getResultStream Methode sollte außerdem bedacht werden, dass die Datenbankabfrage weiterhin die effizienteste Möglichkeit zur Auswahl und Transformation von Datensätzen bietet. Auch wenn mit Hilfe der Stream-API die Sortierung des Streams geändert, Elemente gefiltert und die Verarbeitung vorzeitig beendet werden kann, sollte dies nur in Ausnahmefällen genutzt werden. Datenbanken sind für diese Anwendungsfälle optimiert und erzielen eine deutlich bessere Performanz als ein selbstentwickelter Java-Algorithmus.

Klassen der Date and Time API persistieren

Seit der Einführung des Date-and-Time-API werden in vielen Projekten LocalDate und LocalDateTime anstelle von java.util.Date verwendet. Mit JPA 2.1 wurden diese Klassen allerdings noch nicht als Typ für Entitätseigenschaft unterstützt. Entwickler mussten sich daher entscheiden, ob sie:

• auf die Verwendung dieser Klassen in Entitäten verzichten oder
• die proprietäre Funktionalität einer JPA-Implementierung oder eines anderen Frameworks wie Spring Data einsetzen, oder
• eine eigene Datentypkonvertierung mit Hilfe eines Attribute-Converter bereitstellen wollen.

Ab JPA 2.2 ist diese Entscheidung nicht mehr notwendig. Die Spezifikation unterstützt die folgenden Klassen des Date-and-Time-API als Basistypen:

Java Type Beschreibung JDBC Type
java.time.LocalDate DATE
java.time.LocalTime TIME
java.time.LocalDateTime TIMESTAMP
java.time.OffsetTime TIME_WITH_TIMEZONE
java.time.OffsetDateTime TIMESTAMP_WITH_TIMEZONE

Eigenschaften dieser Typen können nun ohne zusätzliche Annotationen auf Datenbankspalten abgebildet werden (Listing 5). Da die Date-and-Time-API unterschiedliche Klassen zur Speicherung
von Datum und Datum mit Uhrzeit bietet, wird auch die früher häufig verwendete @Temporal Annotation nicht mehr benötigt. Die neuen Klassen liefern dem Persistenzprovider bereits alle benötigten Informationen.
Einige JPA-Implementierung, z.B. Hibernate, können auch anderen Klassen des Date-and-Time-API abbilden. Diese werden in nachfolgenden JPA-Versionen hoffentlich auch durch die Spezifikation unterstützt werden.

(Listing 5)
@Entity
public class MyEntity {

  @Id
  @GeneratedValue(strategy = GenerationType.SEQUENCE)
  private Long id;

  private LocalDate localDate;

  private LocalDateTime localDateTime;
  public LocalDate getLocalDate() {
    return localDate;
    }

  public void setLocalDate(LocalDate localDate) {
    this.localDate = localDate;
  }

  public LocalDateTime getLocalDateTime() {
    return localDateTime;
  }

  public void setLocalDateTime(LocalDateTime localDateTime) {
    this.localDateTime = localDateTime;
  }

  public Long getId() {
    return id;
  }
}

Die Verwendung von Eigenschaften der unterstützten Typen der Date-and-Time-API unterscheiden sich nicht von der Verwendung anderen Eigenschaften.

(Listing 6)
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();

MyEntity e = new MyEntity();
e.setLocalDate(LocalDate.now());
e.setLocalDateTime(LocalDateTime.now());
em.persist(e);

em.getTransaction().commit();
em.close();

 

Fazit:

Wie von einem Maintenance-Release zu erwarten, bringt JPA 2.2 nur eine sehr geringe Anzahl an Änderungen. Diese dienen vor allem zur Unterstützung der mit Java 8 eingeführten Datentypen
und Programmierkonzepten. Da die vorherige Version der Spezifikation allerdings schon sehr ausgereift war und für die meisten Anwendungsfälle gute Lösungsmöglichkeiten bot, adressiert die
neue Version trotzdem die häufigsten Fragestellungen vieler Anwendungsentwickler.

Aktuell wird die JPA-Spezifikation, wie auch alle anderen Java-EE-Spezifikationen, an die Eclipse Foundation übergeben. Es bleibt abzuwarten, in welche Richtung und mit welcher Geschwindigkeit die Entwicklung danach weitergeht. Der JPA-Bugtracker und die sehr aktive Diskussion über eine asynchrone JDBC-API dürften ausreichend Anregungen für weitere Verbesserungen liefern. Und mit der MicroProfile-Initiative gibt es in der Eclipse Foundation auch bereits ein positives Beispiel, wie die Community ein Projekt ohne Oracles Schirmherrschaft
erfolgreich weiterentwickeln kann.

Thorben Janssen ist freiberuflicher Trainer und Autor des Buchs Hibernate Tips – „More than 70 solutions to common Hibernate problems“. Er entwickelt seit mehr als 15 Jahren Anwendungen auf Basis von Java EE und ist Mitglied der CDI 2.0 Expert Group. Auf seinem Blog schreibt er mehrmals wöchentlich über JPA und Hibernate.

Website https://www.thoughts-on-java.org
Xing https://www.xing.com/profile/Thorben_Janssen
Linkedin https://www.linkedin.com/in/thorbenjanssen
Github https://github.com/thjanssen
Email thorben@thoughts-on-java.org
Twitter https://twitter.com/@thjanssen123

Carolyn Molski


Leave a Reply