Speeding up Java Persistence

51
Speeding up Java Persistence Dirk Weil, GEDOPLAN GmbH

Transcript of Speeding up Java Persistence

Page 1: Speeding up Java Persistence

Speeding up Java Persistence

Dirk Weil, GEDOPLAN GmbH

Page 2: Speeding up Java Persistence

Dirk Weil

GEDOPLAN GmbH, Bielefeld

Java EE seit 1998

Konzeption und

Realisierung

Seminare

Vorträge

Veröffentlichungen

Speeding up Java Persistence 2

Page 3: Speeding up Java Persistence

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 3

Page 4: Speeding up Java Persistence

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 4

Page 5: Speeding up Java Persistence

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 5

Page 6: Speeding up Java Persistence

Id-Generierung

@GeneratedValue langsamer

(OOTB)

Speeding up Java Persistence 6

Page 7: Speeding up Java Persistence

Id-Generierung

Tuning: Höhere Allocation Size

Leider nicht verfügbar bei IDENTITY

@Id

@GeneratedValue(strategy = GenerationType.SEQUENCE,

generator = "ArtikelIdGenerator")

@SequenceGenerator(name = "ArtikelIdGenerator",

allocationSize = 1000)

private int id; @Id

@GeneratedValue(strategy = GenerationType.TABLE,

generator = "ArtikelIdGenerator")

@TableGenerator(name = "ArtikelIdGenerator",

allocationSize = 1000)

private int id;

Speeding up Java Persistence 7

Page 8: Speeding up Java Persistence

Id-Generierung

Speeding up Java Persistence 8

Page 9: Speeding up Java Persistence

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 9

Page 10: Speeding up Java Persistence

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 10

Page 11: Speeding up Java Persistence

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 11

Page 12: Speeding up Java Persistence

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 12

Page 13: Speeding up Java Persistence

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 13

Page 14: Speeding up Java Persistence

Relationship Loading

Messergebnis (1000 Items, Hibernate, MySQL)

14 Speeding up Java Persistence

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 %

= D

efault

Kunde Auftrag

Auftrags

Position Artikel Land

1 *

1

*

*

1

1 *

Page 15: Speeding up Java Persistence

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 15

Page 16: Speeding up Java Persistence

Relationship Loading

Fetch Joins

mit JPQL

Achtung: Kartesisches Produkt!

select ap from Auftragsposition ap

left join fetch ap.artikel

...

Speeding up Java Persistence 16

Page 17: Speeding up Java Persistence

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 17

Page 18: Speeding up Java Persistence

Relationship Loading

Entity Graphs

@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 18

Page 19: Speeding up Java Persistence

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 19

Page 20: Speeding up Java Persistence

Fetch Tuning

Lösungsansatz 1: Join Fetching

erzeugt 1 (!) SELECT

Achtung: Volumen!

CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); CriteriaQuery<User> criteriaQuery = criteriaBuilder.createQuery(User.class); Root<User> u = criteriaQuery.from(User.class); u.fetch(User_.groups, JoinType.LEFT).fetch(Group_.permissions, JoinType.LEFT); criteriaQuery.select(u).distinct(true); List<User> users = entityManager.createQuery(criteriaQuery).getResultList();

Speeding up Java Persistence 20

Page 21: Speeding up Java Persistence

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 21

Page 22: Speeding up Java Persistence

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 22

Page 23: Speeding up Java Persistence

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 23

Page 24: Speeding up Java Persistence

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 24

Page 25: Speeding up Java Persistence

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 25

Page 26: Speeding up Java Persistence

Caching

JPA Provider EntityManager

DB

2nd

Level

Cache

1st

Level

Cache

Query

Cache

Speeding up Java Persistence 26

Page 27: Speeding up Java Persistence

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 27

Page 28: Speeding up Java Persistence

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 28

Page 29: Speeding up Java Persistence

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 29

Page 30: Speeding up Java Persistence

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 30

Page 31: Speeding up Java Persistence

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 31

Page 32: Speeding up Java Persistence

Query Cache

EclipseLink:

Hibernate:

TypedQuery<Kunde> query = em.createQuery(…);

query.setHint("org.hibernate.cacheable", true);

TypedQuery<Kunde> query = em.createQuery(…);

query.setHint("eclipselink.cache-usage",

"CheckCacheThenDatabase");

Speeding up Java Persistence 32

Page 33: Speeding up Java Persistence

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 33

Page 34: Speeding up Java Persistence

Second Level Cache

Providerspezifische Implementierung

Cache-Provider

Infinispan, EHCache, OSCache, …

Cache-Strategien

read-only, read-write, …

Storage

Memory, Disk, Cluster, …

Speeding up Java Persistence 34

Page 35: Speeding up Java Persistence

Second Level Cache

Wirkt applikationsweit

Semantik ähnlich HashMap

Ladereihenfolge:

1st Level Cache (EntityManager)

2nd Level Cache, falls enabled

DB

Speeding up Java Persistence 35

Page 36: Speeding up Java Persistence

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 36

Page 37: Speeding up Java Persistence

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 37

Page 38: Speeding up Java Persistence

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 38

Page 39: Speeding up Java Persistence

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 39

Page 40: Speeding up Java Persistence

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 40

Page 41: Speeding up Java Persistence

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 41

Page 42: Speeding up Java Persistence

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 42

Page 43: Speeding up Java Persistence

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 43

Page 44: Speeding up Java Persistence

Inheritance

SINGLE_TABLE

@Entity

@Inheritance(strategy=InheritanceType.SINGLE_TABLE)

public abstract class Vehicle

{

Speeding up Java Persistence 44

Page 45: Speeding up Java Persistence

Inheritance

JOINED

@Entity

@Inheritance(strategy=InheritanceType.JOINED)

public abstract class Vehicle

{

Speeding up Java Persistence 45

Page 46: Speeding up Java Persistence

Inheritance

TABLE_PER_CLASS

@Entity

@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)

public abstract class Vehicle

{

Speeding up Java Persistence 46

Page 47: Speeding up Java Persistence

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 47

Page 48: Speeding up Java Persistence

Inheritance

Optimale Performanz liefern

SINGLE_TABLE und TABLE_PER_CLASS

Aber: Auch andere Implikationen

Genaue Analyse notwendig

Speeding up Java Persistence 48

Page 49: Speeding up Java Persistence

Wenn‘s dennoch nicht reicht

Native Queries

Stored Procedure Queries

User Functions

Speeding up Java Persistence 49

Page 50: Speeding up Java Persistence

Fazit

Viele Optimierungen providerunabhängig möglich

Wesentlich:

Lazy Loading

Caching

Genaue Analyse notwendig

Messen

Kein Selbstzweck

Speeding up Java Persistence 50

Page 51: Speeding up Java Persistence

More

http://www.gedoplan-it-training.de

Seminare in Berlin, Bielefeld, Inhouse

http://www.gedoplan-it-consulting.de

Reviews, Coaching, …

http://javaeeblog.wordpress.com/

http://expertenkreisjava.blogspot.de/

[email protected]

@dirkweil

Speeding up Java Persistence 51