JavaOne 2017 - JNoSQL: The Definitive Solution for Java and NoSQL Database [C...
Effective Java persistence queries with JPQL, Criteria API and QueryDSL
1. Java Persistence Queries
Effektive DB-Abfragen mit Features aus dem Standard
und darüber hinaus
Expertenkreis Java, 18.06.2015, GEDOPLAN
Dirk Weil, GEDOPLAN GmbH
2. Dirk Weil
GEDOPLAN GmbH, Bielefeld
Java EE seit 1998
Konzeption und
Realisierung
Seminare
Vorträge
Veröffentlichungen
Java Persistence Queries 2
3. Java Persistence
Mapping OO RDBMS
POJOs
Detachment macht DTOs verzichtbar
API zum Speichern, Laden, Löschen, Finden von DB-
Einträgen
Eclipselink, Hibernate, OpenJPA, …
Seit Java EE 5 im Standard
Auch in SE nutzbar
Aktuelle Version 2.1 3Java Persistence Queries
4. Demo-Entities
4Java Persistence Queries
@Entity
public class Publisher
{
@Id
private Integer id;
private String name;
@ManyToOne
private Country country;
@OneToMany(mappedBy = "publisher")
private List<Book> books;
@Entity
public class Book
{
@Id
private Integer id;
private String name;
private String isbn;
private int pages;
@ManyToOne
private Publisher publisher;
1
n
@Entity
public class Country
{
@Id
@Column(name = "ISO_CODE")
private String isoCode;
private String name;n
1
5. JPQL
Java Persistence Query Language
SQL-ähnlich, jedoch objektorientiert
EntityManager.createQuery liefert TypedQuery<T>
Ausführung mit getSingleResult bzw. getResultList
5Java Persistence Queries
Publisher publisher = entityManager
.createQuery("select p from Publisher p where p.name=?1",
Publisher.class)
.setParameter(1, "O'Melly Publishing")
.getSingleResult();
List<Book> books = entityManager
.createQuery("select b from Book b where b.pages>=500",
Book.class)
.getResultList();
6. JPQL
Navigation durch Relationen mit '.' und join
6Java Persistence Queries
List<Book> books = entityManager
.createQuery("select b from Book b where b.publisher.country=?1",
Book.class)
.setParameter(1, countryDE)
.getResultList();
List<Publisher> publishers = entityManager
.createQuery("select distinct p from Publisher p "
+ "join p.books b where b.pages>=500",
Publisher.class)
.getResultList();
7. JPQL
JPQL SQL "leichtgewichtig"
7Java Persistence Queries
List<Publisher> publishers = entityManager
.createQuery("select distinct p from Publisher p "
+ "join p.books b where b.pages>=500",
Publisher.class)
.getResultList();
SELECT DISTINCT t1.ID, t1.NAME, t1.COUNTRY_ISO_CODE
FROM JPA_BOOK t0, JPA_PUBLISHER t1
WHERE ((t0.PAGES >= ?) AND (t0.PUBLISHER_ID = t1.ID))
select distinct publisher0_.ID as ID1_35_,
publisher0_.COUNTRY_ISO_CODE as COUNTRY_3_35_,
publisher0_.name as name2_35_
from JPA_PUBLISHER publisher0_
inner join JPA_BOOK books1_ on publisher0_.ID=books1_.publisher_ID
where books1_.pages>=500
Eclipselink
Hibernate
8. Extended Queries
Selektion von Einzelattributen etc.
8Java Persistence Queries
List<Object[]> resultList = entityManager
.createQuery("select p.name, p.country.name from Publisher p",
Object[].class)
.getResultList();
List<Object[]> resultList = entityManager
.createQuery("select p, count(b) from Publisher p "
+ "left join p.books b "
+ "group by p",
Object[].class)
.getResultList();
9. Extended Queries
Selektion von Einzelattributen etc.
9Java Persistence Queries
List<NameAndCount> resultList = entityManager
.createQuery("select new somepkg.NameAndCount(p.name, sum(b.pages)) "
+ "from Publisher p "
+ "join p.books b "
+ "where b.name like '%Java%' "
+ "group by p",
NameAndCount.class)
.getResultList();
public class NameAndCount
{
private String name;
private Number count;
public NameAndCount(String name, Number count)
{
10. Native Queries
bei schon vorhandenen SQL-Queries
für "besondere Fälle" (proprietäres SQL, spezielles Tuning, …)
"Verlust" des O/R-Mappings
auch Komplettobjekte als Ergebnis möglich
10Java Persistence Queries
@SuppressWarnings("unchecked")
List<Object[]> resultList = entityManager
.createNativeQuery("SELECT DISTINCT p.ID, p.NAME, p.COUNTRY_ISO_CODE "
+ "FROM JPA_PUBLISHER p, JPA_BOOK b "
+ "WHERE b.PUBLISHER_ID = p.ID AND b.PAGES >= ?")
.setParameter(1, 500)
.getResultList();
11. Stored Procedure Queries
für "noch besonderere Fälle"
IN-, OUT- oder IN/OUT-Parameter
11Java Persistence Queries
CREATE PROCEDURE GET_COCKTAIL_COUNT(IN ZUTAT_NAME VARCHAR(255))
BEGIN select count(*) from JPA_COCKTAIL C
where exists (…);
END
Number count = (Number) entityManager
.createStoredProcedureQuery("GET_COCKTAIL_COUNT")
.registerStoredProcedureParameter("ZUTAT_NAME",
String.class, ParameterMode.IN)
.setParameter("ZUTAT_NAME", "Sekt")
.getSingleResult();
12. Criteria Query API
Textbasierte Queries lassen sich zur Compile- oder
Deploymentzeit nicht prüfen
Syntax
Namen
Typen
12Java Persistence Queries
select p fron Publisher p
select p from Publisher
select p from Publisher p where p.nam=:name
List<Publisher> books = entityManager
.createQuery("select b from Book b where b.pages>=500",
Publisher.class)
.getResultList();
13. Criteria Query API
Query wird mittels API kombiniert
CriteriaQuery<T> mittels CriteriaBuilder erstellen
from, where, select …
Ausführung als "normale" TypedQuery<T>
13Java Persistence Queries
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Book> cq = cb.createQuery(Book.class);
List<Book> books = this.entityManager
.createQuery(cq)
.getResultList();
14. Criteria Query API
14Java Persistence Queries
// select b from Book b where b.pages>=500
Root<Book> b = cq.from(Book.class);
cq.select(b)
.where(cb.greaterThanOrEqualTo(b.get(Book_.pages), 500));
// select b from Book b where b.publisher.country=:country
Root<Book> b = cq.from(Book.class);
cq.select(b)
.where(cb.equal(b.get(Book_.publisher).get(Publisher_.country),
cb.parameter(Country.class, "country")));
List<Book> books = this.entityManager
.createQuery(cq)
.setParameter("country", CountryTest.testCountryDE)
.getResultList();
15. Criteria Query API
nutzt statisches Metamodell
E_ zu persistenter Klasse E
15Java Persistence Queries
@StaticMetamodel(Publisher.class)
public abstract class Publisher_
{
public static volatile SingularAttribute<GeneratedIntegerIdEntity, Integer> id;
public static volatile SingularAttribute<Publisher, String> name;
public static volatile SingularAttribute<Publisher, Country> country;
public static volatile ListAttribute<Publisher, Book> books;
@Entity
public class Publisher
{
@Id
private Integer id;
private String name;
@ManyToOne
private Country country;
@OneToMany(mappedBy = "publisher")
private List<Book> books;
16. Criteria Query API
Metamodell wird durch Annotation Processor generiert
muss im Compile Classpath liegen (z. B. als Maven
Dependency)
wird vom Compiler aufgerufen (Java 6+)
16Java Persistence Queries
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-jpamodelgen</artifactId>
<version>4.3.10.Final</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>
17. Criteria Query API
Annotation Processing in Eclipse
kann in Luna entsprechend Maven konfiguriert werden
sonst: Im Projekt Annotation Processing manuell
konfigurieren
Java Compiler
Annotation Processing aktivieren, Zielordner wählen
Factory Path Compile Classpath
einstellen 17Java Persistence Queries
18. Root<Publisher> p = cq.from(Publisher.class);
ListJoin<Publisher, Book> b = p.join(Publisher_.books);
cq.select(cb.construct(NameAndCount.class,
p.get(Publisher_.name),
cb.sum(b.get(Book_.pages))))
.where(cb.like(b.get(Book_.name), "%Java%"))
.groupBy(p);
Criteria Query API
Problem: Lesbarkeit
18Java Persistence Queries
// select new NameAndCount(p.name, sum(b.pages))
// from Publisher p
// join p.books b
// where b.name like '%Java%'
// group by p
19. Root<Publisher> p = cq.from(Publisher.class);
ListJoin<Publisher, Book> b = p.join(Publisher_.books);
cq.select(p.get(Publisher_.name))
.distinct(true)
.where(cb.and(cb.equal(p.get(Publisher_.country),
cb.parameter(Country.class, "country")),
cb.like(b.get(Book_.name), "%Java%")));
Criteria Query API
Problem: API durch CriteriaBuilder (u. a.) nicht "fluent"
19Java Persistence Queries
p ~ Publisher;
b ~ p.books;
c ~ Parameter(Country);
select(p.name)
.distinct()
.from(p)
.where(p.country.equal(c).and(b.name.like("%Java%")))
Wunsch
Ist
20. QueryDSL
Open Source (http://www.querydsl.com)
Sponsored by Mysema (http://www.mysema.com)
Query wird mittels API kombiniert
Query Roots und JPAQuery für EntityManager erzeugen
Methoden from, join, where, orderBy, distinct etc.
Query ausführen mittels singleResult, list
20Java Persistence Queries
QPublisher p = QPublisher.publisher;
JPAQuery jpaQuery = new JPAQuery(this.entityManager)
List<Publisher> result = jpaQuery.list(p);
21. QueryDSL
benötigt generierte Klassen
ähnlich JPA Metamodell
21Java Persistence Queries
@Generated("com.mysema.query.codegen.EntitySerializer")
public class QPublisher extends EntityPathBase<Publisher> {
public static final QPublisher publisher = new QPublisher("publisher");
public final StringPath name = createString("name");
public final ListPath<Book, QBook> books = this.<Book, QBook>createList("books", …
public final de.gedoplan.seminar.jpa.demo.basics.entity.QCountry country;
@Entity
public class Publisher
{
@Id
private Integer id;
private String name;
@ManyToOne
private Country country;
@OneToMany(mappedBy = "publisher")
private List<Book> books;
22. QueryDSL
Metamodell wird durch Annotation Processor generiert
z. B. mit Maven Plugin
22Java Persistence Queries
<plugin>
<groupId>com.mysema.maven</groupId>
<artifactId>apt-maven-plugin</artifactId>
<version>1.1.3</version>
<executions>
<execution>
<goals><goal>process</goal></goals>
<configuration>
<outputDirectory>target/generated-sources/annotations</outputDirectory>
<processor>com.mysema.query.apt.jpa.JPAAnnotationProcessor</processor>
</configuration>
</execution>
</executions>
</plugin>
23. QueryDSL
23Java Persistence Queries
QPublisher p = QPublisher.publisher;
QBook b = QBook.book;
Param<Country> countryParam = new Param<>(Country.class);
List<String> names = new JPAQuery(this.entityManager)
.from(p)
.innerJoin(p.books, b)
.where(p.country.eq(countryParam).and(b.name.like("%Java%")))
.distinct()
.set(countryParam, CountryTest.testCountryDE)
.list(p.name);
Root<Publisher> p = cq.from(Publisher.class);
ListJoin<Publisher, Book> b = p.join(Publisher_.books);
cq.select(p.get(Publisher_.name))
.distinct(true)
.where(cb.and(cb.equal(p.get(Publisher_.country),
cb.parameter(Country.class, "country")),
cb.like(b.get(Book_.name), "%Java%")));
QueryDSL
Criteri
a
Query
API
24. QueryDSL
Bisherige Erfahrungen
Intuitives API (meist …)
gute Lesbarkeit
API hat noch Schwächen
z. B. Umwandlung von JPAQuery nur in Query möglich, nicht in
TypedQuery
Noch nicht ganz ausgereift
einige Bugs (z. B. Constructor Result akzeptiert keine Aggregat-
Ausdrücke)
Dokumentation lückenhaft und fehlerhaft
Umstellung com.mysema.querydsl com.querydsl buggy
24Java Persistence Queries