1. Dirk Weil | GEDOPLAN GmbH
Speeding up Java Persistence
2. Dirk Weil | GEDOPLAN GmbH
Dirk Weil
•GEDOPLAN GmbH, Bielefeld
•Java EE seit 1998
•Konzeption und Realisierung
•Seminare
•Vorträge
•Veröffentlichungen
Speeding up Java Persistence
3. Dirk Weil | GEDOPLAN GmbH
Id-Generierung
•Entity-Klassen müssen Id haben
–PK in der DB
–Feld oder Property mit @Id
•Empfehlenswert: Technische Id
–Problem: Erzeugung eindeutiger Werte
@Entity
public class SomeEntity
{
@Id
private int id;
…
Speeding up Java Persistence
4. Dirk Weil | GEDOPLAN GmbH
Id-Generierung
•JPA-Feature: @GeneratedValue
–Nutzt DB-Sequenzen, Identity Columns oder Sequenz-Tabellen
•Probleme:
–Id erst nach persist gesetzt equals?, hashCode?
–Id-Übernahme kostet Zeit
@Id @GeneratedValue private int id;
Speeding up Java Persistence
5. Dirk Weil | GEDOPLAN GmbH
Id-Generierung
•Alternative: BYO-ID (selbst machen)
–Id auch in transitiven Objekten gesetzt
–Insert ohne Zusatzaufwand
–Achtung: i. A. nicht trivial
–Z. B.: UUID
this.id = UUID.randomUUID().toString(); // oder: new com.eaio.uuid.UUID().toString()
Speeding up Java Persistence
6. Dirk Weil | GEDOPLAN GmbH
Id-Generierung
•@GeneratedValue langsamer (OOTB)
Speeding up Java Persistence
8. Dirk Weil | GEDOPLAN GmbH
Id-Generierung
Speeding up Java Persistence
9. Dirk Weil | GEDOPLAN GmbH
Relationship Loading
•Relationen werden durch Attribute mit @…To… repräsentiert
@Entity public class Book { @ManyToOne public Publisher publisher;
@Entity
public class Publisher
{
@OneToMany(mappedBy="publisher")
public List<Book> books;
Speeding up Java Persistence
10. Dirk Weil | GEDOPLAN GmbH
Relationship Loading
•Relationen-Parameter: fetch
•Referenzierte Entities direkt laden?
–EAGER: Direkt
–LAZY: Später bei Bedarf
@ManyToOne(fetch = FetchType.LAZY) private Artikel artikel;
Speeding up Java Persistence
11. Dirk Weil | GEDOPLAN GmbH
Relationship Loading
•Bsp.: Auftragsposition bearbeiten
–n:1-Relation zu Artikel
Kunde
Auftrag
Auftrags Position
Artikel
Land
1
*
1
*
*
1
1
*
@Entity
public class AuftragsPosition
{
@ManyToOne
private Artikel artikel;
Speeding up Java Persistence
12. Dirk Weil | GEDOPLAN GmbH
Relationship Loading
•Annahme: Verwendet nur AuftragsPosition
AuftragsPosition aufPos = em.find(AuftragsPosition.class, id);
…
select … from AuftragsPosition where …
@ManyToOne
private Artikel artikel;
@ManyToOne(fetch=FetchType.LAZY)
private Artikel artikel;
select … from AuftragsPosition where … select … from Artikel where …
Speeding up Java Persistence
13. Dirk Weil | GEDOPLAN GmbH
Relationship Loading
•Annahme: Verwendet auch Artikel
AuftragsPosition aufPos = em.find(AuftragsPosition.class, id); …
Artikel artikel = aufPos.getArtikel();
…
@ManyToOne private Artikel artikel;
@ManyToOne(fetch=FetchType.LAZY)
private Artikel artikel;
select … from AuftragsPosition where … select … from Artikel where …
select from AuftragsPosition where … …
select … from Artikel where …
Speeding up Java Persistence
14. Dirk Weil | GEDOPLAN GmbH
Relationship Loading
•Messergebnis (1000 Items, Hibernate, MySQL)
EAGER
LAZY
AuftragsPosition ohne Artikel
2.967 ms
2.505 ms
- 15 %
AuftragsPosition mit Artikel
2.959 ms
4.305 ms
+ 45 %
Kunde ohne Auftrag
30.295 ms
4.848 ms
- 84 %
= Default
Kunde
Auftrag
Auftrags Position
Artikel
Land
1
*
1
*
*
1
1
*
Speeding up Java Persistence
15. Dirk Weil | GEDOPLAN GmbH
Relationship Loading
•Fazit:
–Zugriffsverhalten genau analysieren
–Default ist schon recht gut
–Besser: Immer LAZY verwenden und bei Bedarf Fetch Joins oder Entity Graphs nutzen
Speeding up Java Persistence
16. Dirk Weil | GEDOPLAN GmbH
Relationship Loading
•Fetch Joins
–mit JPQL
–Achtung: Kartesisches Produkt!
select ap from Auftragsposition ap left join fetch ap.artikel ...
Speeding up Java Persistence
17. Dirk Weil | GEDOPLAN GmbH
Relationship Loading
•Fetch Joins
–mit Criteria Query
CriteriaQuery<Auftrag> cQuery = builder.createQuery(Auftrag.class); Root<Auftrag> a = cQuery.from(Auftrag.class); a.fetch(Auftrag_.auftragsPositionen, JoinType.LEFT) .fetch(AuftragsPosition_.artikel, JoinType.LEFT); …
Speeding up Java Persistence
18. Dirk Weil | GEDOPLAN GmbH
Relationship Loading
•Entity Graphs
TBD
@Entity @NamedEntityGraph( name = "Kunde.auftraege", attributeNodes = @NamedAttributeNode(value = "auftraege"))) public class Kunde { @OneToMany(mappedBy="kunde") private Set<Auftrag> auftraege;
TypedQuery<Kunde> query = entityManager.createQuery(…);
EntityGraph<?> entityGraph = entityManager.getEntityGraph("Kunde.auftraege");
query.setHint("javax.persistence.loadgraph", entityGraph);
Speeding up Java Persistence
19. Dirk Weil | GEDOPLAN GmbH
Fetch Tuning
•ToMany-Fetching: "N+1"-Problem
–z.B. Lesen einiger User inkl. Groups + Permissions
SELECT ... FROM USER
SELECT ... FROM GROUP WHERE USER_ID=?
SELECT ... FROM PERMISSION WHERE GROUP_ID=?
SELECT ... FROM PERMISSION WHERE GROUP_ID=?
SELECT ... FROM GROUP WHERE USER_ID=?
SELECT ... FROM GROUP WHERE USER_ID=?
Speeding up Java Persistence
21. Dirk Weil | GEDOPLAN GmbH
Fetch Tuning
•Lösungsansatz 2: Batch Fetching
@ManyToMany(fetch = FetchType.LAZY, …)
@BatchFetch(value = BatchFetchType.IN, size = 10)
private Set<Group> groups;
Annotation
Relationsauflösung
EclipseLink
@BatchFetch
IN, EXISTS, JOIN
Hibernate
@BatchSize
IN, EXISTS
SELECT ... FROM USER
SELECT ... FROM GROUP WHERE USER_ID IN (?,?,…)
SELECT ... FROM PERMISSION WHERE GROUP_ID IN (?,?,…)
SELECT ... FROM PERMISSION WHERE GROUP_ID IN (?,?,…)
SELECT ... FROM GROUP WHERE USER_ID IN (?,?,…)
N+1 (N/B)+1
Speeding up Java Persistence
22. Dirk Weil | GEDOPLAN GmbH
Basic Attribute Loading
•Fetch-Strategie auch für einfache Werte wählbar
•Lazy Loading ggf. sinnvoll bei
–selten genutzten Werten
–umfangreichen Daten
@Basic(fetch = FetchType.LAZY)
private String longAdditionalInfo;
Speeding up Java Persistence
23. Dirk Weil | GEDOPLAN GmbH
Basic Attribute Loading
•Messergebnis
–Lesen von Kunden
–10 'ungenutzte' Strings à 150 chars
–1000 Interationen, Hibernate, MySQL
EAGER
LAZY
6.782 ms
6.440 ms
-5 %
= Default-Einstellung
Speeding up Java Persistence
24. Dirk Weil | GEDOPLAN GmbH
Tupel-Selects
•Selektion einzelner Attribute
–als Tupel
–mit Constructor Expression
•Messergebnis (ms für 10000 Items)
select k.id, k.name from Kunde k
select new IdAndName(k.id, k.name) from Kunde k
Entity komplett
Object[]
Ctor Expression
11.211 ms
3.890 ms
4.010 ms
Speeding up Java Persistence
25. Dirk Weil | GEDOPLAN GmbH
Query-Parameter
–JPQL Injection
–Prepared Statement Reuse
em.createQuery("select k from Kunde k " + "where k.name='" + someString + "'")
em.createQuery("select k from Kunde k " + "where k.name=:name")
.setParameter("name", someString)
Speeding up Java Persistence
26. Dirk Weil | GEDOPLAN GmbH
Caching
JPA Provider
EntityManager
DB
2nd Level Cache
1st Level Cache
Query Cache
Speeding up Java Persistence
27. Dirk Weil | GEDOPLAN GmbH
First Level Cache
•Standard
•Je EntityManager
•Enthält in Sitzung geladene Objekte
–Achtung: Speicherbedarf!
–sinnvolle EM-Lebensdauer nutzen
–flush/clear ist i. A. Antipattern!
•ausser im Batch – dort häufig sinnvoll
EntityManager
1st Level Cache
Speeding up Java Persistence
28. Dirk Weil | GEDOPLAN GmbH
First Level Cache
•Arbeitet sitzungs- bezogen
// Kunden mit bestimmter Id laden
EntityManager em1 = emf.createEntityManager();
Kunde k1 = em1.find(Kunde.class, id);
// Gleichen Kunden in 2. Session verändern
EntityManager em2 = emf.createEntityManager();
em2.getTransaction().begin();
Kunde k2 = em2.find(Kunde.class, id);
k2.setName("…");
em2.getTransaction().commit();
// Gleichen Kunden in 1. Session erneut laden
Kunde k3 = em1.find(Kunde.class, id);
// ist unverändert!
Speeding up Java Persistence
29. Dirk Weil | GEDOPLAN GmbH
First Level Cache
•HashMap-Semantik
–benötigt Key
–wird für Queries nicht benutzt
// Kunden mit bestimmter Id laden EntityManager em = emf.createEntityManager(); Kunde k1 = em.find(Kunde.class, id); // Query nach gleichem Kunden geht erneut zur DB! Kunde k2 = em.createQuery("select k from Kunde k " + "where k.id=:id", Kunde.class) .setParameter("id", id) .getSingleResult();
Speeding up Java Persistence
30. Dirk Weil | GEDOPLAN GmbH
Query Cache
•Provider-spezifisch
•Speichert Result Set IDs zu Queries
TypedQuery<Kunde> query = em.createQuery("select k from Kunde k where k.name=:name", Kunde.class); query.setParameter("name", "OPQ GbR"); … // Query Cache einschalten Kunde kunde = query.getSingleResult();
["select k from Kunde k where k.name=:name", "OPQ GbR"] [id1]
Speeding up Java Persistence
31. Dirk Weil | GEDOPLAN GmbH
Query Cache
•Trotz mehrfacher Query nur ein DB-Zugriff
while (…) { TypedQuery<Kunde> query = em.createQuery("select k from Kunde k where k.name=:name", Kunde.class); query.setParameter("name", "OPQ GbR"); query.setHint(…) // Query Cache einschalten (providerabh.!) Kunde kunde = query.getSingleResult(); … }
Speeding up Java Persistence
33. Dirk Weil | GEDOPLAN GmbH
Second Level Cache
•JPA 2.x unterstützt 2nd Level Cache
–nur rudimentäre Konfiguration
–Providerspezifische Konfiguration in der Praxis unabdingbar
JPA Provider
EntityManager
2nd Level Cache
1st Level Cache
Speeding up Java Persistence
34. Dirk Weil | GEDOPLAN GmbH
Second Level Cache
•Providerspezifische Implementierung
–Cache-Provider Infinispan, EHCache, OSCache, …
–Cache-Strategien read-only, read-write, …
–Storage Memory, Disk, Cluster, …
Speeding up Java Persistence
35. Dirk Weil | GEDOPLAN GmbH
Second Level Cache
•Wirkt applikationsweit
•Semantik ähnlich HashMap
•Ladereihenfolge:
–1st Level Cache (EntityManager)
–2nd Level Cache, falls enabled
–DB
Speeding up Java Persistence
36. Dirk Weil | GEDOPLAN GmbH
Second Level Cache
•Vorteil bei häufig genutzten Daten
–Konstanten
–selten veränderte Daten
–nur von dieser Anwendung veränderte Daten
Speeding up Java Persistence
37. Dirk Weil | GEDOPLAN GmbH
Second Level Cache
•Bsp.: Stammdaten-Entity Land
–wird n:1 von Kunde referenziert
–nur wenige Land-Werte
–Länder ändern sich nahezu nie
–Länder können dauerhaft im Cache verbleiben
Kunde
Land
1
*
Speeding up Java Persistence
38. Dirk Weil | GEDOPLAN GmbH
Second Level Cache
•Konfiguration lt. Spec
<persistence-unit name="…">
<provider>…</provider>
<shared-cache-mode>ENABLE_SELECTIVE</shared-cache-mode>
…
Cache aktiv für …
Default bei …
ALL
alle Entities
NONE
keine Klasse
Hibernate
ENABLE_SELECTIVE
nur @Cacheable(true)
DISABLE_SELECTIVE
alle außer @Cacheable(false)
EclipseLink
@Entity
@Cacheable(true)
public class Land
{
…
Speeding up Java Persistence
39. Dirk Weil | GEDOPLAN GmbH
Second Level Cache
•Messergebnis (1000 Interationen, Hibernate, MySQL) ohne L2C: 8.975 ms mit L2C für Land: 5.401 ms
Speeding up Java Persistence
40. Dirk Weil | GEDOPLAN GmbH
Second Level Cache
•L2C für Anwendungscode transparent
•find liefert Kopie des Cache-Eintrags
•Für komplett konstante Werte kann ein weiterer Cache in der Anwendung sinnvoll sein
Speeding up Java Persistence
41. Dirk Weil | GEDOPLAN GmbH
Paginierung
•Queries mit großer Ergebnismenge 'häppchenweise' verarbeiten
TypedQuery<Artikel> query = em.createQuery("select a from Artikel a", Artikel.class); query.setFirstResult(50); query.setMaxResults(10); List<Artikel> result = query.getResultList();
select … from Artikel where … and rownum>=50 and rownum<60
Speeding up Java Persistence
42. Dirk Weil | GEDOPLAN GmbH
Paginierung
•Eingeschränkt oder effektlos bei 1:n/m:n-Relationen mit:
–Eager Loading
–Fetch Joins
•Join erzeugt kartesisches Produkt
•Providerabhängige Lösung:
–Ausführung im Memory
–Ausführung mehrerer SQL-Befehle
Speeding up Java Persistence
43. Dirk Weil | GEDOPLAN GmbH
Inheritance
•Mehrere Abbildungen denkbar:
–Alles in einer Tabelle
–Eine Tabelle pro Klasse
–Eine Tabelle pro konkreter Klasse
•Strategie-Auswahl mit @Inheritance
<abstract> Vehicle
Car
Ship
Speeding up Java Persistence
44. Dirk Weil | GEDOPLAN GmbH
Inheritance
•SINGLE_TABLE
@Entity
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
public abstract class Vehicle
{
…
Speeding up Java Persistence
45. Dirk Weil | GEDOPLAN GmbH
Inheritance
•JOINED
@Entity
@Inheritance(strategy=InheritanceType.JOINED)
public abstract class Vehicle
{
…
Speeding up Java Persistence
46. Dirk Weil | GEDOPLAN GmbH
Inheritance
•TABLE_PER_CLASS
@Entity
@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
public abstract class Vehicle
{
…
Speeding up Java Persistence
47. Dirk Weil | GEDOPLAN GmbH
Inheritance
•Laufzeitvergleich für Queries
–auf Basisklasse
–auf abgeleitete Klasse
•(1000 Iterationen, Ergebnis ca. 100 Einträge, Hibernate, MySQL)
SINGLE_ TABLE
TABLE_ PER_CLASS
JOINED
Basisklasse
2.705 ms
29.359 ms
3.434 ms
Subklasse
2.505 ms
1.435 ms
3.377 ms
Speeding up Java Persistence
48. Dirk Weil | GEDOPLAN GmbH
Inheritance
•Optimale Performanz liefern SINGLE_TABLE und TABLE_PER_CLASS
•Aber: Auch andere Implikationen
•Genaue Analyse notwendig
Speeding up Java Persistence
49. Dirk Weil | GEDOPLAN GmbH
Wenn‘s dennoch nicht reicht
•Native Queries
•Stored Procedure Queries
•User Functions
Speeding up Java Persistence