Grundlagen
Als Entwurfsmuster für die Verbindung zwischen persistenten Klassen und einer Datenbank hat sich das DAO-Pattern (Wikipedia, Oracle) etabliert. Dabei steht für jede Klasse oder zumindest für jede Klassenhierarchie ein Datenzugriffsobjekt (Data Access Object, DAO) zur Verfügung. Dieses stellt mindestens die üblichen CRUD Methoden zum Speichern, Lesen und Löschen von Objekten bereit. Weitere sinnvolle Funktionalitäten sind Suchen, Sortieren oder Bildung von Teilmengen. Zur Performance-Optimierung kann es sinnvoll sein, weitere komplexere Datenbankoperationen im DAO zu implementieren, die beispielsweise mit einer Query einen ganzen Objektgraph in den Speicher laden.
Spring Repositories
Standardmethoden
Spring Data stellt Integrationsschnittstellen für eine Vielzahl von Datenhaltungssystemen zur Verfügung. Im Beispielprojekt und in diesem Tutorial wird nur Spring Data JPA verwendet.
Das zentrale Konzept zum Zugriff auf die mit JPA konfigurierte Datenbank sind die JPA Repositories. Dies sind Interfaces, die vom Interface JpaRepository<PersistentClass, KeyType> abgeleitet werden. Das Basisinterface JpaRepository wird dabei mit dem Klassentyp der persistenten Klasse (PersistentClass) und dem Typ des @ID-Attributs (KeyType) dieser Klasse parametrisiert. Spring Data generiert automatisch eine Implementierung für dieses Interface, die insgesamt 18 Methoden zur Verfügung stellt.
Es gibt mehrere Verfahren, weitere Methoden zu dem Interface hinzuzufügen und damit den generierten Funktionsumfang zu erweitern (Foliensatz). Die Spring Data JPA Referenzdokumentation ist hier recht ausführlich.
- Named Queries: Query aus dem Methodennamen erstellen (Spring Referenz)
- @Query annotierte Methoden: In der Annotation können Queries in der JPQL oder native SQL-Queries formuliert werden.
- Verwendung von Spezifikationen in Queries
- Auch Stored Procedures können verwendet werden
Beispiele für Named Queries und Native Queries
/** * * Access to Task class hierarchy */ public interface TaskRepository extends JpaRepository<Task, Long> { List<Task> findByProjectId(Long id); List<Task> findByProjectIdAndIdNotIn(Long pid, Collection<Long> taskIds); List<Task> findBySprintsIdAndIdNotIn(Long spid, Collection<Long> taskIds); List<Task> findBySprintsId(Long spid); List<Task> findAllByOrderByTagAsc(); @Query(value = "SELECT * FROM TASK NATURAL JOIN COMPOUND_TASK", nativeQuery = true) List<CompoundTask> findAllCompoundTask(); @Query(value = "SELECT * FROM TASK NATURAL JOIN SUBTASK", nativeQuery = true) List<Subtask> findAllSubtask(); }
Spezielle Fälle
Einige Fälle sind in der Spring Dokumentation nicht besonders gut dokumentiert und auch in Foren nur schwer zu finden. Dazu gehört der Zugriff auf Subklassen aus dem Repository einer Superklasse. Mit einer nativen Query ist das ganz einfach und im Beispiel oben in Zeile 22 und 24 zu sehen.
Noch allgemeiner ist das ResultSet Mapping, das in diesen Posts von Thorben Janssen beschrieben ist.
Transaktionen
Spring Data Queries sind standardmäßig transaktional. Das reicht manchmal nicht, da mehrere Queries in eine Transaktion eingebettet werden müssen. Auch Zugriffe auf assoziierte Objekte müssen oft innerhalb einer Transaktion stattfinden, da Assoziationen standardmäßig „lazy“ implementiert werden. D.h., assoziierte Objekte werden erst aus der Datenbank geholt, wenn im Java-Code auf sie zugrgriffen wird. Daher gibt es die Möglichkeit, Methoden mit der Annotation @Transactional als transaktional zu markieren, um sie innerhalb einer Transaktion ablaufen zu lassen. @Transaktional kann auch eine ganze Klasse annotieren, das gilt dann für alle Methoden. Lang dauernde Aktionen, z.B. Benutzerinteraktionen, sollten innerhalb transaktionaler Methode unbedingt vermieden werden, um Blockierungen der Datenbank zu verhindern. Viele Services sind transaktionale Klassen, z.B. die Services, die die Benutzerschnittstelle mit dem zentralen Domain-Modell verbinden. Aber auch Testmethoden müssen häufig transaktional sein.
Beispiele
... /** * Facade class to access project entities */ @Service @Transactional @Slf4j class ProjectService {
... @Transactional def 'task hierarchy can be persisted'() { when: "compoundTask are created" task1 = new CompoundTask(estimate: 5000) def task2 = new CompoundTask(sub3) and: 'subtasks are added' task1.subtask.add(sub1) task1.subtask.add(sub2) task1.subtask.add(task2) taskRepository.saveAndFlush(task1) then: 'all tasks should be persisted' assert taskRepository.count() == 5 }
Aufgaben
- Schreiben Sie Repository Interfaces für Ihre eigenen Klassen, die Sie dem Projekt hinzugefügt haben.
- Ersetzen Sie die
findAll().sort { ... }
Methodenaufrufe in Zeile 41 ff. in ProjectService.groovy durch eine sortierende Query.
Dieser Stackoverflow link könnte sich als hilfreich für die 2. Aufgabe erweisen: http://stackoverflow.com/questions/25486583/how-to-use-orderby-with-findall-in-spring-data
Ich hatte das By for dem OrderBy vergessen.