Spring mit JPA und Hibernate (Tutorial)

Dieser Beitrag ist Teil einer (Tutorial-) Serie über die Einführung in das Spring Framework und beschreibt den Einsatz von JPA (Datenbankzugriff) und Hibernate (OR-Mapper) mit dem Spring Framework.

Die Struktur des Projektes

Dieses Beispiel baut auf dem vorherigem Beispiel Spring an einem einfachem Beispiel in der Version 2.0.x auf. Die verwendeten Frameworks und Werkzeuge sind hier beschrieben. Dieses Beispiel ist eine Konsolenanwendung, die eine Fahrerverwaltung umsetzt. In diesem Beispiel werden die folgenden Technologien des Spring Frameworks vorgestellt:

  • Die Definition und die Verwendung von abstrakten und generischen Datenzugriffsobjekten (DAO).
  • Die Spezialisierung von Datenzugriffsobjekten für JPA.
  • Die Definition von Entitäten mit Relationen, Named Queries und Sequenzen.
  • Die Berücksichtigen von Besonderheiten bei Entitäten.
  • Das Erstellen einer XML Konfiguration für JPA.
  • Das Deklarieren eines Datenzugriffsobjekt im Spring Framework.
  • Ein Service im Spring Framework mit Transaktionen versehen.
  • Die Datenbank-spezifische Konfiguration für das Spring Framework erzeugen.
  • Einen JUnit-Test für Datenbankoperationen mit dem Spring Framework erstellen.

Die folgende Bibliotheken werden benötigt:

Die benötigten Bibliotheken (Dependencies)

Die benötigten Bibliotheken - Dependencies (© Frank Rahn)

Die Literaturempfehlungen für dieses Beispiel

Das abstrakte und generische Datenzugriffsobjekt

Ein DAO (Data Access Object) dient zur Abstrahierung von der konkreten Persistenz-Technologie und entkoppelt die Geschäftslogik vom Datenbankzugriff. Die folgende Schnittstelle eines Datenzugriffsobjektes (DAO) ist noch unabhängig von einer konkreten Zugriffstechnologie.

Diese Schnittstelle definiert die elementarsten Methoden (CRUD) für den Datenzugriff. Als generische Parametern wird die Klasse der Entität und des primären Schlüssels (Primary Key) definiert.

Nun folgt eine abstrakte Implementierung dieser Schnittstelle. In dieser Implementierung wurde …

  • in Zeile 21 der Logger definiert und
  • in Zeile 30 bis 41 im Object-Initializer die Ermittlung der konkreten Klassen der Entität und des primären Schlüssels durchgeführt.

Die Spezialisierung des Data Access Objects für JPA

Die grundlegende Schnittstelle eines Datenzugriffsobjektes diesmal abhängig von der Zugriffstechnologie Java Persistence API (JPA).

In der folgenden abstrakten Implementierung dieser Schnittstelle wird die JPA Annotationen @PersistenceContext verwendet. Der EntityManager wird dadurch durch das Spring Framework injiziert.

Im folgendem Klassendiagramm ist der Sachverhalt der abstrakten generischen JPA Datenzugriffsobjekte grafisch per UML dargestellt.

Abstrakte generische Datenzugriffsobjekte (DAO) für JPA

Abstrakte generische Datenzugriffsobjekte für JPA (© Frank Rahn)

Die Definition des Service der Fahrerverwaltung

Zunächst wird die Schnittstelle dieses Services definiert. Auch dieses Beispiel ist relativ einfach gehalten.

Die XML Konfiguration für dieses Modul.

Die Entitäten des Services Fahrerverwaltung

Unsere Fahrerverwaltung besteht aus Fahrern mit ihren Autos. Hier werden nun eine Reihe von Annotationen von JPA verwendet, um das Datenmodell zu definieren.

  1. In den Zeilen 27 bis 29 wird diese Klasse als Entität mit Zugriff auf Attribut-Ebene definiert. Zusätzlich wird das Datenbankschema angegeben. Der Tabellenname rahn.Driver wird aus dem Klassennamen generiert.
  2. In der Zeile 30 wird eine statische und benannte Abfrage mit JPQL definiert. Mit dieser Abfrage können alle Fahrer selektiert werden. Damit wird sogenanntes SQL-Injection ausgeschlossen, da für diese Abfragen ausschließlich PreparedStatement verwendet werden.
  3. In den Zeilen 37 bis 41 wird der primäre Schlüssel für diese Klasse definiert. Dabei wird dieser Identifizierer über eine DB-Sequenz automatisch vergeben.
  4. In der Zeile 54 wird eine gerichtete Relation (1-zu-n) mit weiterreichen der CRUD-Operationen (Cascade) zum Auto definiert.
  5. In der Zeile 55 wird die Spalte driver-id in der Tabelle des Autos definiert. Dieses ist notwendig, da die Klasse Auto den Fahrer nicht kennt – also kein Attribut des Fahrers besitzt. Wenn in der Klasse Auto das Attribut public Driver driver; definiert worden wäre, hatte folgende Anweisung für die Relation ausgereicht:
    @OneToMany(cascade = CascadeType.ALL, mappedBy = "driver") .

Im folgendem Abschnitt werden die von Eclipse IDE generierten Methoden für hashCode() und equals() dargestellt. Diese Methoden müssen überladen werden, da eine Entität eine eindeutige Identität besitzt, die über die beiden Methoden bestimmt werden muss.

Zwei eigentlich gleiche Entitäten werden als ungleich bewertet, wenn …

  • die Entitäten von unterschiedlichen EntityManager gelesen wurden oder
  • die eine Entität per Eager Loading (direkt beim Ausführen des Selects) und die andere Entität per Lazy Loading (durch einen Proxy beim Zugriff auf das Feld) geladen wurden.

Der automatische erzeugte Schlüssel ( id) wird erst beim Speichern der Entitäten gesetzt. Die beiden Methoden hashCode() und equals() müssen von denselben signifikanten Attributen abhängen.

  • In der Zeile 67 und 99 wird mit Persistence.getPersistenceUtil().isLoaded(cars) geprüft, ob die Autos schon geladen wurden. Wenn nicht würden die Entitäten des Autos geladen oder einen Fehler auslösen, wenn der EntityManager schon geschlossen wurde.

Als nächstes folgt die Klasse für die Autos. Das Auto kennt seinen Fahrer nicht, daher gibt es auch kein Attribut für den Fahrer in der Klasse.

  • Der primäre Schlüssel in Zeile 22 wird von Außen vorgegeben (Kennzeichen).

Es fehlt noch die XML Konfiguration für die Persistenz, die nicht sehr umfangreich ist.

  • In der Zeile 11 wird angegeben, dass die Anwendung die Transaktionen der Datenbank verwenden soll:
    EntityTransaction etx = entityManager.getTransaction();
    Im Gegensatz zu der Angabe JTA, wo der Transaktionsmanager des Java EE Containers verwendet würde:
    UserTransaction utx = (UserTransaction) (new InitialContext()).lookup("java:comp/UserTransaction");

Das Data Access Object für die Fahrerverwaltung

Die Definition des konkreten Datenzugriffsobjekt für den Service der Fahrerverwaltung.

  • In Zeile 16 wird die Annotation @Repository verwendet. Durch diese Annotation wird diese Klasse zu einem Spring Bean mit einer Behandlung für Ausnahmen der Persistenzschicht. Diese Ausnahmen werden in eine Unterklasse von org.springframework.dao.DataAccessException übersetzt.
  • Ab der Zeile 32 wird eine Methode findAll() implementiert, die per @NamedQuery definierten Abfrage alle Instanzen dieser Klasse lädt.

Die Implementierung des Services der Fahrerverwaltung

Die Standard-Implementierung der Fahrerverwaltung.

  • Durch die Annotation @Service("drivers") in Zeile 19 wird diese Implementierung als Spring Bean mit dem Namen drivers definiert.
  • In der Zeile 24 wird das Datenzugriffsobjekt DriverDAO verwendet.
  • In der Zeile 20 wird definiert, dass alle Methoden in einer Transaktion ausgeführt werden.
  • In Zeile 31 und 41 wird durch die Annotation angegeben, dass diese Methoden eine existierende Transaktion unterstützen, aber keine eigene Transaktion benötigen.

Im folgenden Bild wird der vollständige Service in einem UML-Klassendiagramm dargestellt.

Klassendiagramm dieses Beispiels

Spring mit JPA und Hibernate (© Frank Rahn)

Die Anwendung mit Logging

Sie ist wie in dem Beispiel Spring an einem einfachem Beispiel aufgebaut. Es ändert sich nur die Anwendung, die XML Konfiguration de/rahn/app/application.xml und der Starter bleiben gleich. Die Änderung an der Application sind hier dargestellt.

Die XML Konfiguration zum Einstieg in die Anwendung muß um die Datenbank- und die Transaktionsdefinitionen erweitert werden.

  • In der Zeile 27 wird auf die Konfiguration mit den Datenbank- und die Transaktionsdefinitionen im gleichen Verzeichnis verwiesen.

In der folgenden XML Konfiguration werden die Datenbank- und die Transaktionsdefinitionen vorgenommen. Hier wird erstmalig der p-Namesraum für das Setzen von Properties verwendet.

Die Syntax lautet dabei:
p:<Property-Name>="<Property-Wert>" oder p:<Property-Name>-ref="<Name der referenzierten Bean>"

  • Die Definitionen für den Datenbankzugriff ( EntityManagerFactory) in den Zeilen 27 bis 34 und die Behandlung der Transaktionen in den Zeilen 37 bis 42.
  • In den Zeilen 22 bis 24 wird eine Datenbank gestartet und durch das angegebene Skript init.sql initialisiert.

Im folgendem Kasten wird das Skript init.sql dargestellt. Der SQL Dialekt ist HSQL (HyperSQL Database). HSQLDB ist eine vollständig in Java geschrieben relationale Datenbank.

Der Unit Test

Zunächst erstellen wir einen Test für die Klasse DriverDAO.

  • In der Zeile 25 wird auf die benötigten XML Konfigurationen von Spring verwiesen. Das ist eine spezielle XML Konfiguration für den Test /META-INF/spring/context-test.xml, sie beinhaltet die Konfigurationen für den Datenbankzugriff, und die Konfiguration für die Fahrerverwaltung ../drivers.xml.

Für die allgemeinen Spring Beans wurde eine spezielle XML Konfiguration /META-INF/spring/context-test.xml erstellt. Sie lädt nur die Konfigurationen für den Datenbankzugriff.

Jetzt fehlt nur noch der Unit Test für den Service der Fahrerverwaltung StandardDriver.

Hier noch die entsprechende Erfolgsmeldung.

Die Erfolgsmeldung von JUnit

Die Erfolgsmeldung von JUnit (© Frank Rahn)

Der Quellcode und Download des Beispiels

Quellcode ansehen bei GitHub:
Spring mit JPA und Hibernate

Download einer ZIP-Datei von GitHub:
Spring mit JPA und Hibernate

Die Maven Befehl

Eclipse Konfiguration neu erzeugen: $ mvn eclipse:clean eclipse:eclipse

Anwendung bauen: $ mvn clean install

Anwendung ausführen: $ mvn initialize exec:exec

Update: Criteria API

Es wurde in der pom.xml ein Maven-Plugin für die Generierung des Metamodels der Criteria API aufgenommen. Dieses Metamodel wird in das Verzeichnis target/generated-sources/apt generiert. Mit der Criteria API können Datenbankabfragen objektorientiert im Quellcode programiert werden.

Nachfolgenden ist die Maven Konfiguration für die Erzeugung der Criteria API mit dem JPA 2 Metamodel Generator von Hibernate dargestellt.

Zusätzlich wurde ein Template, nach dem Muster von Spring, erzeugt. Dadurch kann das abstrakte und generische JPA-Datenzugriffsobjekt die Erzeugung der Factory-Klassen übernehmen.

Das abstrakte und generische JPA-Datenzugriffsobjekt AbstractGenericJpaDAO, wird um die Methode buildQuery() erweitert.

Das Datenzugriffsobjekt der Fahrer stellt die Methode findByName() bereit.

Zum Abschluss darf natürlich der Test nicht fehlen.

Update am 28.09.2012

Es wurden folgende Änderungen an allen Projekten vorgenommen.

  1. Die Projekte wurden aus meinem lokalen Apache Subversion Repository in meine öffentlichen GitHub Repositories verschoben.
  2. Die folgenden typischen Anpassungen an Git wurden an allen Projekten durchgeführt.
    • .directory gelöscht
    • .gitignore hinzugefügt
    • README.md hinzugefügt
    • COPYRIGHT.md hinzugefügt
    • In jedem Repository wurde für jeden Beitrag der Serie ein Branch angelegt.
      • develop-spring-an-einem-einfachen-beispiel
      • develop-spring-mit-aop
      • develop-spring-mit-jpa-und-hibernate
      • develop-spring-mit-einer-einfachen-webanwendung
      • develop-spring-mit-einer-webanwendung-mit-jpa-und-validierung
      • develop-spring-security-mit-einer-webanwendung
      • develop-spring-mit-restful-webservice

Updates von 06.10.2012 bis zum 14.10.2012

Es wurden folgende Änderungen an allen Projekten vorgenommen.

  1. Aktualisierung des OpenJDK auf die Version 1.7.0.
  2. Aktualisierung der Entwicklungsumgebung Eclipse auf die Version 4.2.1.
    Die benötigten Plugins aus dem Eclipse Marketplace:

  3. Aktualisierung der Datei pom.xml:
    • Anpassungen an die OpenJDK Version
    • GitHub Einträge (SCM und URL) hinzugefügt
    • Aktualisierung der Libraries auf aktuellerer Versionen (z. B. Spring Version 3.1.2.RELEASE, Hibernate Version 4.1.7.Final, JUnit Version 4.10, mockito Version 1.9.0, …)
      Die genaueren Versionen bitte aus den jeweiligen pom.xml auf GitHub entnehmen.

Frank Rahn

Frank Rahn ist Softwarearchitekt. Er unterstützt bei der Konzeption von Softwarearchitekturen mit Java-Technologie. Folge Sie ihm auf Facebook, Twitter oder Google+.

Benötigen Sie Unterstützung? Kontaktieren Sie ihn.

Hat Ihnen dieser Beitrag gefallen? Wir würden uns über Ihren Kommentar freuen! Bitte verwenden Sie Ihren bürgerlichen Namen und eine E-Mail-Adresse mit Gravatar.

Letzte Artikel von Frank Rahn (Alle anzeigen)

0 Kommentare

Dein Kommentar

Want to join the discussion?
Feel free to contribute!

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.