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 Literaturempfehlungen für dieses Beispiel
Das abstrakte und generische Datenzugriffsobjekt
Ein DAO (Data Access Object) dient zum Abstrahieren 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.
/** * Ein generische DAO-Schnittstelle für eine Entität mit einem primären Key. * @author Frank W. Rahn * @param <Entity> Die Klasse der Entität * @param <PrimaryKey> Die Klasse des primären Key */ public interface GenericDAO<Entity, PrimaryKey extends Serializable> { /** * Liefere den primären Key für das angegebene Objekt. * @param persistentObject das persistente Objekt * @return den primären Key */ PrimaryKey getPrimaryKey(Entity persistentObject); /** * Speichere das neue Objekt in der Datenbank. * @param newPersistentObject das neue persistente Objekt * @return den primären Key */ PrimaryKey create(Entity newPersistentObject); /** * Aktualisiere das geänderte Objekt in der Datenbank. * @param persistentObject das persistente Objekt */ void save(Entity persistentObject); /** * Lösche das persistente Objekt aus der Datenbank. * @param persistentObject das persistente Objekt */ void remove(Entity persistentObject); /** * Lösche das persistente Objekt aus der Datenbank. * @param key der primäre Key */ void remove(PrimaryKey key); /** * Finde das persistente Objekt an Hand seines primären Keys. * @param key der primäre Key * @return das persistente Objekt */ Entity findByPrimaryKey(PrimaryKey key); }
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.
/** * Eine abstrakte Implementierung der Schnittstelle {@link GenericDAO}. * @author Frank W. Rahn * @param <Entity> Die Klasse der Entität * @param <PrimaryKey> Die Klasse des primären Key */ public abstract class AbstractGenericDAO<Entity, PrimaryKey extends Serializable> implements GenericDAO<Entity, PrimaryKey> { /** Der zentrale Logger für die DAO's. */ protected final static Logger logger = getLogger(GenericDAO.class); /** Die Klasse der Entität. */ protected final Class<Entity> entityClass; /** Die Klasse des primären Key. */ protected final Class<PrimaryKey> primaryKeyClass; { ParameterizedType type = (ParameterizedType) getClass().getGenericSuperclass(); Type[] actualTypeArguments = type.getActualTypeArguments(); @SuppressWarnings("unchecked") Class<Entity> entityClass = (Class<Entity>) actualTypeArguments[0]; this.entityClass = entityClass; @SuppressWarnings("unchecked") Class<PrimaryKey> primaryKey = (Class<PrimaryKey>) actualTypeArguments[1]; this.primaryKeyClass = primaryKey; } /** * {@inheritDoc} * @see GenericDAO#remove(java.io.Serializable) */ @Override public void remove(PrimaryKey key) { remove(findByPrimaryKey(key)); } }
Die Spezialisierung des Data Access Objects für JPA
Die grundlegende Schnittstelle eines Datenzugriffsobjektes diesmal abhängig von der Zugriffstechnologie Java Persistence API (JPA).
/** * Eine Erweiterung der Schnittstelle {@link GenericDAO} für JPA. * @author Frank W. Rahn * @param <Entity> Die Klasse der Entität * @param <PrimaryKey> Die Klasse des primären Key * @see de.rahn.db.dao.GenericDAO */ public interface GenericJpaDAO<Entity, PrimaryKey extends Serializable> extends GenericDAO<Entity, PrimaryKey> { // Noch keine spezielle Definitionen. }
In der folgenden abstrakten Implementierung dieser Schnittstelle wird die JPA Annotation @PersistenceContext
verwendet. Der EntityManager
wird dadurch durch das Spring Framework injiziert.
/** * Eine Implementierung der Schnittstelle {@link GenericJpaDAO} für JPA. * @author Frank W. Rahn * @param <Entity> Die Klasse der Entität * @param <PrimaryKey> Die Klasse des primären Key * @see de.rahn.db.dao.AbstractGenericDAO */ public abstract class AbstractGenericJpaDAO<Entity, PrimaryKey extends Serializable> extends AbstractGenericDAO<Entity, PrimaryKey> implements GenericJpaDAO<Entity, PrimaryKey> { @PersistenceContext private EntityManager entityManager; /** * @return Liefert den {@link #entityManager} */ protected final EntityManager getEntityManager() { return entityManager; } /** * {@inheritDoc} * @see GenericDAO#create(java.lang.Object) */ @Override public PrimaryKey create(Entity newPersistentObject) { entityManager.persist(newPersistentObject); return getPrimaryKey(newPersistentObject); } /** * {@inheritDoc} * @see GenericDAO#save(java.lang.Object) */ @Override public void save(Entity persistentObject) { entityManager.merge(persistentObject); } /** * {@inheritDoc} * @see GenericDAO#remove(java.lang.Object) */ @Override public void remove(Entity persistentObject) { entityManager.remove(persistentObject); } /** * {@inheritDoc} * @see GenericDAO#findByPrimaryKey(java.io.Serializable) */ @Override public Entity findByPrimaryKey(PrimaryKey key) { return entityManager.find(entityClass, key); } }
Im folgendem Klassendiagramm ist der Sachverhalt der abstrakten generischen JPA Datenzugriffsobjekte grafisch per UML dargestellt.
Die Definition des Service der Fahrerverwaltung
Zunächst wird die Schnittstelle dieses Services definiert. Auch dieses Beispiel ist relativ einfach gehalten.
/** * Das Interface zum Service Drivers. * @author Frank W. Rahn */ public interface Drivers { /** * Hole alle Fahrer. * @return die Liste der Fahrer */ List<Driver> getDrivers(); /** * Hole einen Fahrer. * @param id die Id eines Fahrers * @return der Fahrer */ Driver getDriver(Long id); /** * Lege einen Fahrer an. * @param name der Name des Fahrers * @param firstname der Vorname des Fahrers * @return die Id des Fahrers */ Long createDriver(String name, String firstname); /** * Lege einen Fahrer an. * @param driver der Fahrer * @return die Id des Fahrers */ Long create(Driver driver); /** * Speichere den Fahrer. * @param driver der Fahrer */ Driver save(Driver driver); /** * Füge ein Auto zum Fahrer hinzu. * @param id die Id des Fahrers * @param car das Auto * @return der komplette Fahrer */ Driver addCarToDriver(Long id, Car car); }
Die XML Konfiguration für dieses Modul.
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd "> <description> Dieses ist die zentrale Konfiguration für den Service Drivers. </description> <!-- Scanne das Package nach Spring Beans --> <context:component-scan base-package="de.rahn.services.drivers" /> </beans>
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.
- 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. - 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. - 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.
- In der Zeile 54 wird eine gerichtete Relation (1-zu-n) mit weiterreichen der CRUD-Operationen (Cascade) zum Auto definiert.
- 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 Attributpublic Driver driver;
definiert worden wäre, hatte folgende Anweisung für die Relation ausgereicht:@OneToMany(cascade = CascadeType.ALL, mappedBy = "driver")
.
/** * Die Klasse eines Fahrers. * @author Frank W. Rahn */ @Entity @Table(schema = "rahn") @Access(AccessType.FIELD) @NamedQueries(@NamedQuery(name = Driver.FIND_ALL, query = "from Driver d")) public class Driver { /** Konstante für die NamedQuery. */ public static final String FIND_ALL = "Driver.findAll"; /** Der Identifizierer des Fahrers. */ @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "DriverSEQ") @SequenceGenerator(name = "DriverSEQ", sequenceName = "DriverSEQ", schema = "rahn") @Basic(optional = false) private Long id; /** Der Name des Fahrers. */ @Basic(optional = false) @Column(nullable = false) private String name; /** Der Vorname des Fahrers. */ @Basic private String firstname; /** Die Autos die der Fahrer fährt. */ @OneToMany(cascade = CascadeType.ALL) @JoinColumn(name = "driver_id", nullable = false) private Set<Car> cars = new HashSet<>(); ...
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 derEntityManager
schon geschlossen wurde.
/** * @see java.lang.Object#hashCode() */ @Override public int hashCode() { final int prime = 31; int result = 1; if (cars != null && Persistence.getPersistenceUtil().isLoaded(cars)) { result = prime * result + cars.hashCode(); } else { result = prime * result; } result = prime * result + (firstname == null ? 0 : firstname.hashCode()); result = prime * result + (id == null ? 0 : id.hashCode()); result = prime * result + (name == null ? 0 : name.hashCode()); return result; } /** * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (!(obj instanceof Driver)) { return false; } Driver other = (Driver) obj; if (cars == null) { if (other.cars != null) { return false; } } else { if (Persistence.getPersistenceUtil().isLoaded(cars) && Persistence.getPersistenceUtil().isLoaded(other.cars)) { if (!cars.equals(other.cars)) { return false; } } } if (firstname == null) { if (other.firstname != null) { return false; } } else if (!firstname.equals(other.firstname)) { return false; } if (id == null) { if (other.id != null) { return false; } } else if (!id.equals(other.id)) { return false; } if (name == null) { if (other.name != null) { return false; } } else if (!name.equals(other.name)) { return false; } return true; } /** * @see java.lang.Object#toString() */ @Override public String toString() { return new StringBuilder().append("Driver [id=").append(id) .append(", name=").append(name).append(", firstname=") .append(firstname).append("]").toString(); } ...
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).
/** * Die Klasse eines Autos. * @author Frank W. Rahn */ @Entity @Table(schema = "rahn") @Access(AccessType.FIELD) public class Car { /** Die Identität eines angemeldeten Autos. */ @Id @Basic(optional = false) private String id; /** Der Typ des Autos. */ @Basic(optional = false) private String type; ...
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 AngabeJTA
, wo der Transaktionsmanager des Java EE Containers verwendet würde:UserTransaction utx = (UserTransaction) (new InitialContext()).lookup("java:comp/UserTransaction");
<?xml version="1.0" encoding="UTF-8"?> <persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd "> <persistence-unit name="test-spring-drivers" transaction-type="RESOURCE_LOCAL" /> </persistence>
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 vonorg.springframework.dao.DataAccessException
übersetzt. - Ab der Zeile 32 wird eine Methode
findAll()
implementiert, die per@NamedQuery
definierten Abfrage alle Instanzen dieser Klasse lädt.
/** * Ein DAO für den Fahrer. * @author Frank W. Rahn * @param <Entity> Die Klasse der Entität * @param <PrimaryKey> Die Klasse des primären Key */ @Repository public class DriverDAO extends AbstractGenericJpaDAO<Driver, Long> { /** * {@inheritDoc} * @see GenericDAO#getPrimaryKey(java.lang.Object) */ @Override public Long getPrimaryKey(Driver persistentObject) { return persistentObject.getId(); } /** * Suche alle Fahrer. * @return alle Fahrer */ public List<Driver> findAll() { return getEntityManager().createNamedQuery(Driver.FIND_ALL, Driver.class).getResultList(); } }
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 Namendrivers
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.
/** * Die Standard-Implementierung des Services {@link Drivers}. * @author Frank W. Rahn */ @Service("drivers") @Transactional public class StandardDrivers implements Drivers { @Autowired private DriverDAO driverDAO; /** * {@inheritDoc} * @see Drivers#getDrivers() */ @Override @Transactional(propagation = Propagation.SUPPORTS) public List<Driver> getDrivers() { return driverDAO.findAll(); } /** * {@inheritDoc} * @see Drivers#getDriver(Long) */ @Override @Transactional(propagation = Propagation.SUPPORTS) public Driver getDriver(Long id) { return driverDAO.findByPrimaryKey(id); } /** * {@inheritDoc} * @see Drivers#createDriver(String, String) */ @Override public Long createDriver(String name, String firstname) { Driver driver = new Driver(); driver.setName(name); driver.setFirstname(firstname); return create(driver); } /** * {@inheritDoc} * @see Drivers#create(Driver) */ @Override public Long create(Driver driver) { return driverDAO.create(driver); } /** * {@inheritDoc} * @see Drivers#save(Driver) */ @Override public Driver save(Driver driver) { driverDAO.save(driver); return driver; } /** * {@inheritDoc} * @see Drivers#addCarToDriver(Long, Car) */ @Override public Driver addCarToDriver(Long id, Car car) { Driver driver = driverDAO.findByPrimaryKey(id); driver.getCars().add(car); return save(driver); } }
Im folgenden Bild wird der vollständige Service in einem UML-Klassendiagramm dargestellt.
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 Anwendung zum Aufrufen der Fahrerverwaltung. * @author Frank W. Rahn */ @Component public class Application implements Runnable { private static final Logger logger = getLogger(Application.class); @Autowired(required = true) private Drivers drivers; /** * {@inheritDoc} * @see Runnable#run() */ @Override public void run() { // Lege einen Fahrer an Long id = drivers.createDriver("Rahn", "Frank"); logger.info("Einen Fahrer mit der Id '{}' angelegt", id); // Hole den Fahrer wieder Driver driver = drivers.getDriver(id); logger.info("Den Fahrer mit der Id '{}' geholt: {}", id, driver); // Lege ein Auto an Car car = new Car(); car.setId("K-XX 4711"); car.setType("Audi A6"); driver = drivers.addCarToDriver(id, car); logger.info("Den Fahrer mit der Id '{}' geändert: {}", id, driver); // Alle Fahrer selektieren List<Driver> listDrivers = drivers.getDrivers(); for (Driver driver2 : listDrivers) { logger.info( "Fahrer: Id '{}' Name: {} {}", new Object[] { driver2.getId(), driver2.getFirstname(), driver2.getName() }); } } }
Die XML Konfiguration zum Einstieg in die Anwendung muss 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.
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd "> <description> Dieses ist die zentrale Konfiguration für die Anwendungen. </description> <!-- Enabling des AspectJ Support --> <aop:aspectj-autoproxy proxy-target-class="true" /> <!-- Das Verwenden von allgemeinen Annotationen ermöglichen --> <context:annotation-config /> <!-- Die projektspezifischen Konfigurationen laden --> <import resource="db.xml" /> <import resource="classpath:/de/rahn/services/drivers/drivers.xml" /> <import resource="classpath:/de/rahn/app/application.xml" /> </beans>
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.
<!-- Starte die HSQL-Datenbank im Memory --> <jdbc:embedded-database id="dataSource" type="HSQL"> <jdbc:script location="classpath:/META-INF/spring/init.sql" /> </jdbc:embedded-database> <!-- Erzeuge die Persitence-Unit --> <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" p:dataSource-ref="dataSource" p:persistenceUnitName="test-spring-drivers"> <property name="jpaVendorAdapter"> <1bean p:generateDdl="true" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" /> </property> </bean> <!-- Einen Transaktionmanager erzeugen --> <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager" p:entityManagerFactory-ref="entityManagerFactory" /> <!-- Das Verwenden von Annotationen für die Transaktionen ermöglichen --> <tx:annotation-driven proxy-target-class="true" /> </beans>
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.
create SCHEMA rahn; create sequence DriverSEQ; create table rahn.Driver ( id bigint not null, firstname varchar(255), name varchar(255) not null, primary key (id)); create table rahn.Car ( id varchar(255) not null, type varchar(255) not null, driver_id bigint not null, primary key (id)); alter table rahn.Car add constraint FK107B45ADEE2FE foreign key (driver_id) references rahn.Driver; insert into rahn.Driver (firstname, name, id) values ('Martin', 'Rahn', next value for DriverSEQ);
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
.
/** * Die Testklasse für {@link DriverDAO}. * @author Frank W. Rahn */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = { "/META-INF/spring/context-test.xml", "../drivers.xml" }) @Transactional public class DriverDAOTest { @Autowired private DriverDAO driverDAO; /** * Test method for {@link DriverDAO#getPrimaryKey(Driver)}. */ @Test public void testGetPrimaryKey() { Driver driver = new Driver(); driver.setId(new Long(4711)); Long id = driverDAO.getPrimaryKey(driver); assertThat("Primärer Key unterschiedlich", driver.getId(), is(id)); } /** * Test method for {@link DriverDAO#findAll()}. */ @Test public void testFindAll() { List<Driver> drivers = driverDAO.findAll(); assertThat("kein Ergebnis geliefert", drivers, notNullValue()); assertThat("Anzahl der Treffer ungleich", drivers.size(), is(1)); Driver driver = drivers.get(0); assertThat("id ungleich", driver.getId(), is(0L)); assertThat("firstname ungleich", driver.getFirstname(), is("Martin")); assertThat("name ungleich", driver.getName(), is("Rahn")); } /** * Test method for * {@link de.rahn.db.jpa.dao.AbstractGenericJpaDAO#create(Object)}. */ @Test public void testCreate() { Driver driver = new Driver(); driver.setName("Rahn"); driver.setFirstname("Frank"); Long id = driverDAO.create(driver); assertThat("keine id geliefert", id, notNullValue()); assertThat("ungleiche id", driver.getId(), is(id)); Driver driver2 = driverDAO.findByPrimaryKey(id); assertThat("doch nicht gespeichert", driver2, notNullValue()); assertThat("ungleicher Fahrer", driver2, sameInstance(driver)); } /** * Test method for * {@link de.rahn.db.jpa.dao.AbstractGenericJpaDAO#save(Object)}. */ @Test public void testSave() { Driver driver = driverDAO.findByPrimaryKey(0L); assertThat("kein Fahrer gefunden", driver, notNullValue()); driver.setFirstname("Peter"); driverDAO.save(driver); Driver driver2 = driverDAO.findByPrimaryKey(0L); assertThat("kein Fahrer gefunden", driver2, notNullValue()); assertThat("ungleicher Fahrer", driver2, sameInstance(driver)); } /** * Test method for * {@link de.rahn.db.jpa.dao.AbstractGenericJpaDAO#remove(Object)}. */ @Test public void testRemoveEntity() { List<Driver> drivers = driverDAO.findAll(); assertThat("Anzahl der Treffer ungleich", drivers.size(), is(1)); Driver driver = drivers.get(0); driverDAO.remove(driver); drivers = driverDAO.findAll(); assertThat("Anzahl der Treffer ungleich", drivers.size(), is(0)); } /** * Test method for * {@link de.rahn.db.jpa.dao.AbstractGenericJpaDAO#findByPrimaryKey(Serializable)} * . */ @Test public void testFindByPrimaryKey() { Driver driver = driverDAO.findByPrimaryKey(0L); assertThat("kein Ergebnis geliefert", driver, notNullValue()); assertThat("id ungleich", driver.getId(), is(0L)); assertThat("firstname ungleich", driver.getFirstname(), is("Martin")); assertThat("name ungleich", driver.getName(), is("Rahn")); } /** * Test method for * {@link de.rahn.db.dao.AbstractGenericDAO#remove(Serializable)}. */ @Test public void testRemovePrimaryKey() { driverDAO.remove(0L); List<Driver> drivers = driverDAO.findAll(); assertThat("kein Ergebnis geliefert", drivers, notNullValue()); assertThat("Anzahl der Treffer ungleich", drivers.size(), is(0)); } }
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.
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd "> <description> Dieses ist die zentrale Konfiguration für die Tests. </description> <!-- Das Verwenden von allgemeinen Annotationen ermöglichen --> <context:annotation-config /> <!-- Die projektspezifischen Konfigurationen laden --> <import resource="db.xml" /> </beans>
Jetzt fehlt nur noch der Unit Test für den Service der Fahrerverwaltung StandardDriver
.
/** * Die Testklasse für {@link StandardDrivers}. * @author Frank W. Rahn */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = { "/META-INF/spring/context-test.xml", "../drivers.xml" }) @Transactional public class StandardDriversTest { /** CAR_TYPE */ private static final String CAR_TYPE = "Audi A6"; /** CAR_ID */ private static final String CAR_ID = "K-XX 4711"; @Autowired private Drivers drivers; /** * Test method for {@link StandardDrivers#getDriver(Long)}. */ @Test public void testGetDriver() { Driver driver = drivers.getDriver(0L); assertThat("kein Fahrer geliefert", driver, notNullValue()); assertThat("id ungleich", driver.getId(), is(0L)); assertThat("firstname ungleich", driver.getFirstname(), is("Martin")); assertThat("name ungleich", driver.getName(), is("Rahn")); } /** * Test method for {@link StandardDrivers#createDriver(String, String)}. */ @Test public void testCreateDriver() { Long id = drivers.createDriver("Rahn", "Frank"); assertThat("keine id geliefert", id, notNullValue()); Driver driver = drivers.getDriver(id); assertThat("doch nicht gespeichert", driver, notNullValue()); assertThat("ungleiche id", driver.getId(), is(id)); assertThat("firstname ungleich", driver.getFirstname(), is("Frank")); assertThat("name ungleich", driver.getName(), is("Rahn")); } /** * Test method for {@link StandardDrivers#addCarToDriver(Long, Car)}. */ @Test public void testAddCarToDriver() { Car car = new Car(); car.setId(CAR_ID); car.setType(CAR_TYPE); Driver driver = drivers.addCarToDriver(0L, car); assertThat("doch nicht gespeichert", driver, notNullValue()); assertThat("id ungleich", driver.getId(), is(0L)); assertThat("firstname ungleich", driver.getFirstname(), is("Martin")); assertThat("name ungleich", driver.getName(), is("Rahn")); assertThat("anzahl Autos ungleich", driver.getCars().isEmpty(), not(true)); car = driver.getCars().iterator().next(); assertThat("id ungleich", car.getId(), is(CAR_ID)); assertThat("type ungleich", car.getType(), is(CAR_TYPE)); } }
Hier noch die entsprechende Erfolgsmeldung.
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 programmiert werden.
Nachfolgenden ist die Maven Konfiguration für die Erzeugung der Criteria API mit dem JPA 2 Metamodel Generator von Hibernate dargestellt.
<plugin> <groupId>org.bsc.maven</groupId> <artifactId>maven-processor-plugin</artifactId> <version>22.2.1</version> <executions> <execution> <goals> <goal>process</goal> </goals> </execution> </executions> <configuration> <optionMap> <addGeneratedAnnotation>true</addGeneratedAnnotation> <addSuppressWarningsAnnotation>true</addSuppressWarningsAnnotation> <debug>true</debug> </optionMap> <processors> <processor>org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor</processor> </processors> </configuration> <dependencies> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-jpamodelgen</artifactId> <version>${hibernate-jpamodelgen-version}</version> </dependency> </dependencies> </plugin> ...
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.
/** * Ein typisches Spring-Template, dass die Erzeugung einiger notwendigen Factory * Klassen zur Erzeugung von Queries auf Basis der Criteria API an das * {@link AbstractGenericJpaDAO} deligiert. * @author Frank W. Rahn * @param <Entity> Die Klasse der Entität * @see AbstractGenericJpaDAO */ public interface CriteriaQueryTemplate<Entity> { /** * Diese Methode wird durch das {@link AbstractGenericJpaDAO} ausgeführt und * stellt einige Standardkomponenten der Criteria API zu Verfügung. * @param builder der {@link CriteriaBuilder} * @param query die an die Entität gebundene Abfrage * @param rootEntity die Projektionsvariable der FROM-Klausel */ void doBuild(CriteriaBuilder builder, CriteriaQuery<Entity> query, Root<Entity> rootEntity); }
Das abstrakte und generische JPA-Datenzugriffsobjekt AbstractGenericJpaDAO
, wird um die Methode buildQuery()
erweitert.
/** * Erzeuge über die Criteria API eine JPA Query. * @param template der Builder * @return eine ausführbare JPA Query */ protected TypedQuery<Entity> buildQuery( CriteriaQueryTemplate<Entity> template) { // Erzeuge eine Builder CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); // Erzeuge die Query CriteriaQuery<Entity> criteriaQuery = criteriaBuilder.createQuery(entityClass); // Erzeuge die Referenz zur Hauptentität Root<Entity> rootEntity = criteriaQuery.from(entityClass); // Führe den Build durch template.doBuild(criteriaBuilder, criteriaQuery, rootEntity); // Erzeuge eine ausführbare JPA Query return entityManager.createQuery(criteriaQuery); } ...
Das Datenzugriffsobjekt der Fahrer stellt die Methode findByName()
bereit.
/** * Suche alle Fahrer mit dem Namen. * @param name der Name des Fahrers * @return die gefundenen Fahrer */ public List<Driver> findByName(final String name) { return buildQuery(new CriteriaQueryTemplate<Driver>() { @Override public void doBuild(CriteriaBuilder builder, CriteriaQuery<Driver> query, Root<Driver> rootEntity) { // Erzeuge eine logische Ausdruck Predicate predicate = builder.equal(rootEntity.get(Driver_.name), name); // Definiere die Abfrage query.select(rootEntity).where(predicate).distinct(true); } }).getResultList(); } ...
Zum Abschluss darf natürlich der Test nicht fehlen.
/** * Test der Criteria API. */ @Test public void testCriteriaAPI() { List<Driver> drivers = driverDAO.findByName("Rahn"); assertThat("keine Ergebnis geliefert", drivers, notNullValue()); assertThat("Anzahl der Treffer ungleich", drivers.size(), is(1)); Driver driver = drivers.get(0); assertThat("id ungleich", driver.getId(), is(0L)); assertThat("firstname ungleich", driver.getFirstname(), is("Martin")); assertThat("name ungleich", driver.getName(), is("Rahn")); } ...
Update am 28.09.2012
Es wurden folgende Änderungen an allen Projekten vorgenommen.
- Die Projekte wurden aus meinem lokalen Apache Subversion Repository in meine öffentlichen GitHub Repositories verschoben.
- Projekt test-spring-simple
- Projekt test-spring-jpa
- Projekt test-spring-web
- Die folgenden typischen Anpassungen an Git wurden an allen Projekten durchgeführt.
.directory
gelöscht.gitignore
hinzugefügtREADME.md
hinzugefügtCOPYRIGHT.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.
- Aktualisierung des OpenJDK auf die Version 1.7.0.
- Aktualisierung der Entwicklungsumgebung Eclipse auf die Version 4.2.1.
Die benötigten Plugins aus dem Eclipse Marketplace: - 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 jeweiligenpom.xml
auf GitHub entnehmen.
- Wer ist der optimale Java Bean Mapper? - Freitag, 22. September 2023
- Spring Boot Webanwendung: Die ersten Schritte (Tutorial) - Montag, 28. März 2016
- Mainframe-Zugriff via Java - Sonntag, 04. Mai 2014
Hinterlasse einen Kommentar
An der Diskussion beteiligen?Hinterlasse uns deinen Kommentar!