Was ist neu in JPA 2.2

Ursprünglicher Autor: Josh Juneau
  • Übersetzung
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 hat JPA 2.2 gebracht - 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, können Sie mit JPA Daten speichern. JPA 2.1 verbesserte die Spezifikation, sodass Entwickler Aufgaben wie die automatische Generierung von Datenbankschemata und das effiziente Arbeiten 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 nennen, die Ihnen den Einstieg in die Arbeit 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 2.2 ist Teil der Java EE 8-Plattform. Beachten Sie, dass nur Java EE 8-kompatible Anwendungsserver eine sofort einsatzbereite Spezifikation bereitstellen. Zum Zeitpunkt des 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 zum Projekt hinzufügen. Wenn Sie Maven in Ihrem Projekt verwenden, fügen Sie der Maven-POM-Datei die Koordinaten hinzu:

javax.persistencejavax.persistence-api2.2

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

org.eclipse.persistenceeclipselink2.7.0 

Wenn Sie einen Java EE 8-kompatiblen Server wie GlassFish 5 oder Payara 5 verwenden, sollten Sie in der Lage sein, den „bereitgestellten“ Bereich für diese Abhängigkeiten in der POM-Datei anzugeben. Andernfalls geben Sie den Bereich "Kompilieren" an, um sie in die Projektassembly aufzunehmen. Java 8-

Unterstützung für

Datum und Uhrzeit Eine der wahrscheinlich positivsten Neuerungen ist 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, besteht seit langem die Notwendigkeit, grundlegende Unterstützung für die aktualisierte Datums- und Uhrzeit-API hinzuzufügen. 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 erkläre ich zunächst, wie die Unterstützung für die Datums- und Uhrzeit-API ohne JPA 2.2 funktioniert. JPA 2.1 kann nur mit älteren Datumskonstrukten wie java.util.Dateund arbeiten 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 in eine aktualisierte Datums- und Uhrzeit-API zur Verwendung in der Anwendung konvertieren. Ein Datumskonverter in JPA 2.1, der zu einer solchen Konvertierung in der Lage ist, sieht möglicherweise so aus wie Listing 1. Der Konverter darin wird zum Konvertieren zwischen LocalDate und verwendet java.util.Date.

Listing 1

@Converter(autoApply = true)
public class LocalDateTimeConverter implements AttributeConverter {
    @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 keinen solchen Konverter mehr schreiben, da Sie die unterstützten Datums- / Uhrzeit-Typen verwenden. Die Unterstützung für solche Typen ist integriert, sodass Sie den unterstützten Typ einfach ohne zusätzlichen Code im Feld Entitätsklasse angeben können. Das folgende Code-Snippet demonstriert dieses Konzept. Beachten Sie, dass dem Code keine @TemporalAnmerkungen hinzugefügt werden müssen , da die Typenzuordnung automatisch erfolgt.

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

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

Es ist anzumerken, dass in dieser Version nur ein Teil der Datenzeittypen unterstützt wird. Der Attributkonverter kann jedoch problemlos generiert werden, um mit anderen Typen zusammenzuarbeiten, z. B. zum Konvertieren LocalDateTime in ZonedDateTime. Das größte Problem beim Schreiben eines solchen Konverters besteht darin, festzustellen, wie zwischen verschiedenen Typen am besten konvertiert werden kann. Um die Arbeit noch einfacher zu machen, können jetzt Attributkonverter implementiert werden. Ich werde unten ein Implementierungsbeispiel geben.

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

Listing 2

@Converter
public class LocalToZonedConverter implements AttributeConverter {
    @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 es ZonedDateTime Methoden enthält, die einfach zu konvertieren sind. Die Konvertierung erfolgt durch Aufrufen einer toLocalDateTime()Methode. Die inverse Transformation kann über einen Methodenaufruf durchgeführt werden , ZonedDateTimeOf()und die Durchlässigkeitswerte LocalDateTime mit ZoneId für Zeitzone.

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 Funktion, um Attributkonverter implementierbar zu machen. Dies bedeutet, dass Sie Contexts and Dependency Injection (CDI) -Ressourcen direkt in den Attributkonverter einbetten können. Diese Änderung steht im Einklang mit anderen CDI-Verbesserungen in den Java EE 8-Spezifikationen, z. B. mit erweiterten JSF-Konvertern, da sie jetzt auch CDI-Injection verwenden können.

Um diese neue Funktion zu nutzen, müssen Sie die CDI-Ressourcen nach Bedarf in den Attributkonverter einbetten. Listing 2 enthält ein Beispiel für einen Attributkonverter. Jetzt werde ich ihn zerlegen und alle wichtigen Details erläutern.

Die Konverterklasse muss die Schnittstelle implementierenjavax.persistence.AttributeConverterÜbergeben der Werte von X und Y. Der Wert von X entspricht dem Datentyp im Java-Objekt, und der Wert von Y muss mit dem Typ der Datenbankspalte übereinstimmen. Dann sollte die Konverterklasse mit Anmerkungen versehen werden @Converter. Schließlich muss die Klasse die Methoden convertToDatabaseColumn()und überschreiben convertToEntityAttribute(). Die Implementierung in jeder dieser Methoden sollte Werte von bestimmten Typen und zurück in diese konvertieren.

Um den Konverter bei jeder Verwendung des angegebenen Datentyps automatisch anzuwenden, fügen Sie "automatisch" wie in hinzu @Converter(autoApply=true). Um einen Konverter auf ein einzelnes Attribut anzuwenden, verwenden Sie die @ Konverter-Annotation 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 Werte in einem creditLimitEntitätsfeld verschlüsseln, Customer wenn es gespeichert wird. Um einen solchen 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 gibt ein Beispiel.

Listing 3

@Converter
public class CreditLimitConverter implements AttributeConverter {
    @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, indem die Klasse CreditLimitEncryptor in den Konverter eingefügt und dann zur Unterstützung des Prozesses verwendet wird.

Streaming von Abfrageergebnissen

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

Um diese Funktion zu aktivieren getResultStream(), wurde den Schnittstellen Query und eine Methode hinzugefügtTypedQuery. Durch diese geringfügige Änderung kann JPA einfach einen Ergebnisstrom anstelle einer Liste zurückgeben. Wenn Sie also mit einem großen ResultSetThread arbeiten, ist es sinnvoll, die Leistung zwischen einer neuen Thread-Implementierung und einer scrollbaren ResultSets oder Paginierungs- Implementierung zu vergleichen . Der Grund dafür ist, dass Thread-Implementierungen alle Datensätze auf einmal abrufen, in einer Liste speichern und dann zurückgeben. Bildlauf- ResultSet und Paginierungstechniken extrahieren Daten stückweise, was für große Datenmengen möglicherweise besser ist.

Persistenzanbieter können entscheiden, die neue Methode durch eine getResultStream() verbesserte Implementierung zu überschreiben . Hibernate enthält bereits eine stream () -Methode, die scrollable verwendetResultSet zum Analysieren der Ergebnisse von Datensätzen, anstatt sie vollständig zurückzugeben. Dies ermöglicht es Hibernate, mit sehr großen Datenmengen zu arbeiten und es gut zu machen. Es ist zu erwarten, dass andere Anbieter diese Methode außer Kraft setzen, 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 zum Arbeiten mit Daten bietet. Ich werde einige Szenarien aufzeigen, in denen dies nützlich sein kann, die Möglichkeiten selbst jedoch endlos sind. In beiden Szenarien fordere ich eine Entität an Job und gebe einen Stream zurück. Schauen Sie sich zunächst den folgenden Code an, in dem ich einfach den Ablauf Jobs für einen bestimmten Code analysiere Customerund die Schnittstellenmethode aufrufe Query getResultStream(). Dann habe ich diesen Thread für Details über den Ausgang verwenden customer und work dateJob'a.

public void findByCustomer(PoolCustomer customer){
    Stream 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 eine Liste der Ergebnisse mit der folgenden Methode zurückgegeben wird Collectors .toList().

public List findByCustomer(PoolCustomer customer){
    Stream 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 unten gezeigten Szenario finde ich List Aufgaben, die sich auf Pools einer bestimmten Form 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 Datenstrom zurück Jobs. Dann filtere ich Datensätze basierend auf dem Kundenpoolformular. Wie Sie sehen, ist der resultierende Code sehr kompakt und einfach zu lesen.

public List findByCustPoolShape(String poolShape){
    Stream 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 solche, bei denen sie zu Leistungseinbußen führen können. Als Faustregel gilt: Wenn Daten im Rahmen einer SQL-Abfrage abgefragt werden können, ist es sinnvoll, genau das zu tun. Manchmal überwiegen die Vorteile der eleganten Streamsyntax 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 Annotationen möglich, sodass die Annotation in der Deklaration wiederverwendet werden konnte. In einigen Situationen muss dieselbe Anmerkung für eine Klasse oder ein Feld mehrmals verwendet werden. Beispielsweise kann es mehr als eine @SqlResultSetMappingAnmerkung für eine bestimmte Entitätsklasse geben. In Situationen, in denen Unterstützung für erneute Anmerkungen erforderlich ist, sollten Containeranmerkungen verwendet werden. Doppelte Annotationen reduzieren nicht nur die Notwendigkeit, Sammlungen derselben Annotationen in Container-Annotationen einzubinden, sondern können auch das Lesen von Code vereinfachen.

Dies funktioniert folgendermaßen: Die Implementierung der Annotation-Klasse sollte mit einer Meta-Annotation gekennzeichnet sein, @Repeatableum anzuzeigen, dass sie mehrmals verwendet werden kann. Meta Annotation@RepeatableAkzeptiert einen Container-Annotation-Klassentyp. Beispielsweise wird eine Anmerkungsklasse NamedQuery jetzt mit einer @Repeatable(NamedQueries.class)Anmerkung markiert . In diesem Fall wird die Container-Annotation weiterhin verwendet, aber Sie müssen nicht darüber nachdenken, wenn Sie dieselbe Annotation für die Deklaration oder Klasse verwenden, da @Repeatablediese Details abstrahiert werden.

Wir geben ein Beispiel. Wenn Sie @NamedQueryeiner Entitätsklasse in JPA 2.1 mehr als eine Annotation hinzufügen möchten , müssen Sie diese in der Annotation einkapseln @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 es sich @NamedQueryum eine sich wiederholende Annotation handelt, 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

JPA 2.2 ist eine kleine Änderung, aber die enthaltenen Verbesserungen sind erheblich. Schließlich wird der JPA an 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 CDI-Ressourcen in Attributkonverter eingebettet werden können. Jetzt ist JPA 2.2 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.

Jetzt auch beliebt: