de Emre Savcı

Gestionarea relațiilor de scufundare profundă, încărcarea leneșă și problema N + 1

Hibernarea este un Cartarea relațională a obiectelor instrument care ne permite să folosim obiecte pentru interacțiunea cu baze de date relaționale. Are multe caracteristici, cum ar fi modelarea primului cod, încărcarea leneșă, urmărirea modificărilor, stocarea în cache, auditul etc.

În această postare voi prezenta și discuta relațiile OneToMany și ManyToOne. De asemenea, vom vedea care sunt consecințele construirii unor relații greșite sau folosirii greșite tip de preluare. De asemenea, voi examina relațiile bidirecționale și unidirecționale.

OneToMany

Să începem cu relația OneToMany UniDirectional. Imaginați-vă că avem un Expediere entitatea și o expediere pot conține multe Articols.

Notă: codul de mai jos este doar aici pentru a vă arăta modul simplu de implementare a unei relații, dar nu este recomandat să îl utilizați în producție. Voi explica la sfârșitul exemplelor de cod.

Mai întâi persistence.xml:

<?xml version="1.0" encoding="UTF-8"?><persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence"             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"             xsi:schemaLocation="http://java.sun.com/xml/ns/persistence             http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">    <persistence-unit name="testConfig" transaction-type="RESOURCE_LOCAL">        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>        <class>deneme.Shipment</class>        <class>deneme.Item</class>        <properties>            <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/jpadb"/>            <property name="javax.persistence.jdbc.user" value="root"/>            <property name="javax.persistence.jdbc.password" value="root"/>            <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>            <property name="hibernate.logging.level" value="FINE"/>            <property name="hibernate.show_sql" value="true"/>            <property name="hibernate.format_sql" value="true"/>            <property name="hibernate.ddl-generation" value="auto"/>            <property name="hibernate.hbm2ddl.auto" value="update"/>        </properties>    </persistence-unit></persistence>

Și dependențele noastre pom.xml:

<dependencies><dependency>    <groupId>org.projectlombok</groupId>    <artifactId>lombok</artifactId>    <version>1.18.2</version>    <scope>provided</scope></dependency>
<dependency>    <groupId>org.hibernate</groupId>    <artifactId>hibernate-entitymanager</artifactId>    <version>5.3.3.Final</version></dependency><!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-core --><dependency>    <groupId>org.hibernate</groupId>    <artifactId>hibernate-core</artifactId>    <version>5.3.3.Final</version></dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --><dependency>    <groupId>mysql</groupId>    <artifactId>mysql-connector-java</artifactId>    <version>5.1.6</version></dependency><dependencies>

Iată entitățile noastre:

@[email protected](name = "shipments")@[email protected] Shipment {    @Id    @GeneratedValue(strategy = GenerationType.IDENTITY)    private int id;    private String cargoName;    @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)    @JoinTable(name = "shipment_items")    private List<Item> childList;        public void addItem(Item item) {        if (childList == null) {            childList = new ArrayList();        }        childList.add(item);    }}@[email protected](name = "items")@[email protected] Item {    @Id    @GeneratedValue(strategy = GenerationType.IDENTITY)    private int id;    private String itemName;}

Să detaliem adnotările care sunt definite în entitățile noastre.

Adnotarea @Entity îi spune lui Hibernate evaluarea clasei noastre ca entitate care face ca Hibernate să o urmărească în PersistenceContext.

@Masa adnotarea spune Hibernate care este numele tabelului bazei de date.

@Getter și @Setter adnotările provin de la Lombok pentru a elimina codul cazanului.

@OneToMany (fetch = FetchType.LENEŞ, cascade = CascadeType.TOATE) adnotare definește modul de preluare a obiectelor copil din bazele de date utilizând parametrul FetchType.Lazy. CascadeType.All înseamnă că cascadă toate operațiile de la părinte la copil.

@JoinTable (name = “shipment_items”) adnotarea definește proprietarul relației care este entitatea de expediere este cazul nostru. Se creează un tabel intermediar pentru a menține relații.

Pentru că folosim OneToMany adnotare numai în partea părinte, se numește a Unidirecțional relaţie. Prin urmare, nu putem achiziționa obiectul părinte din partea copilului.

Acum trebuie să dobândim EntityManager din EntityManagerFactory pentru a efectua operațiuni în baza de date.

Acum vom vedea cum să ne salvăm entitățile.

public class Main {    public static void main(String[] args) {        EntityManagerFactory emfactory = Persistence.createEntityManagerFactory("testConfig");        EntityManager entitymanager = emfactory.createEntityManager();        Shipment shipment = new Shipment();        shipment.setCargoName("test cargo");        Item item = new Item();        item.setItemName("test item");        shipment.addItem(item);        entitymanager.close();        emfactory.close();    }}

Când salvăm entitățile pentru prima dată, Hibernate creează tabele și relații corespunzătoare. Să vedem ce interogări sunt generate de Hibernate:

Hibernate: create table items (id int identity not null, itemName varchar(255), primary key (id))
Hibernate: create table shipment_items (Shipment_id int not null, childList_id int not null)
Hibernate: create table shipments (id int identity not null, cargoName varchar(255), primary key (id))
Hibernate: alter table shipment_items drop constraint UK_8ipo6hqqte0sdoftcqflf3hie
Hibernate: alter table shipment_items add constraint UK_8ipo6hqqte0sdoftcqflf3hie unique (childList_id)
Hibernate: alter table shipment_items add constraint FK20iu625dsmyisso2hrcc2qpk foreign key (childList_id) references items
Hibernate: alter table shipment_items add constraint FK6nronhhlbku40g81rfte2p02t foreign key (Shipment_id) references shipments

În baza noastră de date, când executăm interogările:

SELECT * FROM shipments
SELECT * FROM items
SELECT * FROM shipment_items

Întoarce rezultate goale:

Cum se utilizeaza Hibernate pentru a interactiona cu baze de

De ce? Deoarece nu am persistat entitățile în entityManager.

Să le persistăm:

shipment.addItem(item);entitymanager.persist(shipment);

Când rulăm din nou codul și verificăm baza de date, vom vedea că încă nu există date. Asta pentru că nu am început nicio tranzacție. Trebuie să începem o tranzacție și apoi să o comitem imediat după ce persistăm.

entitymanager.getTransaction().begin();entitymanager.persist(shipment);entitymanager.getTransaction().commit();

Când îl vom rula din nou, vom vedea că Hibernate generează interogări care salvează entitățile noastre în baza de date:

Hibernate: insert into shipments (cargoName) values (?)
Hibernate: insert into items (itemName) values (?)
Hibernate: insert into shipment_items (Shipment_id, childList_id) values (?, ?)

Aici vedem că datele noastre se află în baza de date:

1611326048 915 Cum se utilizeaza Hibernate pentru a interactiona cu baze de

Îți amintești că folosim CascadeType.ALL în OneToMany adnotare – deci de ce am folosit asta? Ce se întâmplă dacă nu-l specificăm?

Să-l eliminăm din adnotare:

@OneToMany(fetch = FetchType.LAZY)@JoinTable(name = "shipment_items")private List<Item> childList;

Și persistă din nou entitatea noastră:

entitymanager.getTransaction().begin();entitymanager.persist(shipment);entitymanager.getTransaction().commit();
1611326049 386 Cum se utilizeaza Hibernate pentru a interactiona cu baze de

Vedem două lucruri aici: unul este o excepție aruncată de Hibernate și celălalt este că nu există nicio instrucțiune SQL generată pentru salvarea relației părinte-copil atunci când eliminăm parametrul tip cascadă. Și, desigur, nu există date scrise în baza de date.

Ce trebuie să facem? Folosește CascadeType.ALL este singura cale de urmat? Desigur că nu, dar este modul recomandat conform acestui caz de utilizare.

Pentru mai multe informații despre tipurile de cascadă aruncați o privire aici.

Putem salva entitatea copil în mod explicit și putem vedea că relația noastră este salvată fără probleme:

entitymanager.getTransaction().begin();entitymanager.persist(item);entitymanager.persist(shipment);entitymanager.getTransaction().commit();

Acum Hibernate generează instrucțiunile de inserare corecte și vedem datele noastre în baza de date:

Hibernate: insert into items (itemName) values (?)
Hibernate: insert into shipments (cargoName) values (?)
Hibernate: insert into shipment_items (Shipment_id, childList_id) values (?, ?)

Gestionarea datelor salvate

Bine, am văzut cum să salvăm entități sub formă de relații părinte-copil în baza de date. Acum este timpul să preluăm datele pe care le-am salvat.

Shipment shipment = entitymanager.find(Shipment.class, 1);System.out.println(shipment.getCargoName());

Când ne uităm la ieșire, vom vedea interogarea generată și valoarea returnată getCargoName ():

Hibernate: select shipment0_.id as id1_2_0_, shipment0_.cargoName as cargoNam2_2_0_ from shipments shipment0_ where shipment0_.id=?
test cargo

Nu sunt lucruri interesante aici. Dar să imprimăm dimensiunea childList folosind getChildList().size() pe obiectul nostru de expediere.

Shipment shipment = entitymanager.find(Shipment.class, 1);System.out.println(shipment.getCargoName());System.out.println("Size of childList : " + shipment.getChildList().size());

Când rulăm codul, vom vedea o interogare suplimentară care preia obiectele copil în ieșire:

Hibernate:
SELECTshipment0_.id AS id1_2_0_,shipment0_.cargoName AS cargoNam2_2_0_FROM shipments shipment0_WHERE shipment0_.id = ?
test cargo //the below query generated after calling getChildList() method
Hibernate:
SELECTchildlist0_.Shipment_id AS Shipment1_1_0_,childlist0_.childList_id AS childLis2_1_0_,item1_.id AS id1_0_1_,item1_.itemName AS itemName2_0_1_FROM shipment_items childlist0_INNER JOIN items item1_ON childlist0_.childList_id = item1_.idWHERE childlist0_.Shipment_id = ?
Size of childList : 1

Ceea ce s-a întâmplat aici este cunoscut sub numele de Încărcare leneșă. Pentru că am definit fetch = FetchType.Lazy in @OneToMany adnotare atunci când am încărcat obiectul de expediere prima dată, acesta nu încarcă entități copil împreună.

În relațiile leneșe, o entitate copil se încarcă numai atunci când o accesați prima dată.

Acum să vedem versiunea dornică – vom schimba parametrul Lazy la Eager:

@OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)

Rulați din nou codul și vedeți ieșirea:

Hibernate:
SELECTshipment0_.id AS id1_2_0_,shipment0_.cargoName AS cargoNam2_2_0_,childlist1_.Shipment_id AS Shipment1_1_1_,item2_.id AS childLis2_1_1_,item2_.id AS id1_0_2_,item2_.itemName AS itemName2_0_2_FROM shipments shipment0_LEFT OUTER JOIN shipment_items childlist1_ON shipment0_.id = childlist1_.Shipment_idLEFT OUTER JOIN items item2_ON childlist1_.childList_id = item2_.idWHERE shipment0_.id = ?
test cargoSize of childList : 1

Vedem că nu există nicio interogare suplimentară. A preluat totul ca întreg. (Acum puteți renunța la această modificare – a fost doar pentru a vă arăta ce se întâmplă sub capotă. Vom continua cu încărcarea leneșă în exemple.)

Acum, să încercăm să adăugăm un articol la expedierea existentă.

Shipment shipment = entitymanager.find(Shipment.class, 1);Item item = new Item();item.setItemName("item to existing shipment");shipment.addItem(item);entitymanager.getTransaction().begin();entitymanager.persist(shipment);entitymanager.getTransaction().commit();

Interogări generate:

Hibernate:     select        shipment0_.id as id1_2_0_,        shipment0_.cargoName as cargoNam2_2_0_     from        shipments shipment0_     where        shipment0_.id=?
Hibernate:     select        childlist0_.Shipment_id as Shipment1_1_0_,        childlist0_.childList_id as childLis2_1_0_,        item1_.id as id1_0_1_,        item1_.itemName as itemName2_0_1_     from        shipment_items childlist0_     inner join        items item1_             on childlist0_.childList_id=item1_.id     where        childlist0_.Shipment_id=?
Hibernate:     insert     into        items        (itemName)     values        (?)
Hibernate:     insert     into        shipment_items        (Shipment_id, childList_id)     values        (?, ?)

Selectează o expediere, apoi preia entitatea copil cu o interogare de asociere, inserează entitatea copil într-un tabel și inserează ID-uri în tabelul intermediar care deține relații.

Bine, când ne gândim la asta, arată și funcționează așa cum era de așteptat. Acum rulați din nou codul pentru a adăuga un alt articol la expediere.

Acum aruncați o privire mai atentă asupra interogărilor generate, în special pe cele îndrăznețe:

Hibernate:     select        shipment0_.id as id1_2_0_,        shipment0_.cargoName as cargoNam2_2_0_     from        shipments shipment0_     where        shipment0_.id=?
Hibernate:     select        childlist0_.Shipment_id as Shipment1_1_0_,        childlist0_.childList_id as childLis2_1_0_,        item1_.id as id1_0_1_,        item1_.itemName as itemName2_0_1_     from        shipment_items childlist0_     inner join        items item1_             on childlist0_.childList_id=item1_.id     where        childlist0_.Shipment_id=?
Hibernate:     insert     into        items        (itemName)     values        (?)
Hibernate:     delete     from        shipment_items     where        Shipment_id=?
Hibernate:     insert     into        shipment_items        (Shipment_id, childList_id)     values        (?, ?)
Hibernate:     insert     into        shipment_items        (Shipment_id, childList_id)     values        (?, ?)

Ce s-a intamplat aici? Vedem o ștergere și două inserări la sfârșitul interogărilor noastre.

  • Selectează o expediere
  • Descarcă copilul cu o interogare de alăturare
  • Inserează noul element
  • Șterge toate relațiile anterioare din tabelul intermediar
  • Inserează toate relațiile de la zero

Motiv pentru care generează două instrucțiuni insert la final. Acum cred că vă puteți imagina ce se întâmplă dacă avem mii de articole pentru copii. Desigur, nu vom folosi această tehnică. Este un blocaj pentru performanţă .

În abordarea de mai sus, folosim un tabel intermediar pentru a menține relații. După cum ați observat, generează interogări suplimentare pentru inserarea relațiilor. Pentru că am definit un JoinTable adnotare pe entitatea de expediere …

Procedând în alt mod: ManyToOne

Cum evităm această situație? Ei bine, folosind JoinColumn adnotare în loc de JoinTable.Să vedem cum să-l implementăm cu adnotarea JoinColumn.

@[email protected](name = "shipments")@[email protected] Shipment {    @Id    @GeneratedValue(strategy = GenerationType.IDENTITY)    private int id;    private String cargoName;    @OneToMany(            fetch = FetchType.LAZY,            cascade = CascadeType.ALL,            mappedBy = "shipment")    private List<Item> childList;    public void addItem(Item item) {        if (childList == null) {            childList = new ArrayList();        }        childList.add(item);        item.setShipment(this);    }}@[email protected](name = "items")@[email protected] Item {    @Id    @GeneratedValue(strategy = GenerationType.IDENTITY)    private int id;    private String itemName;    @ManyToOne    @JoinColumn(name = "shipment_id")    private Shipment shipment;}

Notă: există două lucruri de luat în considerare în noul model de relație de mai sus. Adăugăm adnotările ManyToOne și JoinColumn la Item. De asemenea, adăugăm parametrul mappedBy la Expediere.

Acum să ștergem toate tabelele anterioare pentru claritate și un nou început. Și când rulăm codul pentru prima dată, creează și tabele și relații (am schimbat baza de date de la mssql la mysql deoarece am scris această postare în câteva zile separate. Acesta este motivul pentru care vedeți InnoDB în interogările generate de mai jos):

Hibernate:create table items (       id integer not null auto_increment,        itemName varchar(255),        shipment_id integer,        primary key (id)    ) engine=InnoDB
Hibernate:         create table shipments (       id integer not null auto_increment,        cargoName varchar(255),        primary key (id)    ) engine=InnoDB
Hibernate:         alter table items        add constraint FKcpv8kcpjc081l551hre32f2rg        foreign key (shipment_id)        references shipments (id)

Din nou, când introducem o expediere cu un articol:

Shipment shipment = new Shipment();shipment.setCargoName("test cargo");Item item = new Item();item.setItemName("test item");shipment.addItem(item);entitymanager.getTransaction().begin();entitymanager.persist(shipment);entitymanager.getTransaction().commit();

Hibernate generează doar două interogări sql așa cum era de așteptat:

Hibernate:     insert     into        shipments        (cargoName)     values        (?)
Hibernate:     insert     into        items        (itemName, shipment_id)     values        (?, ?)

Și înregistrările bazei noastre de date arată ca mai jos:

1611326049 329 Cum se utilizeaza Hibernate pentru a interactiona cu baze de
1611326050 493 Cum se utilizeaza Hibernate pentru a interactiona cu baze de

Cum rămâne cu selectarea unei expediții din db? Cum ne va schimba interogările?

Să rulăm codul de mai jos și să vedem interogările noastre generate:

Shipment shipment = entitymanager.find(Shipment.class, 1);System.out.println(shipment.getCargoName());System.out.println(shipment.getChildList().size());

Ieșire:

Hibernate:     select        shipment0_.id as id1_1_0_,        shipment0_.cargoName as cargoNam2_1_0_     from        shipments shipment0_     where        shipment0_.id=?
test cargo
Hibernate:     select        childlist0_.shipment_id as shipment3_0_0_,        childlist0_.id as id1_0_0_,        childlist0_.id as id1_0_1_,        childlist0_.itemName as itemName2_0_1_,        childlist0_.shipment_id as shipment3_0_1_     from        items childlist0_     where        childlist0_.shipment_id=?

Prima hibernare selectează o expediere din db. Și apoi pentru că numim metoda getChildList (). Size (), leneș încarcă entitățile copil. Vedeți că nu există interogări de Alăturare atunci când încărcați entitățile copil deoarece nu există un tabel intermediar ca în primul exemplu?

Acum să adăugăm un articol la expedierea existentă și să comparăm interogările generate cu primul exemplu. După cum vă amintiți din primul exemplu, când încercăm să adăugăm un articol nou la expedierea existentă, acesta încarcă toate entitățile secundare și șterge relațiile din tabelul intermediar. Apoi introduce din nou relațiile. Este o problemă imensă de performanță atunci când lucrați cu seturi de date grele.

Dar cu noua noastră abordare, este ușor să rezolvăm această problemă:

Shipment shipment = entitymanager.find(Shipment.class, 1);Item item = new Item();item.setItemName("item to existing shipment");shipment.addItem(item);entitymanager.getTransaction().begin();entitymanager.persist(shipment);entitymanager.getTransaction().commit();

Acesta generează doar două interogări așa cum ar trebui, o selecție pentru expediere și o inserție pentru copil:

Hibernate:     select        shipment0_.id as id1_1_0_,        shipment0_.cargoName as cargoNam2_1_0_     from        shipments shipment0_     where        shipment0_.id=?
Hibernate:     insert     into        items        (itemName, shipment_id)     values        (?, ?)

Acum este timpul să vă arătăm o problemă obișnuită în timp ce lucrați cu instrumentele ORM. Să presupunem că avem 3 transporturi și fiecare transport are 3 articole secundare. Ce se întâmplă atunci când recuperăm aceste expediții și încercăm să repetăm ​​copiii lor?

Inserăm datele noastre inițiale:

for (int i = 1; i <= 3; i++) {    Shipment shipment = new Shipment();    shipment.setCargoName("test shipment " + i);    for (int j = 1; j <= 3; j++) {        Item item = new Item();        item.setItemName("test item " + j);        shipment.addItem(item);    }    entitymanager.getTransaction().begin();    entitymanager.persist(shipment);    entitymanager.getTransaction().commit();}

Să enumerăm toate entitățile de expediere din baza noastră de date și să imprimăm dimensiunile articolului copil:

EntityManagerFactory emfactory = Persistence.createEntityManagerFactory("Hibernate_Jpa");EntityManager entitymanager = emfactory.createEntityManager();List<Shipment> shipments = entitymanager.createQuery("select s from Shipment s").getResultList();for (Shipment shipment : shipments) {    System.out.println(shipment.getChildList().size());}entitymanager.close();emfactory.close();

Iată întrebările noastre generate:

Hibernate:     select        shipment0_.id as id1_1_,        shipment0_.cargoName as cargoNam2_1_     from        shipments shipment0_
Hibernate:     select        childlist0_.shipment_id as shipment3_0_0_,        childlist0_.id as id1_0_0_,        childlist0_.id as id1_0_1_,        childlist0_.itemName as itemName2_0_1_,        childlist0_.shipment_id as shipment3_0_1_     from        items childlist0_     where        childlist0_.shipment_id=?
Child item size for shipment id:1 is 3
Hibernate:     select        childlist0_.shipment_id as shipment3_0_0_,        childlist0_.id as id1_0_0_,        childlist0_.id as id1_0_1_,        childlist0_.itemName as itemName2_0_1_,        childlist0_.shipment_id as shipment3_0_1_     from        items childlist0_     where        childlist0_.shipment_id=?
Child item size for shipment id:2 is 3
Hibernate:     select        childlist0_.shipment_id as shipment3_0_0_,        childlist0_.id as id1_0_0_,        childlist0_.id as id1_0_1_,        childlist0_.itemName as itemName2_0_1_,        childlist0_.shipment_id as shipment3_0_1_     from        items childlist0_     where        childlist0_.shipment_id=?
Child item size for shipment id:3 is 3

Ceea ce s-a întâmplat aici se numește N + 1 problemă. Deoarece s-a executat o interogare pentru selectarea tuturor expedierilor și N (unde N este dimensiunea expedierilor, care este 3 pentru exemplul nostru) interogări executate pentru selectarea entităților secundare.

Pentru a rezolva această problemă avem mai multe opțiuni.

Unul dintre ele este de folosit BatchSize adnotare.

Este posibil să reglați dimensiunea lotului pentru fiecare interogare. Să punem adnotarea pe relația noastră și să vedem SQL generat.

@OneToMany(        fetch = FetchType.LAZY,        cascade = CascadeType.ALL,        mappedBy = "shipment")@BatchSize(size = 10)private List<Item> childList;

Am dat mărimea lotului 10 în scopul testării. Și interogarea de mai jos a fost generată.

Hibernate:     select        shipment0_.id as id1_1_,        shipment0_.cargoName as cargoNam2_1_     from        shipments shipment0_Hibernate:     select        childlist0_.shipment_id as shipment3_0_1_,        childlist0_.id as id1_0_1_,        childlist0_.id as id1_0_0_,        childlist0_.itemName as itemName2_0_0_,        childlist0_.shipment_id as shipment3_0_0_     from        items childlist0_     where        childlist0_.shipment_id in (            ?, ?, ?        )
Child item size for shipment id:1 is 3Child item size for shipment id:2 is 3Child item size for shipment id:3 is 3

Deoarece avem 3 expedieri și dimensiunea lotului este 10, se adaugă cele 3 ID-uri de expediere în interogarea unde. Dacă dimensiunea lotului a fost setată la 2, ar exista 2 ID-uri de expediere în interogarea unde.

O altă soluție este utilizarea fetch join în jpql.

Ne schimbăm interogarea în:

List<Shipment> shipments = entitymanager.createQuery("select s from Shipment s join fetch s.childList").getResultList();

Și rulăm din nou codul. Generează următoarea ieșire:

Hibernate:     select        shipment0_.id as id1_1_0_,        childlist1_.id as id1_0_1_,        shipment0_.cargoName as cargoNam2_1_0_,        childlist1_.itemName as itemName2_0_1_,        childlist1_.shipment_id as shipment3_0_1_,        childlist1_.shipment_id as shipment3_0_0__,        childlist1_.id as id1_0_0__     from        shipments shipment0_     inner join        items childlist1_             on shipment0_.id=childlist1_.shipment_id
Child item size for shipment id:1 is 3Child item size for shipment id:1 is 3Child item size for shipment id:1 is 3Child item size for shipment id:2 is 3Child item size for shipment id:2 is 3Child item size for shipment id:2 is 3Child item size for shipment id:3 is 3Child item size for shipment id:3 is 3Child item size for shipment id:3 is 3

Wow, ce tocmai s-a întâmplat? De ce există 9 ieșiri pentru dimensiunile de imprimare? Deoarece interogarea jpql generează o interogare SQL de unire interioară, aceasta determină înregistrări duplicate pentru fiecare copil. Prin urmare, 3 expedieri x 3 copii = 9 ieșiri duplicate. Pentru a evita această dublare, trebuie să adăugăm încă un cuvânt cheie în jqpl-ul nostru, care este distinct cuvânt cheie.

List<Shipment> shipments = entitymanager.createQuery("select distinct s from Shipment s join fetch s.childList").getResultList();

Acum rezultatul a fost generat exact așa cum era de așteptat:

Hibernate:     select        distinct shipment0_.id as id1_1_0_,        childlist1_.id as id1_0_1_,        shipment0_.cargoName as cargoNam2_1_0_,        childlist1_.itemName as itemName2_0_1_,        childlist1_.shipment_id as shipment3_0_1_,        childlist1_.shipment_id as shipment3_0_0__,        childlist1_.id as id1_0_0__     from        shipments shipment0_     inner join        items childlist1_             on shipment0_.id=childlist1_.shipment_id
Child item size for shipment id:1 is 3Child item size for shipment id:2 is 3Child item size for shipment id:3 is 3

Și astfel rezolvăm problemele duplicate.

Cred că este suficient pentru o postare, deoarece a devenit destul de lungă deja. Vă mulțumesc tuturor pentru că ați citit până acum. Sper că acum vă este clar cum Hibernate gestionează relațiile, ce întrebări sunt generate sub capotă și cum să evitați unele greșeli obișnuite.

Concluzie

  • Preferați încărcarea leneșă atunci când o puteți folosi
  • Utilizați asocierea bidirecțională cu adnotarea ManyToOne pe entitățile copil atunci când trebuie (mai degrabă decât OneToMany)
  • Acordați responsabilitatea relațiilor părții copilului ori de câte ori este posibil
  • Pentru a evita problema N + 1, utilizați BatchSize adnotare sau scrie jpql cu fetch join
  • Utilizare JoinColumn in loc de JoinTable pe OneToMany relații pentru a evita interogări de alăturare suplimentare

Puteți găsi exemplul de cod în depozitul meu github: hibernare-exemple

Resurse

HIBERNATE – Persistența relațională pentru Java idiomatic
Hibernate nu numai că se ocupă de maparea de la clasele Java la tabelele bazelor de date (și de la tipurile de date Java la datele SQL …docs.jboss.orgCare este problema interogării N + 1 SELECT?
SELECT N + 1 este în general declarat ca o problemă în discuțiile de cartografiere obiect-relațională (ORM) și înțeleg că …stackoverflow.comUn ghid pentru începători la tipurile JPA și Hibernate Cascade – Vlad Mihalcea
(Ultima actualizare la: 25 aprilie 2018) Introducere JPA traduce tranzițiile stării entității în instrucțiunile DML din baza de date …vladmihalcea.com