Fünf Dinge, die man als Anfänger über JPA wissen sollte

Warum ist die Java Persistence API eigentlich so beliebt, und was sollte man als Anfänger darüber wissen?

In Pocket speichern vorlesen Druckansicht
Lesezeit: 9 Min.
Von
  • Thorben Janssen
Inhaltsverzeichnis

Gute Kenntnisse über die Java Persistence API (JPA) und ihre Implementierungen sind nach wie vor eine der gefragtesten Fähigkeiten unter Java-Entwicklern. Es ist daher nicht verwunderlich, dass es viele Blogartikel, Bücher und Kurse gibt, die zeigen, wie eine Persistenzschicht mit der JPA-Spezifikation implementiert werden kann.

Aber Neulinge fragen sich wahrscheinlich trotz aller Beliebtheit, warum JPA so oft verwendet wird und was man darüber wissen muss. Daher werfe ich einen Blick auf die fünf wichtigsten Dinge, die man als Entwickler über JPA wissen sollte.

Beginnen wir mit dem Offensichtlichsten: Die JPA-Spezifikation definiert eine objektrelationale Abbildung zwischen Tabellen in einer relationalen Datenbank und Java-Klassen. Das Tolle daran ist, dass JPA diese Zuordnung sehr einfach macht. Sehr oft muss eine Klasse lediglich mit einer @Entity-Annotation versehen werden. Alle ihre Attribute werden dann automatisch auf gleichnamige Datenbankspalten abgebildet.

Hier ist ein Beispiel für ein solches Basis-Mapping:

@Entity
public class Professor {

@Id
private Long id;

private String firstName;

private String lastName;

public String getFirstName() {
return firstName;
}

public void setFirstName(String firstName) {
this.firstName = firstName;
}

public String getLastName() {
return lastName;
}

public void setLastName(String lastName) {
this.lastName = lastName;
}

public void setId(Long id) {
this.id = id;
}

public Long getId() {
return id;
}
}

Die Professor-Klasse wird durch JPA auf eine Datenbanktabelle mit dem Namen professor und den Spalten id, firstname und lastname abgebildet. Ich erläutere die Abbildung dieser Klasse im Detail in der auf YouTube verfügbaren Beispiellektion meines Kurses "JPA for Beginners":

Empfohlener redaktioneller Inhalt

Mit Ihrer Zustimmmung wird hier ein externes YouTube-Video (Google Ireland Limited) geladen.

Ich bin damit einverstanden, dass mir externe Inhalte angezeigt werden. Damit können personenbezogene Daten an Drittplattformen (Google Ireland Limited) übermittelt werden. Mehr dazu in unserer Datenschutzerklärung.

Und man kann nicht nur einfache Attribute auf Datenbankspalten abbilden, sondern auch Assoziationen zwischen den Entitäten modellieren. Auf diese Weise können die Fremdschlüsselspalten und Beziehungstabellen des Tabellenmodells als Entitätsattribute mit Getter- und Setter-Methoden dargestellt werden. Diese Attribute können dann wie jedes andere Entitätsattribut verwendet werden. Die verwendete JPA-Implementierung stellt dabei sicher, dass die erforderlichen Datensätze entweder während der Initialisierung der Entität oder bei der erstmaligen Verwendung geladen werden.

Das folgende Beispiel zeigt die Abbildung einer typischen Viele-zu-Eins-Beziehung zwischen der professor- und der course-Tabelle. Das Attribut der Professor-Klasse modelliert die Beziehung, und die JPA-Implementierung sorgt dafür, dass die erforderlichen Lese- und Schreiboperationen durchgeführt werden:

@Entity
public class Course {

@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "course_generator")
@SequenceGenerator(name = "course_generator", sequenceName = "course_seq")
private Long id;

private String name;

private LocalDate startDate;

private LocalDate endDate;

@ManyToOne
private Professor professor;

...
}

JPA ist nur eine Spezifikation, die eine Reihe von Schnittstellen und deren Funktionen definiert. Das bedeutet, dass man die Spezifikation zur Implementierung Ihrer Anwendung verwenden kann, aber das zum Ausführen eine Implementierung hinzugefügt werden muss. Zwei bekannte JPA-Implementierungen sind EclipseLink, die Referenzimplementierung der Spezifikation, und Hibernate, die wahrscheinlich beliebteste JPA-Implementierung.

Ich habe zu Beginn dieses Artikels erklärt, dass JPA Java-Klassen auf Datenbanktabellen abbildet und dies sogar Beziehungen zwischen diesen Klassen beinhaltet. Diese Abbildung führt offensichtlich zu einer Abstraktion. Der Abstraktionsgrad zwischen einem einfachen Attribut und einer Datenbankspalte mag eher gering sein, ist aber bei Beziehungen deutlich höher. Die JPA-Implementierung muss dann nicht nur die Typkonvertierung zwischen dem JDBC-Typ der Datenbankspalte und dem Java-Typ des Entitätsattributs durchführen, sondern auch zusätzliche Abfragen verwalten, um die zugehörigen Datensätze abzurufen.

Aus diesem Grund ist es äußerst wichtig, dass jeder Entwickler das Mapping und seine Auswirkungen genau versteht. Andernfalls wird die JPA-Implementierung diese Beziehungen ineffizient behandeln, und die Anwendung wird unter schwerwiegenden Performanzproblemen leiden. Deshalb beschäftige ich mich in vier Lektionen meines "JPA for Beginners Online Training" mit den Abbildungen verschiedener Beziehungstypen und deren, als Fetching bezeichneten Ladeverhalten.

Neben der Abstraktion durch die Attributszuordnungen löst JPA auch die erforderlichen Einfüge-, Aktualisierungs- und Entfernungsoperationen auf Basis eines komplexen Lebenszyklusmodells aus. Das Gute daran ist, dass für diese Operationen keine SQL-Anweisungen geschrieben werden müssen. Aber gleichzeitig verliert man dadurch die Kontrolle über die SQL-Anweisung und den Zeitpunkt ihrer Ausführung.

Die automatische Erstellung und Ausführung von SQL-Anweisungen macht es sehr einfach, die Geschäftslogik zu implementieren, und verbessert die Entwicklerproduktivität. Es macht es aber auch schwierig vorherzusagen, wann welche SQL-Anweisungen ausgeführt wird. Daher ist ein fundiertes Verständnis des Lebenszyklusmodells und dessen Auswirkungen auf die Ausführung von SQL-Anweisungen erforderlich.

JPA abstrahiert den Datenbankzugriff und verbirgt ihn hinter einer Reihe von Annotationen und Schnittstellen. Das bedeutet aber nicht, dass man die Datenbank ignorieren könnte. Auch wenn man nicht direkt mit auf einem Tabellenmodell arbeitet, müssen dennoch die Möglichkeiten und Grenzen von relationalen Tabellenmodellen berücksichtigt werden. Tut man dies nicht, führt dies früher oder später zu Performanzproblemen.

Des Weiteren sollte man darauf achten, dass die Entitäten so ähnlich wie möglich zu den abgebildeten Datenbanktabellen sind. Dadurch wird sichergestellt, dass die JPA-Implementierung ein schnelles und effizientes objektrelationales Mapping zur Verfügung stellen kann.

Es muss außerdem berücksichtigt werden, dass die Datenbank weiterhin SQL-Anweisungen ausführt. Durch die Verwendung von JPA müssen viele dieser Anweisungen nicht mehr selbst geschrieben werden, aber man sollte trotzdem in der Lage sein, dies zu lesen und zu verstehen. Dies ist notwendig, um die Interaktion der JPA-Implementierung mit der Datenbank zu verstehen und eine effiziente Datenbankanbindung zu realisieren.

Um die ausgeführten SQL-Anweisungen überprüfen zu können, müssen diese von der JPA-Implementierung zuerst protokolliert werden. Die erforderliche Konfiguration ist abhängig von der jeweiligen JPA-Implementierung. Im folgenden Beispiel zeige ich eine Konfiguration für Hibernate:

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{HH:mm:ss,SSS} %-5p [%c] - %m%n

log4j.rootLogger=info, stdout
# basic log level for all messages
log4j.logger.org.hibernate=info

# SQL statements and parameters
log4j.logger.org.hibernate.SQL=debug
log4j.logger.org.hibernate.type.descriptor.sql=trace

Mit dieser Konfiguration schreibt Hibernate alle ausgeführten SQL INSERT-, UPDATE- und DELETE-Anweisungen in die Logdatei. So kann man genau sehen, wann und welche Anweisungen Hibernate ausgeführt hat.

19:13:35,772 DEBUG [org.hibernate.SQL] - 
select
professor0_.id as id1_1_0_,
professor0_.firstName as firstNam2_1_0_,
professor0_.lastName as lastName3_1_0_
from
Professor professor0_
where
professor0_.id=?
19:13:35,773 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] - binding parameter [1] as [BIGINT] -[1]
19:13:35,774 TRACE [org.hibernate.type.descriptor.sql.BasicExtractor] - extracted value ([firstNam2_1_0_] : [VARCHAR]) - [Jane]
19:13:35,774 TRACE [org.hibernate.type.descriptor.sql.BasicExtractor] - extracted value ([lastName3_1_0_] : [VARCHAR]) - [Doe]
19:13:35,775 DEBUG [org.hibernate.SQL] -
update
Course
set
endDate=?,
name=?,
professor_id=?,
startDate=?
where
id=?
19:13:35,776 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] - binding parameter [1] as [DATE] - [2019-05-31]
19:13:35,776 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] - binding parameter [2] as [VARCHAR] - [Software Development 1]
19:13:35,776 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] - binding parameter [3] as [BIGINT] - [1]
19:13:35,776 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] - binding parameter [4] as [DATE] - [2018-08-15]
19:13:35,777 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] - binding parameter [5] as [BIGINT] - [1]

Auf den ersten Blick erscheint es oft so, als würde man mit JPA nur ein paar wenige Annotationen benötigen um eine vollständige Persistenzschicht zu implementieren. Während der praktischen Umsetzung stellt sich dann aber schnell heraus, dass die Verwendung der Spezifikation deutlich komplexer ist als erwartet und ein gutes Verständnis der grundlegenden Konzept erfordert.

Mit meinem Onlinekurs "JPA for Beginners" biete ich eine einfache und schnelle Möglichkeit diese Kenntnisse zu erlernen. Mit kurzen Videolektionen und dazu passenden Aufgaben erlernen die Teilnehmer innerhalb kurzer Zeit, eine Persistenzschicht auf Basis der JPA-Spezifikation zu erstellen und dabei häufig auftretende Fehler zu vermeiden.

Zur Einführung der zweiten,. überarbeiteten Version des Kurses gibt es diesen noch bis zum 15 März zum reduzierten Preis auf https://thoughts-on-java.org/jpa-for-beginners/. ()