Se afișează postările cu eticheta jpa. Afișați toate postările
Se afișează postările cu eticheta jpa. Afișați toate postările

04 noiembrie 2025

Training Java performance - Ziua 2 (JPA)

 SOLR/ES/Lucene - full text search

Vector search - next thing


Brian: paginare N rezultate, apoi N/2 , N/4 ... dar intr-o singura tranzactie


Query-uri dinamice:

1. concat string-uri: WHERE 1+1" (ca apoi sa poti concatena alte conditii, if criteria)

2. Query DieSL: JpaQuery

- Diesel genereaza entitatile (ex. QMaterial)

3. CriteriaAPI


Planul de executie uraste "OR"

- face cache la planul construit anterior

- query diferit: alt plan


Index: ideal e sa ai distributie buna pe date (nu prea multe valori repetitive)


AskTom - (Oracle) - intrebari tuning etc


@Id

@GeneratedValue(strategy = _ )

- SEQUENCE: trebuie creata; la rollback tranzactie nu se poate recupera (da impresia ca e un rand sters in table)

INCREMENT BY 50: tragi din baza 50 id-uri odata

- IDENTITY = auto-increment; ceri de fiecare data din baza

- PK violation daca inserezi id-uri 'cunoscute'

- UUID - greu de ghicit, poate fi generat de client; dar urat

- TABLE


@Embedded pe un obiect din entity -> muti anumite attribute din clasa (coloane) pt a-i scurta marimea


@Entity to Domain Model - vezi notele


@Transactional pe metoda - nu poti face face try/catch pe query

- interzise tranzactiile manuale

Write behind: JPA amana scrierile pana inainte de commit (flush)- pt a insera/update in batch-uri

em.flush() - grabeste flush, dar nu comite; crapa de la erori

+ un select ar putea grabi flush


Carte: "Release it!"


Entitate gasita cu find pe o metoda @Transactional:

.set...() face update (auto-save)

alt .set nu face / nu se mai potriveste equals


em.detach(obj); // scoate din urmarire


em.clear(); // elimina din context toate entitatile (detach la tot), goleste 1st level cache al lui Hibernate


flush vs clear:

- flush scrie tot ce avea de scris; util: inainte sa chemi un PL/SQL, Trigger sau native query.

- goleste fara sa scrie nimic


@DynamicUpdate - modifica coloana din entitate doar daca chiar se modifica


1st level cache = transaction-scoped; find se uita in cache

2nd level cache = intre request-uri diferite (optional @Cacheble pe @Entity sau query hint)

- util: primul select ia din baza, al doilea select identic intr-un TTL=5 min ia din cache


Persistence context


@Cache pe entity class


em.merge(entity); // update fara auto-save, fara @Transactional, cand construiesti cu new entitatea Venita de la client (nu mai faci find)

Merge = suprascriere entitate veche

select parinte, copii si apoi update


cascade

- DETACH ~ detaseaza copiii odata cu parintele

- REFRESH ~ git reset

- MERGE - copiii se ins/upd/del


@Transactional cand doar citesti din BD: lazy init (entitatile-copii ca lista nu sunt dispo pt un framework gen Jackson)


@OneToMany(cascade = ALL, fetch = EAGER, orphanRemoval = true)

entitate parent.getChildren().remove(1) => UPDATE copil set parent null => cu orphanRemoval omoara copiii cu FK = null

( valabil la em.merge )


Cand un camp nu trebuie sa fie modificat pe un flux (ajuta cu merge side effects):

1) @Column(updatable = false) String createdBy; // eroare daca incerci sa modifici din cod

2) setCreatedBy - ignora/arunca eroare daca e deja setat campul

3) suprascrii cu valoarea care era initial in baza


Concurenta la modificare entitate:

Optimistic lock: te lasa sa scrii, cand salvezi iti da eroare daca cineva a salvat inaintea ta

@Version

private Long version; // sau LocalDateTime

// la em.merge(entity) se adauga o conditie suplimentara: AND version = 1 (cat era pe entity)


Pessimistic lock: pus lock in baza = intarzie a doua tranzactie pana comite prima

cu row lock: SELECT ... where version=7 FOR UPDATE [NO WAIT - la nivel de rand]

- face lock pe randurile returnate de SELECT

tx2 va astepta pana tx1 comite; = default in postgres/H2

adaugare coloane IN_EDIT_BY, IN_EDIT_SINCE cand materialul incepe sa fie editat de un user (lock pe linia din tabel)


Nu folosim synchronized in EJB


Monitorizare "connection acquisition time" = metrici - Grafana, Kibana, New Relic


@Transactional:

persist, flush nu duc la commitment daca apare exceptie in metoda tranzactata

- orice metoda chemata in thread-ul curent e parte din aceeasi tranzactie

- thread async: NU, si acopera exceptia; pt ca se pierde conexiunea JDBC care avea tranzactia pornita; nu merge nici daca pasezi EntityManager-ul de pe thread-ul gazda


Metoda @Transactional care cheama alta metoda @Transactional = e aceeasi tranzactie (parinte)

Propagation.REQUIRES_NEW ca sa fie distincta

TxType.REQUIRED = este default


throw checked exception intr-o metoda @Transactional nu impiedica commit-ul, doar unchecked (runtime) impiedica (!!!)

=> metoda @Transactional nu se recomanda cu throws! - faci try/catch/throw RuntimeException


@Transactional cu Propagation.REQUIRES_NEW pe o metoda-copil din acelasi serviciu = ca si cum nu are @Transactional


! nu se da API-call din @Transactional


Deadlock:

synchronized(x) din java + SELECT FOR UPDATE id=7 in parallel cu 

SELECT FOR UPDATE id=7 + synchronized(x) din java


View materializat pe disc si actualizat periodic;


Rainbow brackets- plugin IntelliJ


DbUnit 


Tuning tips:

- EXISTS (SELECT...) in loc de SELECT COUNT() sau id=(SELECT id...)

- Evita SELECT DISTINCT pe mai multe coloane = multe comparatii

- WHERE in loc de HAVING

- INNER JOIN ON <cond> in loc de WHERE<cond>

- LIMIT

- UNION ALL nu UNION (=DISTINCT)

- A UNION B in loc de WHERE... OR..

- GROUP BY in loc de OVER PARTITION BY

- tabele auxiliare

- IN si = in loc de != si <> si NOT IN

25 octombrie 2025

Training Java performance - Ziua 1 (JPA)

Hibernate - performanta down, magie

Orice @Entity trebuie sa aiba @Id (Integer, Long sau String - cheie naturala, ideal)

@OneToMany(mappedBy="numele companiei in cealalta clasa")

@Inheritance(strategy=TABLE_PER_CLASS, SINGLE_TABLE, JOINED)

Exemplu: 2 tabele Pisica/miaunat & Caine/latrat
SINGLE_TABLE: simplu, unele coloane sunt degeaba, nu poti pune NOT NULL pe coloanele specific; se foloseste daca dif dintre clase sunt minime; @DiscriminatorValue & @DiscriminatorColumn
TABLE_PER_CLASS (2 tabele): nu poti face select pe Animal; faci UNION (scump)
JOINED (3 tabele): join la orice query (ca sa incluzi si Animal) ---> nu se foloseste in practica

PARTITION BY - sparge datele in datafile separate (dupa an sau luna de obicei);
se face dupa cum vei face cautarea
daca cauti dupa altceva, merge rau
- nu ocupa spatiu in plus, precum indecsii

INDEX e ceva complementar (~ hashMap); prea multi afecteaza performanta

Employee si EmployeeDetails - pot avea shared primary key (FK al lui Details sa fie si PK)
- datele masive pot fi tinute in alt loc daca nu sunt frecventate des

Evitare referinte reciproce Employee si EmployeeDetails - depinde de caz - daca selectezi details si vrei si employee-ul initial, pui
altfel @OneToOne pt details, in Employee; daca modifici unul poti uita de celalalt

@ManyToMany
@JoinTable

@Enumerated - cand campul este un enum

ProjectType { LIB("L") } - in db ajunge "L", nu 0 sau LIB

XML/JSON - salvat ca CLOB (character large object)
- editare xml din clob risca stricaciuni
- poti selecta bucati din XML cu instr speciale Oracle
- poate fi prea mare, ocupa discul -- OOME (out of memory error)
Trebuie citit/scris in BD via fisier (cu InputStream/OutputStream)
Best practice: nu incarcat in BD, ci arhivat si salvat pe disc (ftp sau pe acelasi disc), cu referinta in BD;

like pe coloana CLOB: dureaza f mult pt ca CLOB nu e indexabil

https://github.com/victorrentea/jpa/tree/systematic25
run StartDatabase, JpaApplication
JpaPlayground - cu Lombok
fol. EntityManager in loc de Repository (EE)
em.persist(new Teacher());

@Transactional in Spring & Jakarta

Creare 2 entitati inrudite: trebuie legate both-way - daca uiti?
Relatiile bidirectionale sunt greu de intretinut.

Design mai bun: getter sa fie unmodifiable - blochezi get/add; faci o metoda de add in care legi dublu;
in cealalta clasa setter-ul e de tip default (nivel de package) - sa nu poti accesa set din afara pachetului

@OneToOne (cascade = CascadeType.ALL)
- TeacherDetails nu are ref la Teacher

CascadeType.ALL intre parinte si copiii exclusivi ai lui (creare, stergere automata)

campuri List vs Set
- ordonare cu @OrderBy("type ASC, value desc") pt List, sortare din query JPA
@OrderColumn(name = "INDEX") - salveaza in DB ordinea manual setata de user in UI
altfel ordinea nu e garantata

hashCode/equals pe:
- id?
- campuri?
- id + campuri? - situatie campuri identice si unul are id=null; ambele circula
=> nu impl hashCode/equals pe entitati, facem propriul equals; mai tricky la HashSet

@NotNull - previne sa inserezi prin java (Jakarta validation la persist)
nullable = false - previne sa inserezi "pe sub mana" -- folosit in POC, nu in proiecte mature (nu creezi DB din java); pot ramane out of sync

@AssertTrue pe metoda ~ @NotNull cand validarea e doar pe campul unui copil

@OneToMany poate fi def unidirectional, cu @JoinColumn(name = "loanerId")

constructor in entity:
protected cu constructor gol - ca sa nu planga Hibernate
public cu parametri - este cel relevant

getter/setters: nu neaparat necesare, Hibernate foloseste reflectie pt a popula campurile

--> BalanceEntityListener ??
--> @Transient in BalanceEntity?

http://annas-archive.org/ -- book "sql performance explained"

URL BD: jdbc:mssql ----> jdbc:p6spy:mssql , cu username, parola, dependency la p6spy
-> alternativa mai buna la show-sql=true

@OneToMany
N+1 queries = 1 pt parinte, N pt copii (loading lazy info)
fetch = FetchType.LAZY by default (doar pe colectii); la fel si pt @ManyToMany
EAGER daca eviti N+1 queries -> incarca tot dinainte - a nu se folosi aproape niciodata
@BatchSize(size=20) - este Hibernate specific - incarca entitati inrudite in calupuri

Ia toate info odata (pt @ManyToOne)
select p from Person p
LEFT JOIN FETCH p.children
LEFT JOIN FETCH p.country ---- nu este colectie

@ManyToOne - si asta aduce N+1 queries

fetch = EAGER by default

entityManager.createQuery("SELECT pFROM errorProne"); // app porneste, vezi bug-ul tarziu
@NamedQuery (name = "", query = "") ; // detecteaza devreme, nu compileaza

Sub-select: select doar anumite campuri
@Subselect("query...")
public record.... - in spring

@NamedNativeQuery - pt EE
(query= "", resultClass = , resultSetMapping = )
- cu COALESCE
- to VIEW ----> declari un nou @Entity in Java cu @Table("VIEW_NAME")

Teste pe query-uri

FetchGraph - sa incarci partial entitati

Paginare: ORDER BY, LIMIT __, OFFSET __
- in BD, in Java sau pe client

Problema: daca se insereaza/sterge in timpul frunzaririi (cached)
problema: el face select tot dar iti da numai ce ceri
Nu poti pagina in DB daca faci FETCH, pt ca se strica cardinalitatea
pe doua directii: iese produs cartezian

LEFT JOIN FETCH ALL PROPERTIES - ia in cascada

@ManyToOne private Country country; ==> private Long countryId; // pastreaza FK
-> pune numeric/string ref in loc de object reference

entityManager
.createNamedQuery(..)
.setFirstResult(0) // offset
.setMaxResults(2) // limit
.getResultList();

@ElementCollection -- entitati-copii fara id

Cross join: N x N x N results

Intrerupere query in timpul rularii - exista api de jdbc "abort" - nu din JPA, doar JDBC (statement.cancel())
sau: timeout in JPA