Java Persistence API (JPA)
Willemers Informatik-Ecke
JDBC Jakarta/Java Enterprise Edition

Die Java Persistence API ermöglicht die Abbildung von Datenbanktabellen in Java-Klassen und realisiert damit das Object Relational Mapping (ORM).

Die dafür verwendeten Klassen sind reine Datenklassen, die auch als POJO oder Java-Bean bezeichnet werden. Eine Java-Bean besteht aus Attributen, den passenden Getter- und Setter-Methoden und einem öffentlichen Konstruktor. Unter JPA werden sie als Entity bezeichnet.

JPA setzt auf dem JDBC-Treiber.

Referenzimplementierung

Die Referenzimplementierung für einen Persistence Provider, der JPA umsetzt, ist EclipseLink. EclipseLink ist Bestandteil des Application Servers Glassfish. Neben EclipseLink gibt es auch weitere Persistence Provider: Die JPA gehört nicht zum Standard-Umfang der Java Standard Edition oder Tomcat. In diesen Fällen muss die Bibliothek des Persistence Providers eingebunden werden. Diese JAR wird unter Eclipse bei Anlegen eines JPA-Projekts gleich angefordert und bei Bedarf automatisch heruntergeladen.

Verbindungsaufbau zur Datenbank

Als Basis für die Beispiele dieser Seite wird PostgreSQL verwendet, dürfte aber auch mit den meisten anderen Datenbanken realisierbar sein. Das Szenario ist die Verwaltung einer Ferienwohnung. setzt diese Seite voraus.

Zur Realisierung erstellen wir zunächst eine Standalone-Anwendungen mit JPA. Eclipse erzeugt es nach einem Aufruf von File|New|JPA Project. Es erscheint ein Dialog:

JPA bietet unter Eclipse eine eigene Perspektive. Dazu werden weitere Fenster wie auf der linken Seite ein Data Source Explorer und auf der rechten Seite die JPA-Details angezeigt.

Entity-Klassen aus einer Datenbanktabelle erzeugen

Eclipse kann aus der erstellten Datenbank automatisch Entity-Klassen erstellen.

Dazu klicken Sie das Projekt mit der rechten Maustaste an und wählen: New | JPA Entity from Table.

Als Ergebnis werden im package model die Klassen für die Tabellen erzeugt.
package model;
import java.io.Serializable;

@Entity
@NamedQuery(name="Wohnung.findAll", query="SELECT w FROM Wohnung w")
public class Wohnung implements Serializable {
    private static final long serialVersionUID = 1L;

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

    private String bez;
    private double preis;
    private Integer qm;

    //bi-directional many-to-one association to Buchung
    @OneToMany(mappedBy="wohnung")
    private List buchungs;
    // Es folgen ein leerer Standard-Konstruktor, Getter und Setter

Damit JPA die Klasse als Entity erkennt, wird sie mit der Annotation @Entity gekennzeichnet.

Für die Entity wird eine @NamedQuery definiert. Es handelt sich um eine SELECT-Anweisung, die später unter dem Namen Wohnung.findAll aufgerufen werden kann. Um flexible Anfragen zu ermöglichen, stellt JPA eine Reihe von SELECT-Anweisungen zur Verfügung.

Die Variable id wird mit @Id als Primärschlüssel gekennzeichnet. Ein solches Attribut muss zwingend vorhanden sein.

Da beim Einfügen neuer Elemente von der Datenbank ein solcher Schlüssel erzeugt wird, muss mit der Annotation @GeneratedValue angegeben werden, wie dieser Schlüssel erstellt wird.

Die Attribute, die auf Spalten der Datenbanktabelle können mit @Column markiert werden. Dadurch ist es auch möglich, Spalten mit ungleichen Namen zu referenzieren. Tatsächlich werden alle Attribute automatisch als Referenzen auf Tabellenspalten aufgefasst, sofern sie nicht mit @Transient markiert werden.

Hinter @OneToMany wird eine 1:n-Beziehung zur Tabelle buchung durch eine Liste umgesetzt.

Annotationen

Persistenzkonfiguration: persistence.xml

Die Datei persistence.xml definiert die Datenbankkonfiguration einer JPA-Anwendung. Bei Eclipse befindet sie sich im Projektordner JPA Content. Wird das Projekt allerdings durch einen Dateimanager oder per Terminal geöffnet, befindet sie sich unterhalb des Projektverzeichnis in src/META-INF.

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1"
    xmlns="http://xmlns.jcp.org/xml/ns/persistence"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
            http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
    <persistence-unit name="gastPU" transaction-type="JTA">
    <jta-data-source>jdbc/__meineDB</jta-data-source>
        <class>Gast</class>
    </persistence-unit>
</persistence>
Eclipse bietet neben der XML-Darstellung unter der Lasche Source einige Dialoge, die die Konfiguration vereinfachen.

Die Entities wurden automatisch eingetragen. Nach der vollständigen Bearbeitung sieht die Datei folgendermaßen aus:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1"
    xmlns="http://xmlns.jcp.org/xml/ns/persistence"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
    http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
    <persistence-unit name="JpaStandalone" transaction-type="RESOURCE_LOCAL">
        <class>model.Buchung</class>
        <class>model.Gast</class>
        <class>model.Wohnung</class>
        <class>model.Mietobjekt</class>
        <properties>
            <property name="javax.persistence.jdbc.url"
                         value="jdbc:postgresql://localhost:5432/fewo"/>
            <property name="javax.persistence.jdbc.user" value="fewoadmin"/>
            <property name="javax.persistence.jdbc.password" value="geheim"/>
            <property name="javax.persistence.jdbc.driver" value="org.postgresql.Driver"/>
        </properties>
    </persistence-unit>
</persistence>
Anmerkung: Zur besseren Lesbarkeit wurden einige Zeilen umbrochen.

Aktionen auf Entites: Der EntityManager

Auslesen einer Tabelle

Das folgende Hauptprogramm soll eine Tabelle auslesen. Als einfache Tabelle wird die Wohnung herangezogen. Sie hat keine Referenzen, aber dafür Zahlen und Fließkommawerte.

Für das Hauptprogramm wird das Package model rechts angeklickt und New|Class ausgewählt. Als Name der Klasse wird beispielsweise Main verwendet.

package model;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.persistence.TypedQuery;

public class Main {
    public static void main(String[] args) {
        EntityManagerFactory factory = null;
        factory = Persistence.createEntityManagerFactory("JpaStandalone"); // PU
        EntityManager manager = factory.createEntityManager();
        TypedQuery<Wohnung> query = manager.createQuery
                ("select t from Wohnung t", Wohnung.class);
        List<Wohnung> liste = query.getResultList();
        for (Wohnung wohnung : liste) {
            System.out.println(wohnung.getBez());
        }
    }
}

Der EntityManager

Der EntityManager sorgt dafür, dass die Daten zwischen Datenbanktabelle und den Java-Objekten hin und her gehen.

Wenn Sie die Transaktionsverwaltung dem Application Server überlassen, dann erhalten Sie den EntityManager per Inject:

@PersistenceContext(unitName = "gastPU")
private EntityManager entityManager;

In allen anderen Fällen, wie auch in unserem Beispiel, erzeugen Sie den EntityManager durch eine EntityManagerFactory. Die Factory wiederum erhält den Namen der Persistence Unit, die in der Datei persistence.xml beschrieben wird.

EntityManagerFactory factory = Persistence.createEntityManagerFactory("JpaStandalone");
EntityManager entityManager = factory.createEntityManager();

Suche über den Schlüssel

Wohnung wohnung = (Wohnung)manager.find(Wohnung.class, id);

Änderungen an den Wohnungen

Änderungen an Datenbanken erfolgen, wenn Sie ernst gemeint sind, als Transaktionen. Die Transaktion wird bei JPA durch den EntityManager eingeleitet und abgeschlossen. Sinnvollerweise werden begin und commit mit den Datenbankbefehlen in einen try-Block gesetzt. Wird mit catch eine Exception gefangen, wird rollback ausgelöst.

Im folgenden Beispiel sollen alle Wohnungen auf die Standardgröße von 65 qm gebracht werden. Das wird dem Maurer schwerer fallen als dem Programmierer.

manager.getTransaction().begin();
for (Wohnung wohnung : liste) {
    wohnung.setQm(65);
    manager.persist(wohnung);
}
manager.getTransaction().commit();

Neuer Eintrag in der Datenbank

Ein neuer Eintrag in der Datenbanktabelle entsteht, wenn ein neues Entity-Objekt angelegt wird und über persist eingetragen wird.
public static void insertNew(EntityManager manager) {
    Wohnung w = new Wohnung();
    w.setBez("Parterre");
    w.setQm(22);
    w.setPreis(450.0);
    manager.getTransaction().begin();
    manager.persist(w);
    manager.getTransaction().commit();
}
Im Falle der Wohnung wird die Datenbank eine ID erzeugen. Dazu muss der Eintrag für @GeneratedValue bei der Variablen id zur Datenbank passen.

Löschen der Wohnung

Ist die Wohnung erst einmal gefunden, kann sie mit remove gelöscht werden.
Wohnung wohnung = (Wohnung)manager.find(Wohnung.class, id);
manager.remove(wohnung);

Relationen

Bei einer 1:n-Beziehung steht eine Zeile einer Tabelle im Verhältnis zu mehreren Zeilen einer anderen Tabelle. So kann ein Kunde mehrere Bestellungen erhalten. In der Datenbank wird dies erreicht, indem die Tabelle Bestellung die ID des Kunden enthält.

Verbindung zwischen zwei Entities

Beim Auslesen einer Buchung wird automatisch der Gast und die Wohnung mit geladen, die mit der Buchung verknüpft sind. Falls es keinen Fremdschlüssel auf eine andere Tabelle gibt, liefert getWohnung bzw. getGast null zurück.
EntityManager manager = getManager();
List<Buchung> buchlist = listBuchungen(manager);
for (Buchung b : buchlist) {
    Wohnung w = b.getWohnung();
    System.out.print("" + b.getId() + ": "+ b.getVon());
    if (w!=null) {
        System.out.print(" " + w.getBez());
    }
    System.out.println();
}

Foreign-Key in Entity

Eine Referenz der Entity Buchung auf eine andere Tabelle Gast (Foreign key), erfolgt über eine Collection-List (java.util) und @OneToMany.
@OneToMany(mappedBy="gast")
private List buchungen;
Die referenzierte Klasse Gast muß nun ein Attribut mit dem Namen aufweisen, dass in OneToMany. Es muss vom Typ der referenzierenden Entity sein und wird mit @ManyToOne eingeleitet. @JoinColumn ist optional.
@ManyToOne
@JoinColumn(name = "gastid")
private Gast gast;

JPA unter Jakarta/JEE

Ein vollwertiger Application Server enthält bereits einen JPA Persistence Provider. Die Datenbank kann dann durch den Administrator des Application Servers konfiguriert werden. Dieses Verfahren nennt sich JTA, da in diesem Fall der Application Server auch die Verwaltung der Transaktionen übernimmt.

Eine JEE-Anwendung kann aber genau wie eine Java-SE-Anwendung die Konfiguration der JPA selbst übernehmen. Dies wird als Resource Local im Eclipse konfiguriert.

Erstellung eines Dynamischen Webprojekt mit JPA unter Eclipse

Nach der Einrichtung eines Dynamischen Webprojekts erscheint ein Dialog:

Konvertieren eines Projekts zu JPA

Auch ein Projekt, das bisher JPA noch nicht verwendet hat, kann darauf umgestellt werden:

Links

Christian Dürr: Grundlagen der JPA

JEE-Literatur

Marcus Schießer, Martin Schmollinger: Workshop Java EE 7

Etwas abstrakter: Dirk Weil: Java EE 7: Enterprise-Anwendungsentwicklung leicht gemacht

Das Buch enthält über 100 Seiten über JEE: Arnold Willemer: Java Alles-in-einem-Band für Dummies