Was ist neu in JPA 2.2?

Schöne Ferien an alle!

Es kam so plötzlich vor, dass der Start der zweiten Gruppe „Java Enterprise Developer“ mit dem 256. Tag des Jahres zusammenfiel. Zufall? Ich glaube nicht.

Nun, wir teilen das vorletzte Interesse: Welche neuen Dinge brachte JPA 2.2 - Streaming-Ergebnisse, verbesserte Datumskonvertierung, neue Anmerkungen - nur einige Beispiele für nützliche Verbesserungen.

Lass uns gehen!

Die Java Persistence API (JPA) ist eine grundlegende Java EE-Spezifikation, die in der Branche weit verbreitet ist. Unabhängig davon, ob Sie für die Java EE-Plattform oder für das alternative Java-Framework entwickeln, ist JPA Ihre Wahl zum Speichern von Daten. JPA 2.1 hat die Spezifikation verbessert, sodass Entwickler Probleme wie die automatische Generierung von Datenbankschemata und die effiziente Arbeit mit in der Datenbank gespeicherten Prozeduren lösen können. Die neueste Version, JPA 2.2, verbessert die Spezifikation basierend auf diesen Änderungen.
In diesem Artikel werde ich über neue Funktionen sprechen und Beispiele geben, die Ihnen den Einstieg erleichtern. Als Beispiel verwende ich das Projekt „Java EE 8 Playground“, das auf GitHub verfügbar ist. Die Beispielanwendung basiert auf der Java EE 8-Spezifikation und verwendet für die Persistenz die Frameworks JavaServer Faces (JSF), Enterprise JavaBeans (EJB) und JPA. Sie müssen mit JPA vertraut sein, um zu verstehen, worum es geht.



Verwenden von JPA 2.2

JPA Version 2.2 ist Teil der Java EE 8-Plattform. Es ist erwähnenswert, dass nur Java EE 8-kompatible Anwendungsserver eine Spezifikation bereitstellen, die sofort einsatzbereit ist. Zum Zeitpunkt dieses Schreibens (Ende 2017) gab es einige solcher Anwendungsserver. Die Verwendung von JPA 2.2 mit Java EE7 ist jedoch einfach. Zuerst müssen Sie die entsprechenden JAR-Dateien mit Maven Central herunterladen und dem Projekt hinzufügen. Wenn Sie Maven in Ihrem Projekt verwenden, fügen Sie die Koordinaten zur Maven-POM-Datei hinzu:

<dependency> <groupId>javax.persistence</groupId> <artifactId>javax.persistence-api</artifactId> <version>2.2</version> </dependency> 

Wählen Sie dann die JPA-Implementierung aus, die Sie verwenden möchten. Ab JPA 2.2 verfügen sowohl EclipseLink als auch Hibernate über kompatible Implementierungen. Als Beispiele in diesem Artikel verwende ich EclipseLink, indem ich die folgende Abhängigkeit hinzufüge:

 <dependency> <groupId>org.eclipse.persistence</groupId> <artifactId>eclipselink</artifactId> <version>2.7.0 </version> </dependency> 

Wenn Sie einen Java EE 8-kompatiblen Server wie GlassFish 5 oder Payara 5 verwenden, sollten Sie in der POM-Datei den Bereich "Bereitgestellt" für diese Abhängigkeiten angeben können. Andernfalls geben Sie den Bereich "Kompilieren" an, um sie in die Projektassembly aufzunehmen.

Java 8 Datums- und Uhrzeitunterstützung

Möglicherweise ist eine der positivsten Ergänzungen die Unterstützung der Java 8-API für Datum und Uhrzeit. Seit der Veröffentlichung von Java SE 8 im Jahr 2014 haben Entwickler Problemumgehungen verwendet, um die Datums- und Uhrzeit-API mit JPA zu verwenden. Obwohl die meisten Problemumgehungen recht einfach sind, ist die Notwendigkeit, grundlegende Unterstützung für die aktualisierte Datums- und Uhrzeit-API hinzuzufügen, längst überfällig. Die JPA-Unterstützung für die Datums- und Uhrzeit-API umfasst die folgenden Typen:

  • java.time.LocalDate
  • java.time.LocalTime
  • java.time.LocalDateTime
  • java.time.OffsetTime
  • java.time.OffsetDateTime

Zum besseren Verständnis werde ich zunächst erläutern, wie die API-Unterstützung für Datum und Uhrzeit ohne JPA 2.2 funktioniert. JPA 2.1 kann nur mit älteren java.sql.Timestamp wie java.util.Date und java.sql.Timestamp . Daher müssen Sie einen Konverter verwenden, um das in der Datenbank gespeicherte Datum in ein altes Design zu konvertieren, das von JPA 2.1 unterstützt wird, und es dann zur Verwendung in der Anwendung in eine aktualisierte Datums- und Uhrzeit-API konvertieren. Ein LocalDate in JPA 2.1, der eine solche Konvertierung ermöglicht, sieht möglicherweise wie in Listing 1 aus. Der darin enthaltene Konverter wird zum Konvertieren zwischen LocalDate und java.util.Date .

Listing 1

 @Converter(autoApply = true) public class LocalDateTimeConverter implements AttributeConverter<LocalDate, Date> { @Override public Date convertToDatabaseColumn(LocalDate entityValue) { LocalTime time = LocalTime.now(); Instant instant = time.atDate(entityValue) .atZone(ZoneId.systemDefault()) .toInstant(); return Date.from(instant); } @Override public LocalDate convertToEntityAttribute(Date databaseValue){ Instant instant = Instant.ofEpochMilli(databaseValue.getTime()); return LocalDateTime.ofInstant(instant, ZoneId.systemDefault()).toLocalDate(); } } 

JPA 2.2 muss einen solchen Konverter nicht mehr schreiben, da Sie unterstützte Datums- und Uhrzeittypen verwenden. Die Unterstützung für solche Typen ist integriert, sodass Sie den unterstützten Typ einfach ohne zusätzlichen Code im Feld für die Entitätsklasse angeben können. Das folgende Code-Snippet demonstriert dieses Konzept. Beachten Sie, dass dem @Temporal Code keine Anmerkungen @Temporal müssen, da die @Temporal automatisch erfolgt.

 public class Job implements Serializable { . . . @Column(name = "WORK_DATE") private LocalDate workDate; . . . } 

Da die unterstützten Datums- und Uhrzeittypen in der JPA erstklassige Objekte sind, können sie ohne zusätzliche Zeremonien angegeben werden. In JPA 2.1 @Temporal Anmerkung in allen konstanten Feldern und Eigenschaften des java.util.Calendar java.util.Date und java.util.Calendar .

Es ist erwähnenswert, dass in dieser Version nur ein Teil der LocalDateTime ZonedDateTime . Der Attributkonverter kann jedoch einfach generiert werden, um mit anderen Typen zu arbeiten, z. B. zum Konvertieren von LocalDateTime in ZonedDateTime . Das größte Problem beim Schreiben eines solchen Konverters besteht darin, zu bestimmen, wie am besten zwischen verschiedenen Typen konvertiert werden kann. Um die Sache noch einfacher zu machen, können jetzt Attributkonverter implementiert werden. Ich werde unten eine Beispielimplementierung geben.

Der Code in Listing 2 zeigt, wie die Zeit von LocalDateTime in ZonedDateTime .

Listing 2:

 @Converter public class LocalToZonedConverter implements AttributeConverter<ZonedDateTime, LocalDateTime> { @Override public LocalDateTime convertToDatabaseColumn(ZonedDateTime entityValue) { return entityValue.toLocalDateTime(); } @Override public ZonedDateTime convertToEntityAttribute(LocalDateTime databaseValue) { return ZonedDateTime.of(databaseValue, ZoneId.systemDefault()); } } 

Insbesondere ist dieses Beispiel sehr einfach, da ZonedDateTime Methoden enthält, die einfach zu konvertieren sind. Die Konvertierung erfolgt durch Aufrufen der toLocalDateTime() -Methode. Die inverse Konvertierung kann durchgeführt werden, indem die ZonedDateTimeOf() -Methode ZonedDateTimeOf() und der LocalDateTime Wert zusammen mit ZoneId , um die Zeitzone zu verwenden.

Eingebettete Attributkonverter

Attributkonverter waren eine sehr schöne Ergänzung zu JPA 2.1, da sie es ermöglichten, Attributtypen flexibler zu gestalten. Das JPA 2.2-Update bietet eine nützliche Möglichkeit, Attributkonverter implementierbar zu machen. Dies bedeutet, dass Sie CDI-Ressourcen (Contexts and Dependency Injection) direkt in den Attributkonverter einbetten können. Diese Änderung steht im Einklang mit anderen CDI-Verbesserungen in den Java EE 8-Spezifikationen, z. B. erweiterten JSF-Konvertern, da diese jetzt auch die CDI-Injektion verwenden können.

Um diese neue Funktion nutzen zu können, binden Sie die CDI-Ressourcen nach Bedarf einfach in den Attributkonverter ein. Listing 2 enthält ein Beispiel für einen Attributkonverter. Jetzt werde ich es auseinander nehmen und alle wichtigen Details erläutern.

Die Konverterklasse muss die Schnittstelle javax.persistence.AttributeConverter implementieren und die X- und Y-Werte übergeben. Der X-Wert entspricht dem Datentyp im Java-Objekt und der Y-Wert muss dem Datenbankspaltentyp entsprechen. Dann sollte die Konverterklasse mit @Converter kommentiert @Converter . Schließlich muss die Klasse die convertToDatabaseColumn() und convertToEntityAttribute() überschreiben. Die Implementierung in jeder dieser Methoden sollte Werte von bestimmten Typen und zurück zu diesen konvertieren.

Um den Konverter bei jeder Verwendung des angegebenen Datentyps automatisch anzuwenden, fügen Sie wie in @Converter(autoApply=true) " @Converter(autoApply=true) . Um einen Konverter auf ein einzelnes Attribut anzuwenden, verwenden Sie die Annotation @Converter auf Attributebene, wie hier gezeigt:

 @Convert(converter=LocalDateConverter.java) private LocalDate workDate; 

Der Konverter kann auch auf Klassenebene angewendet werden:

 @Convert(attributeName="workDate", converter = LocalDateConverter.class) public class Job implements Serializable { . . . 

Angenommen, ich möchte die im Feld creditLimit der creditLimit enthaltenen Werte beim creditLimit verschlüsseln. Um diesen Prozess zu implementieren, müssen die Werte vor dem Speichern verschlüsselt und nach dem Abrufen aus der Datenbank entschlüsselt werden. Dies kann vom Konverter durchgeführt werden, und mit JPA 2.2 kann ich das Verschlüsselungsobjekt in den Konverter einbetten, um das gewünschte Ergebnis zu erzielen. Listing 3 enthält ein Beispiel.

Listing 3:

 @Converter public class CreditLimitConverter implements AttributeConverter<BigDecimal, BigDecimal> { @Inject CreditLimitEncryptor encryptor; @Override public BigDecimal convertToDatabaseColumn (BigDecimal entityValue) { String encryptedFormat = encryptor.base64encode(entityValue.toString()); return BigDecimal.valueOf(Long.valueOf(encryptedFormat)); } ... } 

In diesem Code wird der Prozess ausgeführt, CreditLimitEncryptor Klasse in den Konverter CreditLimitEncryptor und anschließend zur Unterstützung des Prozesses verwendet wird.

Streaming-Abfrageergebnisse

Jetzt können Sie die Funktionen von Java SE 8-Streams problemlos nutzen, wenn Sie mit Abfrageergebnissen arbeiten. Threads vereinfachen nicht nur das Lesen, Schreiben und Verwalten von Code, sondern verbessern in einigen Situationen auch die Abfrageleistung. Einige Thread-Implementierungen tragen auch dazu bei, eine übermäßig große Anzahl von Datenanforderungen gleichzeitig zu vermeiden, obwohl in einigen Fällen die Verwendung der ResultSet Paginierung möglicherweise besser funktioniert als Streams.

Um diese Funktion zu aktivieren, wurde die Methode getResultStream() zu den TypedQuery Query und TypedQuery hinzugefügt. Diese geringfügige Änderung ermöglicht es JPA, einfach einen Ergebnisstrom anstelle einer Liste zurückzugeben. Wenn Sie also mit einem großen ResultSet , ist es sinnvoll, die Leistung zwischen einer neuen Thread-Implementierung und einem scrollbaren ResultSets oder einer Paginierung zu vergleichen. Der Grund dafür ist, dass Thread-Implementierungen alle Datensätze auf einmal abrufen, in einer Liste speichern und dann zurückgeben. Eine scrollbare ResultSet und Paginierungstechnik ruft Daten stückweise ab, was für große Datenmengen möglicherweise besser ist.

Persistenzanbieter können beschließen, die neue Methode getResultStream() verbesserter Implementierung zu überschreiben. Der ResultSet enthält bereits eine stream () -Methode, die ein scrollbares ResultSet , um die Ergebnisse von Datensätzen zu analysieren, anstatt sie vollständig zurückzugeben. Dadurch kann Hibernate mit sehr großen Datenmengen arbeiten und dies gut tun. Es ist zu erwarten, dass andere Anbieter diese Methode überschreiben, um ähnliche Funktionen bereitzustellen, die für JPA von Vorteil sind.

Neben der Leistung ist die Möglichkeit, Ergebnisse zu streamen, eine nette Ergänzung zu JPA, die eine bequeme Möglichkeit bietet, mit Daten zu arbeiten. Ich werde einige Szenarien demonstrieren, in denen dies nützlich sein kann, aber die Möglichkeiten selbst sind endlos. In beiden Szenarien frage ich die Job Entität ab und gebe den Stream zurück. Schauen Sie sich zunächst den folgenden Code an, in dem ich den Jobs Stream einfach gegen einen bestimmten Customer analysiere, indem ich die Schnittstellenmethode getResultStream() von Query getResultStream() . Dann verwende ich diesen Thread, um Details zu customer und work date Job'a auszugeben.

 public void findByCustomer(PoolCustomer customer){ Stream<Job> jobList = em.createQuery("select object(o) from Job o " + "where o.customer = :customer") .setParameter("customer", customer) .getResultStream(); jobList.map(j -> j.getCustomerId() + " ordered job " + j.getId() + " - Starting " + j.getWorkDate()) .forEach(jm -> System.out.println(jm)); } 


Diese Methode kann leicht geändert werden, sodass mit der Collectors .toList() -Methode eine Liste der Ergebnisse wie folgt zurückgegeben wird.

 public List<Job> findByCustomer(PoolCustomer customer){ Stream<Job> jobList = em.createQuery( "select object(o) from Job o " + "where o.customerId = :customer") .setParameter("customer", customer) .getResultStream(); return jobList.collect(Collectors.toList()); } 

In dem folgenden Szenario, das unten gezeigt wird, finde ich eine List Aufgaben, die sich auf Pools eines bestimmten Formulars beziehen. In diesem Fall gebe ich alle Aufgaben zurück, die dem als Zeichenfolge übermittelten Formular entsprechen. Ähnlich wie im ersten Beispiel gebe ich zuerst einen Stream von Jobs Datensätzen zurück. Dann filtere ich Datensätze basierend auf dem Kundenpoolformular. Wie Sie sehen können, ist der resultierende Code sehr kompakt und leicht zu lesen.

 public List<Job> findByCustPoolShape(String poolShape){ Stream<Job> jobstream = em.createQuery( "select object(o) from Job o") .getResultStream(); return jobstream.filter( c -> poolShape.equals(c.getCustomerId().getPoolId().getShape())) .collect(Collectors.toList()); } 

Wie bereits erwähnt, ist es wichtig, die Leistung in Szenarien zu berücksichtigen, in denen große Datenmengen zurückgegeben werden. Es gibt Bedingungen, unter denen Threads beim Abfragen von Datenbanken nützlicher sind, aber es gibt auch Bedingungen, unter denen sie zu Leistungseinbußen führen können. Eine gute Faustregel lautet: Wenn Daten im Rahmen einer SQL-Abfrage abgefragt werden können, ist es sinnvoll, genau das zu tun. Manchmal überwiegen die Vorteile der Verwendung einer eleganten Thread-Syntax nicht die beste Leistung, die mit der Standard-SQL-Filterung erzielt werden kann.

Unterstützung für doppelte Anmerkungen

Mit der Veröffentlichung von Java SE 8 wurden doppelte Anmerkungen möglich, sodass Sie Anmerkungen in der Deklaration wiederverwenden können. In einigen Situationen muss dieselbe Anmerkung für eine Klasse oder ein Feld mehrmals verwendet werden. Beispielsweise kann es für eine bestimmte Entitätsklasse mehr als eine @SqlResultSetMapping Annotation geben. In Situationen, in denen die Unterstützung für eine erneute Annotation erforderlich ist, sollte eine Container-Annotation verwendet werden. Doppelte Anmerkungen reduzieren nicht nur die Notwendigkeit, Sammlungen identischer Anmerkungen in Containeranmerkungen zu verpacken, sondern können auch das Lesen von Code erleichtern.

Dies funktioniert wie folgt: Die Implementierung der Annotation-Klasse sollte mit der Meta-Annotation @Repeatable gekennzeichnet werden, um anzuzeigen, dass sie mehrmals verwendet werden kann. Die @Repeatable nimmt den Typ der Containeranmerkungsklasse an. Beispielsweise ist die Annotationsklasse NamedQuery jetzt mit der Annotation @Repeatable(NamedQueries.class) . In diesem Fall wird die Container-Annotation noch verwendet, aber Sie müssen nicht darüber nachdenken, wenn Sie dieselbe Annotation für die Deklaration oder Klasse verwenden, da @Repeatable dieses Detail abstrahiert.

Wir geben ein Beispiel. Wenn Sie einer Entitätsklasse in JPA 2.1 mehr als eine @NamedQuery Annotation hinzufügen möchten, müssen Sie diese in die @NamedQueries Annotation @NamedQueries , wie in Listing 4 gezeigt.

Listing 4:

 @Entity @Table(name = "CUSTOMER") @XmlRootElement @NamedQueries({ @NamedQuery(name = "Customer.findAll", query = "SELECT c FROM Customer c") , @NamedQuery(name = "Customer.findByCustomerId", query = "SELECT c FROM Customer c " + "WHERE c.customerId = :customerId") , @NamedQuery(name = "Customer.findByName", query = "SELECT c FROM Customer c " + "WHERE c.name = :name") . . .)}) public class Customer implements Serializable { . . . } 

In JPA 2.2 ist jedoch alles anders. Da @NamedQuery eine doppelte Annotation ist, kann sie in der Entitätsklasse mehrmals angegeben werden, wie in Listing 5 gezeigt.

Listing 5:

 @Entity @Table(name = "CUSTOMER") @XmlRootElement @NamedQuery(name = "Customer.findAll", query = "SELECT c FROM Customer c") @NamedQuery(name = "Customer.findByCustomerId", query = "SELECT c FROM Customer c " + "WHERE c.customerId = :customerId") @NamedQuery(name = "Customer.findByName", query = "SELECT c FROM Customer c " + "WHERE c.name = :name") . . . public class Customer implements Serializable { . . . } 

Liste der doppelten Anmerkungen:

  • @AssociationOverride
  • @AttributeOverride
  • @Convert
  • @JoinColumn
  • @MapKeyJoinColumn
  • @NamedEntityGraphy
  • @NamedNativeQuery
  • @NamedQuery
  • @NamedStoredProcedureQuery
  • @PersistenceContext
  • @PersistenceUnit
  • @PrimaryKeyJoinColumn
  • @SecondaryTable
  • @SqlResultSetMapping

Fazit

Die JPA 2.2-Version enthält einige Änderungen, die enthaltenen Verbesserungen sind jedoch erheblich. Schließlich ist der JPA auf Java SE 8 ausgerichtet, sodass Entwickler Funktionen wie die Datums- und Uhrzeit-API verwenden, Abfrageergebnisse streamen und Anmerkungen wiederholen können. Diese Version verbessert auch die CDI-Konsistenz, indem die Möglichkeit hinzugefügt wird, CDI-Ressourcen in Attributkonverter einzubetten. JPA 2.2 ist jetzt verfügbar und Teil von Java EE 8. Ich denke, Sie werden es gerne verwenden.

DAS ENDE

Wie immer warten wir auf Fragen und Kommentare.

Source: https://habr.com/ru/post/de423195/


All Articles