JPA-SQL ist eine freie Abfragesprache für JPA, mit der man Queries in SQL schreiben kann. Die Abfragen werden vom Javacode getrennt im JPA-SQL-Editor formuliert, der dann typsicheren JPA-Criteria-Code generiert. Damit vereint JPA-SQL die Einfachheit von SQL mit den Vorteilen der JPA-Criteria-API.

Der Standard für die Datenbankentwicklung in Java ist JPA (Java-Persistence-API). Die am häufigsten verwendeten Frameworks die JPA implementieren sind Hibernate, EclipseLink sowie Apache OpenJPA. Bei der Frage, wie man bei der Verwendung von JPA-Datenbankabfragen am besten formulieren soll, hat man die Qual der Wahl. Da die meisten Entwickler mit SQL vertraut sind, ist es auch in der Java-Entwicklung gängige Praxis, Datenbankabfragen in plain-SQL zu schreiben und diese mit Hilfe von SQL-Strings in den Java-Code einzubetten.

(Listing 1)
select * from customers
where contacttitle LIKE Owner AND city LIKE London
order by customerid
SQL Code für die nachfolgenden Code-Beispiele.

 

(Listing 2)
public List<Customer> searchCustomer(final String contacttitle, final String city) {
 
    final String SQL = "SELECT * FROM CUSTOMERS WHERE contacttitle LIKE ? AND city LIKE ? ORDER BY customerid"; 
 
    final Query nativeQuery = PersistenceUtils.getEntityManager("example").createNativeQuery(SQL, Customer.class);
    nativeQuery.setParameter(1, contacttitle);
    nativeQuery.setParameter(2, city);
    final List<Customer> resultList = nativeQuery.getResultList();
 
    return resultList;
}
SQL Code in Java Code eingebettet.

 

Was ist mit Typsicherheit?

Die häufige Verwendung von SQL-Strings in Java ist jedoch verwunderlich, denn Programmcode, der in Form von Strings in den Java-Code eingebunden wird, ist nicht typsicher. Dabei wird gerade die Typsicherheit immer als einer der bedeutenden Vorteile von Java ins Feld geführt, insbesondere wenn es um den Vergleich zwischen Java und Skriptsprachen wie JavaScript geht. Ist die Typsicherheit etwa nicht so wichtig, wenn es um die Formulierung von Datenbankabfragen geht?

SQL ist immer datenbankspezifisch

Datenbankanwendungen enthalten oft hunderte Abfragen und mehr. Soll die Anwendung zu mehreren Datenbanksystemen kompatibel sein, muss man im Falle von SQL-Strings viele zusätzliche Queries schreiben und die Summe an Abfragen kann sich schnell vervielfachen. Denn in der Praxis sind in SQL formulierte Abfragen fast immer datanbankspezifisch, insbesondere, wenn diese etwas komplexer werden. Trotz SQL-Standard unterscheidet sich die SQL-Syntax der verschiedenen Datenbanken an vielen Stellen gravierend.

SQL-Strings sind problematisch

Die wachsende Menge an Abfragen ist jedoch nicht das einzige Problem. Bei der Programmierung in Java profitiert der Entwickler von umfangreicher IDE-Unterstützung, u.a. Codevervollständigung, Syntax-Highlighting, Compiler-Warnings und vieles mehr. SQL-Strings schreiben ist dagegen so, als würde man mit einem Text-Editor programmieren. Fehler im SQL-Abfragecode wird man erst zur Laufzeit feststellen. Die von der Datenbank zurückgelieferten Fehlermeldungen sind oft schwer nachvollziehbar. Debuggen ist ebenfalls nicht möglich. Richtig problematisch wird es spätestens, wenn der Entwickler am Datenmodell etwas ändern muss. Da ein Refactoring von Code nicht möglich ist, der mittels Strings in Java-Code eingebettet wird, muss man mit hoher Wahrscheinlichkeit unzählige Code-Passagen anpassen. Das ist nicht nur aufwändig, sondern stellt zudem eine enorme, potentielle Fehlerquelle dar. SQL-Strings sind darüber hinaus auf Grund zahlreicher String-Verkettungen sehr schlecht lesbar und der Java-Code wird insgesamt sehr unübersichtlich, was die Wartung erschwert. Zu guter Letzt sind nachlässig formulierte SQL-Strings immer ein potentiell hohes Risiko für SQL-Injection.

Datenbankabfragen in Java

Alternativ zu SQL-Strings kann man seine Datenbankabfragen vollständig in Java schreiben. Mit QueryDSL und jOOQ stehen dafür moderne Fluent-APIs zur Verfügung. Letztere ist für Enterprise Datenbanken lizenzkostenpflichtig.
Wer auf dem JPA-Standard bleiben möchte, hat die Wahl zwischen JPQL (Java Persistence Query Language) und der JPA-Criteria-API. Für Hibernate stehen zusätzlich die Äquivalente HQL sowie die Hibernate Criteria API zur Verfügung. Die wird jedoch in der offiziellen Hibernate Dokumentation als veraltet bezeichnet und sollte demnach nicht mehr verwendet werden. Es wird angeraten alternativ die JPA Criteria API zu verwenden.

JPQL- oder JPA-Criteria?

In sämtlichen Foren sind sich Entwickler in dieser Frage recht einig. JPQL ist sehr viel einfacher. Im Vergleich zu JPA-Criteria lässt sich JPQL-Code sehr viel leichter lesen und schreiben. Das dürfte für die meisten Entwickler ausschlaggebend dafür zu sein, ihre Abfragen mit JPQL zu schreiben. JPQL ist eine speziell für JPA definierte Abfragesprache, mit der Abfragen auf Entities durchgeführt werden. Die Syntax basiert auf SQL.

JPQL ist nicht typsicher

JPQL ist jedoch ebenfalls nicht typsicher. Queries werden auch damit in Form von Strings in Java-Code eingebettet, sodass man mit JPQL dieselben Nachteile wie bei der Verwendung von nativen SQL-Strings in Kauf nehmen muss.

JPA-Criteria ist typsicher – aber zu komplex

Die JPA-Criteria ist dagegen eine typsichere API, was alle Vorteile mit sich bringt, die man sich als Entwickler wünscht, insbesondere volle IDE-Unterstützung wie Codevervollständigung, Syntax-Highlighting, Error-Warnings, Quick-Fixes und Refactoring. Dazu lässt sich Criteria-Code auch debuggen. Erst zur Laufzeit werden die benötigten SQL-Statements vom ORM-Framework passend für die jeweils angebundene Datenbank dynamisch erzeugt. Damit ist JPA-Criteria-Code auch datenbankunabhängig. Was also spricht gegen die Verwendung von JPA-Criteria? Im Vergleich zu JPQL wirkt Criteria-Code stark aufgebläht und schwer lesbar, weshalb JPA-Criteria in der Praxis meist nur für dynamische Queries eingesetzt wird.

JPA-SQL vereint die Vorteile beider Welten

JPA-SQL ist eine externe domänenspezifische Sprache (DSL), die mit Xtext designet wurde. JPA-SQL ist zusammen mit dem JPA-SQL Code-Editor als Eclipse-Plugin frei verfügbar und lässt sich über den Eclipse Marketplace oder via Update-Site in Eclipse einbinden.
Wie bei JPQL basiert die JPA-SQL Syntax auf SQL. Anders als natives SQL und JPQL werden JPA-SQL Abfragen nicht in Form von Strings in den Java-Code eingebettet, sondern getrennt vom Java-Code im JPA-SQL-Code-Editor formuliert. Eine JPA-SQLQuery besteht nur aus den Statements die für die Abfrage benötigt werden sowie aus einem Methodenkopf über den die Query später aufgerufen wird. Als Benutzer hat man das Gefühl, sämtliche Datenbankabfragen tatsächlich in SQL zu schreiben.

Entity- und DAO-Klassen sind Voraussetzung

Um eine Abfrage formulieren zu können, muss im Projekt eine Entity- sowie eine entsprechende DAO-Klasse (Data-Access-Object) vorhanden sein, für die man den JPA-SQL Code-Editor öffnen kann.

(Listing 3)
import com.company.example.entities.Customer
 
searchCustomer(String contacttitle, String city) {
    select * from Customer 
    where contacttitle like :contacttitle and city like :city 
    order by customerid
}
JPA-SQL Code im JPA-SQL Code Editor, getrennt vom Java-Code.

 

JPA-SQL generiert JPA Criteria Code

Bereits während der Eingabe wird der gesamte JPA-SQL-Code vom JPA-SQL-Compiler in JPA-Criteria-Code umgewandelt, der dann in die DAO-Klasse generiert wird. Der generierte Code wird dann in diese DAO-Klasse schrieben. Spätere Änderungen oder Erweiterungen werden ausschließlich im JPA-SQL Code-Editor vorgenommen. Der generierte JPA-Criteria-Code muss und sollte nicht mehr angefasst werden.

(Listing 4)
public List<Customer> searchCustomer(final String contacttitle, final String city) {
	final EntityManager entityManager = em();

	final CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();

	final ParameterExpression<String> contacttitleParameter = criteriaBuilder.parameter(String.class, "contacttitle");
	final ParameterExpression<String> cityParameter = criteriaBuilder.parameter(String.class, "city");

	final CriteriaQuery<Customer> criteriaQuery = criteriaBuilder.createQuery(Customer.class);

	final Root<Customer> root = criteriaQuery.from(Customer.class);

	criteriaQuery
			.where(criteriaBuilder.and(criteriaBuilder.like(root.get(Customer_.contacttitle), contacttitleParameter),
					criteriaBuilder.like(root.get(Customer_.city), cityParameter)));

	criteriaQuery.orderBy(criteriaBuilder.asc(root.get(Customer_.customerid)));

	final TypedQuery<Customer> query = entityManager.createQuery(criteriaQuery);
	query.setParameter(contacttitleParameter, contacttitle);
	query.setParameter(cityParameter, city);
	return query.getResultList();
}

Der JPA-Compiler genereiert typischen JPA-Criteria Code.

 

Komfortabler Code-Editor

Der von JPA-SQL mitgelieferte Code-Editor unterstützt viele wichtige IDE-Funktionen, u.a. Syntax-Highlighting, Codevervollständigung und Inline-Refactoring. Besonders hilfreich ist die Möglichkeit, direkt auf Entity-Klassen und deren Attribute zugreifen zu können. Des Weiteren lassen sich Ergebnisse von Aggregatfunktionen direkt an dafür vorgesehene Entities übergeben.

Umfang und Grenzen

JPA-SQL dient ausschließlich dazu, JPA-Criteria-Code zu generieren, mit dem Ziel den Einsatz der JPA-Criteria-API stark zu vereinfachen. JPA-SQL deckt alles ab, was mit JPA-Criteria möglich ist und ist gleichzeitig auf deren Funktionsumfang begrenzt. Die JPA-SQL Sprachspezifikation ist in der Dokumentation veröffentlicht. Proprietere SQL-Statements können, bzw. müssen sogar weiterhin als native SQL-Queries abgesetzt werden.

Fazit:

JPA-SQL wurde entwickelt, um die Datenbankentwicklung mit JPA, insbesondere mit der JPA-Criteria-API zu vereinfachen, sodass Benutzer allein mit SQL-Kenntnissen JPA-Queries formulieren können. JPA-SQL bietet dem Entwickler die Möglichkeit, sämtliche Datenbankabfragen in einem separaten Code-Editor in SQL-Syntax zu schreiben. Der Abfragecode wird dadurch strikt vom Java-Code getrennt. Das Endergebnis ist generierte Java-Code auf Basis der JPA-Criteria-API. Damit vereint JPA-SQL die Einfachheit und Übersichtlichkeit von SQL mit den zahlreichen Vorteilen der JPA-Criteria-API, insbesondere deren Typsicherheit. JPA-SQL kann sowohl für Hibernate, als auch für EclipseLink und OpenJPA eingesetzt werden. JPA-SQL ist völlig lizenzkostenfrei verfügbar und lässt sich über den Eclipse-Marketplace sowie via Update-Site in Eclipse einbinden.

 

Thomas Mitzka

Thomas Mitzka arbeitet seit 1995 als Datenbankadministrator, Entwickler und Systemadministrator. Mit der wachsenden Popularität von Java, hat er sich in der Entwicklung von Datenbank-Applikationen ganz auf die Kombination Java und SQL-Datenbanken fokussiert. Der Einsatz der Java Persistence API ist hierbei ein Schwerpunkt und in der Praxis von ganz besonderem Interesse. 


Diesen Artikel finden Sie auch in der Erstausgabe der JAVAPRO. Einfach hier kostenlos bestellen.

(Visited 765 times, 2 visits today)

Leave a Reply