Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
848 lines (563 sloc) 19.2 KB

JPA/Hibernate camp

1. O mnie

  • Architect Solution - RiscoSoftware

  • VavaTech trener : Spring ekosystem, JPA , EIP Camel

  • Sages trener : JPA , EIP - Apache Camel

  • blog http://przewidywalna-java.blogspot.com

  • twitter przodownikR1

2. Źródła wiedzy

  • Hibernate in Action

  • Java Persistence with Hibernate

  • Java JEE 6

  • Pro JPA 2

  • Pro JPA 2: Mastering the Java™ Persistence API (Expert’s Voice in Java Technology)

  • Hibernate from Novice to Professional

  • Spring Data Modern Data Access for Enterprise Java

  • Spring Data

  • Spring Boot

  • Spring Essentials

  • Spring in Action

  • etc

3. Hibernate / JPA

4. Wydajność (Performance)

Performance

5. Sposoby pobierania rekorów / Fetching

5.1. JOIN

  • Tworzymy join’a w obrębie instrukcji SELECT

5.2. SELECT

  • Tworzony jest dodatkowy SELECT oprócz bazowego zapytania SELECT

    • N+1 problem jest możliwy

    • Lazy

5.3. SUBSELECT

  • Tworzony jest dodatkowy SELECT w obrębie już istniejącego polecanie SELECT w celu dociągnięcia kolekcji.

    • możliwy dla relacji *-to-Many

    • Tylko dla kolekcji

5.4. BATCH

  • Tworzone dodatkowe polecenia SELECT w obrębie w celu przyspieszenia dociągania części kolekcji

6. Lazy

  • leniwe ładowanie to jeden z features Hibernate. Pozwala ładować grafy obiektów w momencie faktycznej potrzeby skorzystania z tych danych.

  • w wielu wypadkach zapewnia znaczny wzrost wydajności poprzez ograniczenie niepotrzebnych strzałów do bazy

  • zmniejsza czas odpowiedzi

  • zapobiega zbyt dużemu zużyciu pamięci

  • problem LazyLoadingException

6.1. Sposoby inicjalizacji :

6.1.1. Fizyczne 'dotknięcie' kolekcji

  • Przykład

Person person = this.em.find(Person.class, id);
person.getPhones().size();

6.1.2. Fetch Join

Caution
Cartesian problem : Nie możemy używać join fetch dla wielu kolekcji ponieważ tworzymy kartezjana !! Złączenie będzie zawierało wszystkie możliwe kombinacje.
  • Przykład

Query q = this.em.createQuery("SELECT p FROM Person p JOIN FETCH p.phones ph WHERE p.id = :id");
q.setParameter("id", id);
person = (Person) q.getSingleResult();

6.1.3. Fetch Join z Criteria

  • Przykład

CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery q = cb.createQuery(Person.class);
Root p = q.from(Person.class);
p.fetch("phones", JoinType.INNER);
q.select(p);
q.where(cb.equal(p.get("id"), personId));

Person person = (Person)this.em.createQuery(q).getSingleResult();

6.1.4. Named Entity Graph

  • Przykład

@Entity
@NamedEntityGraph(name = "graph.Person.phones",
      attributeNodes = @NamedAttributeNode("phones"))
public class Person implements Serializable {
  • Przykład

EntityGraph graph = this.em.getEntityGraph("graph.Person.phones");

Map hints = new HashMap();
hints.put("javax.persistence.fetchgraph", graph);

Person person = this.em.find(Person.class, personId, hints);

6.1.5. Dynamiczny graf

  • Przykład

EntityGraph graph = this.em.createEntityGraph(Person.class);
Subgraph phonesGraph = graph.addSubgraph("phones");

Map hints = new HashMap();
hints.put("javax.persistence.loadgraph", graph);

Person person = this.em.find(Person.class, personId, hints);

6.2. less lazy loading

  • Przykład

@ManyToMany
@Fetch(FetchMode.JOIN)
public Set<ArtEntity> getArtEntities() {
return artEntities;
}

6.3. Batching for Performance

  • Przykład

@ManyToMany
@BatchSize(size = 10)
public Set<ArtEntity> getArtEntities() {
return artEntities;
}

6.4. OpenInView

  • Pojedyńcza instancja EntityManagera na HttpRequest

  • Tworzy nową transakcję na początku każdego HttpRequest’u

  • Komituje lub Rollback’uje transakcję na końcu każdego requestu

Caution
Lock transakcji na połączenie bazodanowe. Opóźniona reakcja bazodanowa dopóki nie zostanie wyredendowany widok.
Caution
File upload requests
  • Przykład

<bean name="openEntityManagerInViewInterceptor" class="org.springframework.orm.jpa.support.OpenEntityManagerInViewInterceptor" />
  • Przykład

<filter>
<filter-name>OpenEntityManagerInViewFilter</filter-name>
<filter-class>org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter</filter-
class>
</filter>
<!—Map the EntityManager Filter to all requests -->
<filter-mapping>
<filter-name>OpenEntityManagerInViewFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
  • Analiza logów Hibernate - poprawa wolnych zapytań

    • Analiza statystyk

      • wolne zapytania

      • zbyt wiele zapytań n+1 problem

      • trafienia w cache

  • Wybór strategii pobierania danych : fetchType

6.5. FETCH JOIN

  • Przykład

  SELECT DISTINCT a FROM Author a JOIN FETCH a.books b

@NamedEntityGraph

@NamedEntityGraph(name = "graph.AuthorBooksReviews",  attributeNodes = @NamedAttributeNode(value = "books"))
  • Ciężkie zapytania np raporty powinny być wykonywane po stronie bazy

@NamedStoredProcedureQuery

  • Użyj cache

    • pierwszy poziom cache aktywowany jest domyślnie

    • drugi poziom cache jest włączany świadomie przez programistę. Konfigurowany dla klasy i kolekcji

    • buforowanie zapytań. Jest użyteczny podczas częstego wykonywania zapytań z takimi samymi parametrami.

  • Masowe operacje UPDATE i DELETE CriteriaUpdate and CriteriaDelete

    • Przykład

CriteriaBuilder cb = this.em.getCriteriaBuilder();

// create update
CriteriaUpdate<Order> update = cb.createCriteriaUpdate(Order.class);

// set the root class
Root e = update.from(Order.class);

// set update and where clause
update.set("amount", newAmount);
update.where(cb.greaterThanOrEqualTo(e.get("amount"), oldAmount));

// perform update
this.em.createQuery(update).executeUpdate();
  • Strategie pobierania (Fetching Strategies)

7. FETCH

7.1. Eager

  • natychmiatowe

  • czasem wygodne do użycia

  • znaczący przyrost danych pobieranych z bazy

  • sekwencyjne odczyty z bazy lub bufora danych

  • FetchType.EAGER - domyślne dla @OneToOne i @ManyToOne

7.2. Lazy

  • opóźniony/odroczony dostęp do danych

  • proxy

  • tworzy nowe zapytanie do bazy danych jeśli obiekt nie istnieje w buforze

  • FetchType.LAZY - domyślne dla @OneToMany i @ManyToMany

  • jest możliwe jedynie, gdy podstawowa encja jest w stanie managed

  • pobranie encji w stanie Detached spowoduje wyrzucenie wyjątku - LazyInitializationException

7.2.1. Zapobieganie LazyInitializationException

  • ponowne utrwalenie encji

  • pobieranie przy pomocy Fetch JOIN

  • wybór Eager zamiast Lazy

  • openSessionInView pattern

  • EntityGraph

  • isInitialized() - sprawdzamy czy pośrednik jest zaincjalizowany

  • initialize() - programowe wymuszenie inicjalizacji

7.3. Fetch Join

  • obiekt czy kolekcja zostaje pobrana razem z obiektem głównym przez zastosowanie złączenia JOIN FETCH

7.3.1. INNER JOIN FETCH** - dla pobrania pojedynczych obiektów

7.3.2. LEFT JOIN FETCH** – dla pobrania kolekcji

7.4. Batch

  • poprawa wydajności dla strategii lazy poprzez pobranie grupy obiektów. To samo dotyczy się poprawy strategii eager.

Note
To tak naprawdę nie strategia a wskazówka mająca na celu zwiększenia wydajności innych strategii jak : lazy czy eager. To dobra strategia dla mniej doświadczonych developerów , którzy chcą osiągnąć zadowalającą wydajność bez potrzeby wnikliwej analizy kodu SQL.

7.5. Extra lazy

  • tylko dla kolekcji

  • nie dociąga całej kolekcji

  • @LazyCollection(LazyCollectionOption.EXTRA)

  • niektóre operacje jak : size(), contains(), get(), etc. nie odpalają pełnej inicjalizacji kolekcji

7.5.1. EXTRA

  • .size() , .contains() etc nie inicjalizują pełnej kolekcji

7.5.2. TRUE

  • inicjalizacja pełnej kolekcji przy pierwszym odwołaniem do niej

7.5.3. FALSE

  • Eager loading

7.6. Określanie głębi wczytywanch obiektów

Sterowanie max liczbą złączonach tabel w jednym zapytania SQL.
Note
parametr odpowiedzialny za to ustawienie to : hibernate.max_fetch_depth
List<Author> authors = this.em.createQuery(
        "SELECT DISTINCT a FROM Author a JOIN FETCH a.books b",
        Author.class).getResultList();

+ Relationships gets loaded in same query - Requires a special query for each use case - Creates cartesian product

  • @NamedEntityGraph Declaratively defines a graph of entities which will be loaded

@NamedEntityGraph(
    name = "graph.AuthorBooksReviews",

    attributeNodes =
@NamedAttributeNode(value = "books")
)

8. Kartezjan problem

  • Omówienie

  • Przykład

  @Entity
  public class Person extends AbstractEntity{

    private static final long serialVersionUID = -4106601879598237198L;
    private String firstName = null;
    private String lastName = null;

    @OneToMany(cascade = CascadeType.ALL,fetch=FetchType.EAGER)
  //  @Fetch(FetchMode.SELECT)
    @JoinColumn(name="PERSON_ID")
    private List<Address> addresses;


    @OneToMany(cascade = CascadeType.ALL,fetch=FetchType.EAGER)
  //  @Fetch(FetchMode.SELECT)
    @JoinColumn(name="PERSON_ID")
    private List<Phone> phones;

}
 select
        person0_.id as id1_1_0_,
        person0_.version as version2_1_0_,
        person0_.firstName as firstNam3_1_0_,
        person0_.lastName as lastName4_1_0_,
        addresses1_.PERSON_ID as PERSON_I4_0_1_,
        addresses1_.id as id1_0_1_,
        addresses1_.id as id1_0_2_,
        addresses1_.version as version2_0_2_,
        addresses1_.CITY as CITY3_0_2_,
        phones2_.PERSON_ID as PERSON_I4_2_3_,
        phones2_.id as id1_2_3_,
        phones2_.id as id1_2_4_,
        phones2_.version as version2_2_4_,
        phones2_.phoneNumber as phoneNum3_2_4_
    from
        Person person0_
    left outer join
        Address addresses1_
            on person0_.id=addresses1_.PERSON_ID
    left outer join
        Phone phones2_
            on person0_.id=phones2_.PERSON_ID
    where
        person0_.id=?

9. Kroki optymalizacji

9.1. Dziennik zdarzeń

  • trafienia w bufor

  • koszty złączenia czy może dwa osobne selecty

  • czas wykonywania zapytań

9.2. Analiza przypadków użycia

  • próby wykrycia problemu n+1

  • analiza wywołań zapytań w celu zmniejszenia liczby i złożoności dla danej akcji biznesowej

9.3. Dostrajanie parametrów

  • hibernate.max_fetch_depth

  • hibernate batch fetch

  • dobór najlepszego stylu kaskadowego dla każdej relacji w celu zmniejszenia wywołania liczby transakcji i zapytań do bazy poprzez zarządce transakcji

Tip
org.hibernate.SQL → DEBUG, org.hibernate.type → TRACE
Tip
Patrz rodział : Jak pokazać parametryzacje zapytań SQL ?

9.4. Gradle

  • Przykład

ext {
    hibernateVersion = 'hibernate-version-you-want'
}

buildscript {
    dependencies {
        classpath "org.hibernate:hibernate-gradle-plugin:$hibernateVersion"
    }
}

hibernate {
    enhance {
        enableLazyInitialization= false
        enableDirtyTracking = false
        enableAssociationManagement = false
    }
}
}

9.5. Maven

  • Przykład

<build>
    <plugins>
        [...]
        <plugin>
            <groupId>org.hibernate.orm.tooling</groupId>
            <artifactId>hibernate-enhance-maven-plugin</artifactId>
            <version>$currentHibernateVersion</version>
            <executions>
                <execution>
                    <configuration>
                        <failOnError>true</failOnError>
                        <enableLazyInitialization>true</enableLazyInitialization>
                        <enableDirtyTracking>true</enableDirtyTracking>
                        <enableAssociationManagement>true</enableAssociationManagement>
                    </configuration>
                    <goals>
                        <goal>enhance</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
        [...]
    </plugins>
</build>

10. readOnly

source : java persistence with hibernate

  • Przykład

em.unwrap(Session.class).setDefaultReadOnly(true);
Item item = em.find(Item.class, ITEM_ID);
item.setName("New Name");
em.flush(); // no update
  • Przykład

Item item = em.find(Item.class, ITEM_ID);
em.unwrap(Session.class).setReadOnly(item, true);
item.setName("New Name");
em.flush() //no update
  • Przykład

org.hibernate.Query query = em.unwrap(Session.class).createQuery("select i from Item i");
query.setReadOnly(true).list();
List<Item> result = query.list();
for (Item item : result)
  item.setName("New Name");
em.flush(); // no update
  • Przykład

Query query = em.createQuery(queryString).setHint(org.hibernate.annotations.QueryHints.READ_ONLY,true );

11. Batch processing

//bad code :)

Session session = SessionManager.openSession();
Transaction tx = session.beginTransaction();

for(int i=0;i<1_000_000;i++) {
Book book = new Book();
book.setName("MyBook "+i);
book.setPrice(i);
session.save(book);
}

tx.commit();
session.close();
  • zapis do bazy danych rekordów jeden po drugim.

    • zwiększenie obciążenia bazy

    • obciążenie aplikacji

    • pasma sieci

  • możliwy OutOfMemoryException

11.1. Możliwe rozwiązania problemu :

  • Możliwość pierwsza

 <property name="hibernate.jdbc.batch_size">50</property>
 <property name="hibernate.cache.use_second_level_cache">false</property>
  • Możliwość druga

Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
for(int i=0;i<1_000_000;i++) {
Book book = new Book();
book.setName("MyBook "+i);
book.setPrice(i);
 if (i % 50 == 0) {
       session.flush();
       session.clear();
   }
 }
transaction.commit();
session.close();

12. Inne możliwe problemy i wskazówki:

  • aktualizowanie encji jedna-po-drugiej zamiast zrobienie tego w pojedyńczym kwerendzie

  • ciężkie procesowanie po stronie Javy zamiast bardziej wydajnego procesowania po stronie bazy

  • dla małych woluminów danych Eager loading zawsze sens.

  • dla wstępnej optymalizacji i częściowej eliminacji problemu n+1 stosuj globalnie parametr : hibernate.default_batch_fetch_size

  • opcja hibernate.max_fetch_depth - dostosowuje ilość możliwych złączeń w systemie. (optymalna wartość to 1-5)

  • tam gdzie to możliwe wybieraj implementację Set zamiast List (cartesian problem)

  • dla dużych wartości kolekcji używaj późnego ładowania

  • zastosuj _@Version aby uzyskać poziom uniknąć niepowtarzalnych odczytów na domyślnym poziomie izolacji jakim jest odczyt zatwierdzonych (Read Committed) (scalability issue) czyli podnosimy prawie za darmo poziom na powtarzalne odczyty (Repeatable Read)

  • praca z wielkimi kolekcjami - stosuj @BatchSize w celu poprawy wydajności , @LazyCollection = Extra lazy

  • wolny insert lub update (wiersz po wierszu→ narzut na tworzenie kwerendy, przepustowość sieci) : stosuj przetwarzania wsadowe , jako półśrodek hibernate.jdbc.batch_size=100

  • pobieranie zbyt wielu kolumn na ogromny impakt wydajność (CPU, Memory, I/O, Networking)

  • atrybut transakcji read-only (Spring) - optymalizacja flush Entity Manager’a

  • stosuj szybką pulę połączeń HikariCp

  • monitoruj puli dropwizard

  • testuj parametry puli aby zwiększać wydajność całej aplikacji np: jmeter

  • testuj zapytania natywne po stronie bazy

  • dla przetwarzań wsadowych możliwe rozwiązania to :

    • StatelessSession

      • brak integracji z second cache i query cache

      • brak dirty checking

      • zwraca wszystkie encje w stanie detached

      • pomija wszystkie event listener’y

      • bliżej JDBC niż inne rozwiązania Hibernate

    • clear and flush session

foreach(){

if(i%100 ==0 ){
 em.flush();
 em.clear();

}
// the same as JDBC batch size
  • rozważ użycie Spring Batch

  • dobierz dobrze strategie tworzenia klucz głównego aby zredukować uderzenia do bazy

    • jeśli tylko można ominąć dirty checking z powodów wydajnościowych zrób to :

  • z poziomu Spring’a @Transactional(readOnly=true)

  • StatelessSession

    • jdbc fetch size : np dla Oracle = 10

  • <property name='hibernate.jdbc.fetch_size' value=100 />

  • redukcja 'database roundtrip', szczególnie dla wierszy z wieloma kolumnami

  • ustawienie per aplikacja , oraz per zapytanie : HINT

    • rozważ użycie 'Horizontal Partitioning'

CREATE TABLE Books (
book_id SERIAL PRIMARY KEY,
-- other columns
date_published DATE
) PARTITION BY HASH ( YEAR(date_published) )
PARTITIONS 10;
  • rozważ użycie rozwiązania 'kontekst per silnik' dla niektórych rozwiązań bazodanowych.

  • jeśli tylko można stosuj @Immutable

  • OutOfMemory - batch insert/update

//bad code :)

Session session = SessionManager.openSession();
Transaction tx = session.beginTransaction();

for(int i=0;i<1_000_000;i++) {
Book book = new Book();
book.setName("MyBook "+i);
book.setPrice(i);
session.save(book);
}

tx.commit();
session.close();
  • flush() i clear() w odniesieniu do cache first level

  • Session.setCacheMode (CacheMode.IGNORE) dla second level cache

Transaction tx = session.beginTransaction();
session.setCacheMode(CacheMode.IGNORE);
//....
  • globalnie

<property name="hibernate.jdbc.batch_size">25</property> <!--  5-30  -->
<property name="hibernate.cache.use_second_level_cache">false</property>

13. Rady

  • Listener vs Converter jak i gdzie go stosować ?

  • Formuła

  • ColumnTransformer

  • DynamicInsert i DymanicUpdate a optymalizacja

  • Immutable jak i kiedy ?

  • @Subselect i symbioza z Immutable

  • Lazy i rozwiązywanie problemów

  • Filters vs View - centralizacja zarządzania (on/off)

link:./src/test/java/pl/java/scalatech/exercise/filter/FilterTest.java[]
  • DTO (Fowler : rozprysk, równoległa hierarchia klas (one2one) )

    • składacz (LazyInitializationException)

    • spłaszczanie domeny

    • transfer obiektów - integracja systemowa

14. Architektura

  • Read-only transaction routing → Master-Slave replication

    Jeśli sterownik bazy danych nie wspiera Master-Slave routingu wtedy można użyć wielu instancji DataSource.
  • Master-Slave

master slave
  • Multi-Master

multi master

  • Sharding

sharding

cqrs