Spring Boot JPA Query Methods

Das war’s! Die Zeiten, in denen Relationale Datenbanken der Quasi-Standard zur Persistierung waren sind endgültig vorbei. Ehrlicherweise muss ich sagen: Zum Glück ist das so. Denn es gibt viele Anwendungsfälle, die sich mit der Konkurrenz aus dem Lager der NoSQL Systeme deutlich besser lösen lassen. Aber genauso gibt es genügend Problemstellungen, für die Relationale Systeme nach wie vor sehr gut geeignet sind.

Durch das fixierte Datenbankschema wirkt die Anbindung an vielen Stellen eher schleppend: Änderungen im Schema müssen im Code nachgezogen werden und andersherum. „Mal schnell“ eine Anpassung vornehmen ist nicht…

Oder vielleicht doch?

Eine wesentliche Verbesserung brachten die Object-Relational-Mapper (kurz ORM), die angetreten sind, die Strukturen in der Datenbank mit denen im Java-Code zu synchronisieren. Viel Schreibarbeit fiel weg und die Standardzugriffe auf die Relationale Datenbank kamen schon frei Haus. Mit Tools zur Datenbankmigration (zum Beispiel Flyway) reduzieren sich die Reibungsverluste schon deutlich.

Einziger Wermutstropfen: Die Datenabfrage, auch mit Hilfe eines ORMs, ist stark formalisiert. Bildlich gesprochen bauen wir SQL aus Java-Code. Durch die breit gefächerten Möglichkeiten von SQL sind natürlich auch die Konstrukte auf Java-Seite entsprechend komplex. Was in umfangreichen Abfragen ein Vorteil ist, da ich Teile der Queries wiederverwenden kann, treibt mich bei kleinen „Standardabfragen“ in den Wahnsinn…

Da gibt‘s doch sicher was von Spring…

Die gute Nachricht: Es geht auch einfach! Die Macher des Springframeworks haben mit dem Projekt Spring Data JPA diverse Abfragemöglichkeiten geschaffen, unter anderem auch eine für alle möglichen Standardanforderungen, die Repository Query Methods.

Spring Data JPA versteckt dabei einen Teil des Funktionsumfangs der Jakarta Persistence Spezifikation (kurz: JPA, früher stand das für Java Persistence API) vor uns als Anwender. Was zunächst nach einem schlechten Deal klingt, entpuppt sich in der Praxis als sehr hilfreich!

Was wir brauchen

Zunächst benötigen wir die entsprechende Abhängigkeit in unserem Spring Boot Projekt. Einfach folgendes in die pom.xml einfügen, in gradle geht es analog:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

Damit stehen uns im Projekt die Konstrukte von JPA und die Hilfskomponenten von Spring Data JPA zur Verfügung. Im Beispielprojekt unter https://gitlab.mischok-it.de/open/company-database gibt es drei JPA Entities: Employee, Company und Office. Jeder Angestellte (Employee) arbeitet in einer Firma (Company). Jede Firma kann mehrere Büros (Office) haben. Es gibt also jeweils zwischen Angestellten und Firmen sowie zwischen Büros und Firmen eine n:1 Beziehung. Bis hierhin ist das einfach nur Standard JPA, die Entity Employee ist beispielsweise so definiert:

@Entity
@Table(name="employee")
public class Employee implements WithId {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name="id")
private Long id;
@Column(name="first_name")
private String firstname;
@Column(name="last_name")
private String lastname;
@ManyToOne
@JoinColumn(name = "company_id")
private Company company;
}

Was liefert uns Spring?

Während wir mit Standard JPA etwas mehr Aufwand betreiben müssen um unsere Daten abzufragen, schenkt uns Spring Data JPA die sogenannten Repositories. Diese werden als Beans im Spring Kontext angelegt und dienen zum Lesen und Schreiben von Entities, damit von Daten in der Datenbank.

Repositories liegen stets als Interface vor, es wird keine explizite Implementierung benötigt! Spring kümmert sich unter der Haube und für uns unsichtbar um die Umsetzung unserer Wünsche. Wie sieht das in der Praxis aus?

Folgende Repository-Definition bietet uns Zugriff auf Employee Entities:

@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
}

Und nein, ich habe nicht vergessen, den Code einzufügen ;-) Das ist wirklich Alles! Dieses Repository können wir an anderer Stelle per @Autowired injecten und haben sofort Zugriff auf diese und viele weitere weitere Methoden:

  • T save(T entity);
  • List<T> findAll();
  • Optional<T> findById(ID id);
  • void delete(T entity);
  • boolean existsById(ID id);

Wir können also ohne weitere Vorarbeit loslegen und nach Herzenslust Employees erstellen, sie wieder Löschen, Editieren oder alle abfragen.

Richtige Abfragen

Irgendwann haben wir jedoch gelernt, dass wir Abfragen generell nach Möglichkeit der Datenbank überlassen sollten. Wenn wir also alle Angestellten abfragen wollen, die in einer bestimmten Firma arbeiten, würden wir das in SQL mit einem Abgleich über die Spalte company_id abbilden. Mit Spring Data JPA kommen wir einfacher an das Ziel, das Zauberwort sind Query Methods.

Zur Umsetzung der geschilderten Anforderung legen wir also in unserem Repository eine solche Query Method an:

@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
List<Employee> findByCompany(Company company);
}

Der Vollständigkeit halber sei noch einmal erwähnt, dass wirklich keine weiteren Schritte nötig sind ;-) Damit haben wir tatsächlich bereits unsere erste einfache Abfrage definiert! Der entsprechende Test in der Klasse QueryTest zeigt die Verwendung:

@Test
public void testGetEmployeesByCompany() {
List<Employee> employeesOfReplant = employeeRepository.findByCompany(innovatic);
assertThat(employeesOfReplant, hasSize(4));
...
}

Ja, es ist wirklich so einfach ;-) Den Sprung von der Company auf deren ID erspart uns bereits JPA, was implizit den Vergleich auf die IDs herunterbricht. Insgesamt bleibt die Syntax dadurch schön kompakt – zumindest im einfachen Beispiel…

Aufbau der QueryMethods

Neben ein paar Spezialfällen sind die Query Methods immer nach einem festen Schema aufgebaut: Der Rückgabewert ist eine List<T>, wobei T die abzufragende Entity ist. Danach folgen das Schlüsselwort findBy und die Bedingungen der Abfrage. Die Query Method findByCompany ist bereits eine abgekürzte Schreibweise für findByCompanyEquals.

Wie werten wir die Bedingung aus? Da der (implizite) Vergleich mit dem Schlüsselwort Equals einen Parameter benötigt, ziehen wir den erste an die Query Method übergebenen Parameter dafür heran. Dieser muss vom Typ Company sein, da die zu vergleichende Eigenschaft der Entity von eben diesem Typ ist.

Veknüpfung von Bedingungen

Zwischenfazit: Wir haben gesehen, wie wir mit sehr einfachen Mitteln Entities nach einer bestimmten Eigenschaft filtern können. Aufmerksamen LeserInnen fällt auf, dass ich bereits in zwei Nebensätzen das Thema der kompakten Syntax angerissen habe. Bitte weiter im Hinterkopf behalten, das wird jetzt gleich lebensnotwendig ;-)

Natürlich können wir auch mehrere Bedingungen formulieren und diese mittels And oder Or verbinden. Zunächst ein Beispiel:

List<Employee> findByFirstnameAndLastname(String firstname, String lastname);

Diese Methode sucht nach Employees mittels eines exakten Matches auf Vor- und Nachname. An sich problemlos oder? Wir dürfen lediglich die Reihenfolge und Anzahl der Parameter nicht aus den Augen verlieren. Aber wenn die Anzahl und die Typisierung passt, löst Spring Boot die Query Method auf und wir erhalten bei der Abfrage das gewünschte Ergebnis.

Das Beste kommt noch

In einem folgenden Post werde ich noch eine ganze Reihe weiterer Schlüsselwörtern vorstellen, die in den Query Methods verwendet werden können. Wir haben deutlich mehr Möglichkeiten, als nur einfache Objekt- oder Stringvergleiche zu machen, soviel schon einmal als kleiner Teaser…

Eine kurze Auflösung aber schon an dieser Stelle: Mein Beharren auf der kompakten Schreibweise hat natürlich einen Hintergrund. Stellen wir uns den Anwendungsfall vor, dass wir nun alle Employees anhand der Firma, sowie Vor- und Nachname ermitteln wollen. Die Umsetzung als Query Method sieht dann zum Beispiel so aus:

List<Employee> findByCompanyEqualsFirstnameEqualsAndLastnameEquals(Company company, String firstname, String lastname);

Das ist jetzt noch keine komplexe Abfrage, aber definitiv schon nicht mehr flüssig zu lesen oder auf einen Blick zu erfassen. Wenn wir uns in einem weiteren Artikel zum Beispiel noch String-Vergleiche und Operationen ansehen, wird es noch unübersichtlicher, soviel muss ich vorweg nehmen. Daher ist in jedem Fall von Anfang an darauf zu achten, die Schreibweise so kompakt wie möglich zu halten, im letzten Beispiel also die Schlüsselwörter Equals wegzulassen.

In der Praxis

Ich liebe Test-Driven-Development! Spring eignet sich aus meiner Sicht generell sehr gut dafür und die Query Methods fügen sich sehr gut in den Workflow ein: Wenn ich aus einer Klasse mit Businesslogik eine neue Abfrage definieren möchte, injiziere ich mir zunächst mein Repository. Dann fange ich einfach an, meine „Abfrage“ zu schreiben: in Form der Query Method. Wenn der Name passt und ich alle Parameter angelegt habe, meckert meine Entwicklungsumgebung natürlich, dass es diese Methode im Repository-Interface nicht gibt. Allerdings reicht mir dann ein Shortcut oder ein Mausklick, die Methode für mich anlegen zu lassen – und ich bin fertig! Dieses Top-Down-Vorgehen bringt Tempo und macht Spaß in der Entwicklung!

Wie geht es weiter?

Ich habe zu Anfang versprochen, dass es Spring Data JPA mit den Query Methods gelungen ist, manche Schwächen in der Arbeit mit Relationalen Datenbanken zu kaschieren. Aus meiner Sicht helfen die Query Methods dabei, unnötige Schreibarbeit einzusparen und einfache Anfragen auch wirklich einfach umsetzen zu können.

Bei den Beispielen sind wir jetzt zugegebenermaßen nicht über das Reich der Trivialitäten hinaus gekommen. Die Query Methods können aber mehr! In einem folgenden Post sehen wir uns an, wie wir mit Datumswerten arbeiten können, welche Möglichkeiten wir beim Stringvergleich haben und welche Unterstützung wir beim Listenvergleich erhalten.

Bis dahin viel Spaß bei den ersten Schritten mit Spring Data JPA und den Repository Query Methods!

Spring-Boot News

Bleibe auf dem aktuellsten Stand mit unseren kostenlosen Spring-Boot updates. So wirst Du direkt informiert, wenn wir einen neuen Artikel veröffentlichen.
Kein Spam, kein Bullshit, nur Spring-Boot Insider-Wissen, versprochen.

Noch keine Kommentare vorhanden.

Was denkst du?

© 2020 Mischok