Różnica między FetchType LAZY i EAGER w Java Persistence API?


552

Jestem nowicjuszem Java Persistence API i Hibernacji.

Jaka jest różnica między FetchType.LAZYi FetchType.EAGERw interfejsie API Java Persistence?


1
Ładowanie kolekcji EAGER oznacza, że ​​są one pobierane w całości w momencie pobierania ich rodzica. Podczas ładowania EAGER, całe moje dziecko jest pobierane. Dziecko jest pobierane z PersistentSet i PersistentList (lub PersistentBag), wewnątrz Trwałej torby, jest wyświetlane jako lista macierzy. Czy to prawda ?? ..
geetha

Odpowiedzi:


1064

Czasami masz dwa byty i istnieje między nimi związek. Na przykład możesz mieć jednostkę o nazwie, Universitya inna jednostka o nazwie, Studenta uniwersytet może mieć wielu studentów:

Jednostka uniwersytecka może mieć pewne podstawowe właściwości, takie jak identyfikator, nazwa, adres itp., A także właściwość kolekcji zwaną studentami, która zwraca listę studentów dla danej uczelni:

Uniwersytet ma wielu studentów

public class University {
   private String id;
   private String name;
   private String address;
   private List<Student> students;

   // setters and getters
}

Teraz, gdy ładujesz uniwersytet z bazy danych, JPA ładuje dla ciebie pola identyfikatora, nazwy i adresu. Ale masz dwie opcje dotyczące sposobu ładowania uczniów:

  1. Aby załadować go razem z resztą pól (tj. Chętnie), lub
  2. Aby załadować go na żądanie (tj. Leniwie), gdy wywołasz getStudents()metodę uniwersytecką .

Gdy uniwersytet ma wielu studentów, nie jest efektywnie ładować wszystkich studentów razem z nim, szczególnie gdy nie są oni potrzebni, aw takich przypadkach możesz zadeklarować, że chcesz, aby studenci zostali załadowani, gdy są faktycznie potrzebni. Nazywa się to leniwym ładowaniem.

Oto przykład, w którym studentsjest wyraźnie zaznaczony do szybkiego załadowania:

@Entity
public class University {

    @Id
    private String id;

    private String name;

    private String address;

    @OneToMany(fetch = FetchType.EAGER)
    private List<Student> students;

    // etc.    
}

A oto przykład, w którym studentsjest wyraźnie zaznaczony do załadowania leniwie:

@Entity
public class University {

    @Id
    private String id;

    private String name;

    private String address;

    @OneToMany(fetch = FetchType.LAZY)
    private List<Student> students;

    // etc.
}

5
@ BehrangSaeedzadeh można wymienić niektóre praktyczne różnice lub zalety i wady każdego rodzaju załadunku (inne niż wspomniana wydajność). Dlaczego warto korzystać z szybkiego ładowania?
ADTC

73
@ ADTC Aby opóźnione ładowanie działało, sesja JDBC musi być nadal otwarta, gdy jednostki docelowe chcą zostać załadowane do pamięci przez wywołanie metody pobierającej (np. getStudents()), Ale czasami nie jest to możliwe, ponieważ do czasu tej metody jest wywoływana, sesja jest już zamknięta, a jednostka odłączona. Podobnie, czasami mamy architekturę klient / serwer (np. Klient Swing / serwer JEE), a podmioty / DTO są przesyłane przewodowo do klienta i ponownie najczęściej w tych scenariuszach leniwe ładowanie nie będzie działać ze względu na sposób, w jaki podmioty są serializowane za pomocą drutu.
TheFooProgrammer

4
Chciałbym dodać więcej informacji do tej odpowiedzi z mojej książki - Aby zaoszczędzić pamięć, Leniwe ładowanie jest zwykle używane dla relacji jeden do wielu i wielu do wielu. W stosunku jeden do jednego zwykle używany jest chętny.
Erran Morad

2
Czy podczas leniwego ładowania, gdy wywołuję getStudents()metodę po raz pierwszy, wyniki są buforowane? aby następnym razem uzyskać szybszy dostęp do tych wyników?
JavaTechnical

2
@JavaTechnical zależy od tego, czy włączysz pamięć podręczną drugiego poziomu (domyślnie włączona)
Ced

285

Gruntownie,

LAZY = fetch when needed
EAGER = fetch immediately

11
Bardzo jasne, ale dopiero po przeczytaniu odpowiedzi @ Behanga. Dziękuję za jasne podsumowanie. :-)
Nabin

66

EAGERładowanie kolekcji oznacza, że ​​są one pobierane w całości w momencie pobierania ich rodzica. Więc jeśli masz Coursei tak List<Student>, wszyscy studenci są pobierani z bazy danych w momencie Coursepobierania.

LAZYz drugiej strony oznacza, że ​​zawartość Listjest pobierana tylko wtedy, gdy próbujesz uzyskać do nich dostęp. Na przykład dzwoniąc course.getStudents().iterator(). Wywołanie dowolnej metody dostępu Listzainicjuje wywołanie bazy danych w celu pobrania elementów. Jest to realizowane przez utworzenie proxy wokół List(lub Set). Tak więc dla twoich leniwych kolekcji konkretne typy nie są ArrayListi HashSet, ale PersistentSeti PersistentList(lub PersistentBag)


Użyłem tej koncepcji przy pobieraniu szczegółów encji podrzędnej, ale nie widzę między nimi żadnej różnicy. Kiedy określę Chętne pobieranie, Pobiera wszystko, a kiedy debuguję, widzę „Odroczoną fasolę” w encji podrzędnej. Kiedy mówię course.getStudents(), uruchamia zapytanie SQL (widziałem to na konsoli). W typie pobierania Lazy również dzieje się to samo. Więc jaka jest różnica?
Neha Choudhary

chętne kolekcje są pobierane, gdy ładowana jest jednostka będąca właścicielem. Leniwe kolekcje są pobierane podczas uzyskiwania do nich dostępu. Jeśli to nie jest zachowanie, które widziałeś, prawdopodobnie coś było nie tak z twoim środowiskiem (np. Uruchamianie starych wersji klasy)
Bozho

1
@Bozho Podałeś tylko leniwe ładowanie kolekcji. Czy proste pole ciągu może być ładowane leniwie?
vikiiii

Nie. Musisz użyć zapytania lub innej zamapowanej encji, aby uzyskać podzbiór kolumn
Bozho

@Bozho, hej, czy możesz odpowiedzieć na to pytanie, jeśli jest ustawiony na fetchtype = LAZYdomyślny, nawet jeśli spróbujesz pobrać kolekcję za pomocą gettera hibernety zgłasza błąd informujący, że nie może ocenić
Все Едно

16

Mogę rozważyć wydajność i wykorzystanie pamięci. Jedną dużą różnicą jest to, że strategia pobierania EAGER pozwala na użycie pobranego obiektu danych bez sesji. Dlaczego?
Wszystkie dane są pobierane, gdy chętnie zaznaczone dane w obiekcie, gdy sesja jest połączona. Jednak w przypadku leniwej strategii ładowania, leniwe ładowanie zaznaczonego obiektu nie pobiera danych, jeśli sesja zostanie rozłączona (po session.close()instrukcji). Wszystko, co można zrobić przez hibernację proxy. Szybka strategia pozwala, aby dane były nadal dostępne po sesji zamknięcia.


11

Zgodnie z moją wiedzą oba rodzaje pobierania zależą od wymagań.

FetchType.LAZY jest na żądanie (tj. kiedy potrzebowaliśmy danych).

FetchType.EAGER jest natychmiastowy (tzn. zanim nadejdzie nasz wymóg, niepotrzebnie pobieramy rekord)


11

Domyślnie dla wszystkich obiektów kolekcji i map obowiązuje reguła pobierania, FetchType.LAZYaw innych przypadkach jest zgodna z FetchType.EAGERzasadami.
W skrócie, @OneToManya @ManyToManystosunki nie sprowadzić powiązanych obiektów (zbieranie i map) implicictly ale odzyskiwanie operacja jest kaskadowo przez pola w @OneToOnei @ManyToOnete.

(dzięki uprzejmości: - objectdbcom)


9

Oba FetchType.LAZYi FetchType.EAGERsłużą do zdefiniowania domyślnego planu pobierania .

Niestety możesz zastąpić tylko domyślny plan pobierania dla LAZY pobierania. Pobieranie EAGER jest mniej elastyczne i może prowadzić do wielu problemów z wydajnością .

Radzę powstrzymać potrzebę tworzenia stowarzyszeń EAGER, ponieważ pobieranie jest obowiązkiem czasu zapytania. Dlatego wszystkie zapytania powinny korzystać z dyrektywy pobierania, aby pobrać tylko to, co jest konieczne w bieżącym uzasadnieniu biznesowym.


2
„Pobieranie EAGER jest mniej elastyczne i może prowadzić do wielu problemów z wydajnością”. ... Prawdziwszym stwierdzeniem jest: „Używanie lub nieużywanie pobierania EAGER może prowadzić do problemów z wydajnością”. W tym szczególnym przypadku, gdy leniwie zainicjowane pole jest drogie i ORAZ rzadko używane, leniwe pobieranie poprawia wydajność. Ale w przypadku, gdy zmienna jest często używana, opóźniona inicjalizacja może faktycznie obniżyć wydajność , wymagając więcej podróży do bazy danych niż szybkiej inicjalizacji. Sugerowałbym zastosowanie FetchType poprawnie, a nie dogmatycznie.
scottb

Promujesz tutaj swoją książkę !! Ale tak, uważam, że zależy to od przypadku użycia i wielkości obiektu, o której mowa w relacji liczności.
John Doe

6

Z Javadoc :

Strategia EAGER jest wymogiem w środowisku wykonawczym dostawcy trwałości, że dane muszą być chętnie pobierane. Strategia LAZY jest wskazówką dla środowiska wykonawczego dostawcy trwałości, że dane powinny być pobierane leniwie przy pierwszym dostępie.

Np. Chętny jest bardziej proaktywny niż leniwy. Leniwy zdarza się tylko przy pierwszym użyciu (jeśli dostawca skorzysta z podpowiedzi), podczas gdy z niecierpliwymi rzeczami (może) zostać wstępnie pobrany.


1
co rozumiesz przez „pierwsze użycie”?
Leon

@leon: Powiedzmy, że masz byt z chętnym polem i leniwym polem. Kiedy otrzymasz encję, chętne pole zostanie załadowane z bazy danych do czasu otrzymania referencji encji, ale leniwe pole mogło nie być. Zostałby pobrany tylko wtedy, gdy spróbujesz uzyskać dostęp do pola za pośrednictwem jego akcesorium.
TJ Crowder

@ TJ Crowder, jakie jest ustawienie domyślne, gdy nie określono typu pobierania?
Mahmoud Saleh,

@MahmoudSaleh: Nie mam pojęcia. Prawdopodobnie różni się w zależności od czegoś. Nie korzystałem z JPA w prawdziwym projekcie, więc nie wdałem się w jego sedno.
TJ Crowder

2
@MahmoudS: Domyślne typy pobierania: OneToMany: LAZY, ManyToOne: EAGER, ManyToMany: LAZY, OneToOne: EAGER, Kolumny: EAGER
Markus Pscheidt

5

Typ Lazypobierania jest domyślnie wybierany przez Hibernacja, chyba że wyraźnie zaznaczysz Eagertyp pobierania. Aby być bardziej dokładnym i zwięzłym, różnicę można określić jak poniżej.

FetchType.LAZY = To nie ładuje relacji, chyba że wywołasz je metodą gettera.

FetchType.EAGER = To ładuje wszystkie relacje.

Plusy i minusy tych dwóch typów pobierania.

Lazy initialization poprawia wydajność, unikając niepotrzebnych obliczeń i zmniejszając wymagania dotyczące pamięci.

Eager initialization zajmuje więcej pamięci, a szybkość przetwarzania jest niska.

To powiedziawszy, zależy od sytuacji, w której można zastosować jedną z tych inicjalizacji.


1
Należy zauważyć, że „nie ładuje relacji, chyba że wywołasz je metodą gettera”, a także moim zdaniem dość opóźnioną decyzję projektową… Właśnie natrafiłem na przypadek, w którym zakładałem, że pobierze go po uzyskaniu dostępu i tak nie było, ponieważ nie wywołałem w tym celu funkcji gettera. Nawiasem mówiąc, co stanowi funkcję „gettera”? Czy JPA odracza ładowanie właściwości, dopóki wywoływana funkcja nie getMemberbędzie dokładnie pasować do wzorca nazwy członka?
ToVine,

3

Book.java

        import java.io.Serializable;
        import javax.persistence.Column;
        import javax.persistence.Entity;
        import javax.persistence.GeneratedValue;
        import javax.persistence.GenerationType;
        import javax.persistence.Id;
        import javax.persistence.ManyToOne;
        import javax.persistence.Table;

        @Entity
        @Table(name="Books")
        public class Books implements Serializable{

        private static final long serialVersionUID = 1L;
        @Id
        @GeneratedValue(strategy=GenerationType.IDENTITY)
        @Column(name="book_id")
        private int id;
        @Column(name="book_name")
        private String name;

        @Column(name="author_name")
        private String authorName;

        @ManyToOne
        Subject subject;

        public Subject getSubject() {
            return subject;
        }
        public void setSubject(Subject subject) {
            this.subject = subject;
        }

        public int getId() {
            return id;
        }
        public void setId(int id) {
            this.id = id;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public String getAuthorName() {
            return authorName;
        }
        public void setAuthorName(String authorName) {
            this.authorName = authorName;
        }

        }

Subject.java

    import java.io.Serializable;
    import java.util.ArrayList;
    import java.util.List;
    import javax.persistence.CascadeType;
    import javax.persistence.Column;
    import javax.persistence.Entity;
    import javax.persistence.FetchType;
    import javax.persistence.GeneratedValue; 
    import javax.persistence.GenerationType;
    import javax.persistence.Id;
    import javax.persistence.OneToMany;
    import javax.persistence.Table;

    @Entity
    @Table(name="Subject")
    public class Subject implements Serializable{

    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="subject_id")
    private int id;
    @Column(name="subject_name")
    private String name;
    /**
    Observe carefully i have mentioned fetchType.EAGER. By default its is fetchType.LAZY for @OneToMany i have mentioned it but not required. Check the Output by changing it to fetchType.EAGER
    */

    @OneToMany(mappedBy="subject",cascade=CascadeType.ALL,fetch=FetchType.LAZY,
orphanRemoval=true)
    List<Books> listBooks=new ArrayList<Books>();

    public List<Books> getListBooks() {
        return listBooks;
    }
    public void setListBooks(List<Books> listBooks) {
        this.listBooks = listBooks;
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }

    }

HibernateUtil.java

import org.hibernate.SessionFactory;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.Configuration;
public class HibernateUtil {

 private static SessionFactory sessionFactory ;
 static {
    Configuration configuration = new Configuration();
    configuration.addAnnotatedClass (Com.OneToMany.Books.class);
    configuration.addAnnotatedClass (Com.OneToMany.Subject.class);
    configuration.setProperty("connection.driver_class","com.mysql.jdbc.Driver");
    configuration.setProperty("hibernate.connection.url", "jdbc:mysql://localhost:3306/hibernate");                                
    configuration.setProperty("hibernate.connection.username", "root");     
    configuration.setProperty("hibernate.connection.password", "root");
    configuration.setProperty("dialect", "org.hibernate.dialect.MySQLDialect");
    configuration.setProperty("hibernate.hbm2ddl.auto", "update");
    configuration.setProperty("hibernate.show_sql", "true");
    configuration.setProperty(" hibernate.connection.pool_size", "10");
    configuration.setProperty(" hibernate.cache.use_second_level_cache", "true");
    configuration.setProperty(" hibernate.cache.use_query_cache", "true");
    configuration.setProperty(" cache.provider_class", "org.hibernate.cache.EhCacheProvider");
    configuration.setProperty("hibernate.cache.region.factory_class" ,"org.hibernate.cache.ehcache.EhCacheRegionFactory");

   // configuration
    StandardServiceRegistryBuilder builder = new StandardServiceRegistryBuilder().applySettings(configuration.getProperties());
    sessionFactory = configuration.buildSessionFactory(builder.build());
 }
public static SessionFactory getSessionFactory() {
    return sessionFactory;
}
} 

Main.java

    import org.hibernate.Session;
    import org.hibernate.SessionFactory;

    public class Main {

    public static void main(String[] args) {
        SessionFactory factory=HibernateUtil.getSessionFactory();
        save(factory);
        retrieve(factory);

    }

     private static void retrieve(SessionFactory factory) {
        Session session=factory.openSession();
        try{
            session.getTransaction().begin();
            Subject subject=(Subject)session.get(Subject.class, 1);
            System.out.println("subject associated collection is loading lazily as @OneToMany is lazy loaded");

            Books books=(Books)session.get(Books.class, 1);
            System.out.println("books associated collection is loading eagerly as by default @ManyToOne is Eagerly loaded");
            /*Books b1=(Books)session.get(Books.class, new Integer(1));

            Subject sub=session.get(Subject.class, 1);
            sub.getListBooks().remove(b1);
            session.save(sub);
            session.getTransaction().commit();*/
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            session.close();
        }

        }

       private static void save(SessionFactory factory){
        Subject subject=new Subject();
        subject.setName("C++");

        Books books=new Books();
        books.setAuthorName("Bala");
        books.setName("C++ Book");
        books.setSubject(subject);

        subject.getListBooks().add(books);
        Session session=factory.openSession();
        try{
        session.beginTransaction();

        session.save(subject);

        session.getTransaction().commit();
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            session.close();
        }
    }

    }

Sprawdź metodę retrieve () w Main.java. Kiedy otrzymamy Temat, jego lista kolekcji Książki , opatrzone adnotacjami @OneToMany, zostaną załadowane leniwie. Ale z drugiej strony, Książki podobne stowarzyszenie odbioru przedmiotu , z uwagami @ManyToOne, ładunki eargerly (autorem jest [default][1]za @ManyToOne, fetchType=EAGER). Możemy zmienić zachowanie, umieszczając fetchType.EAGER na @OneToManySubject.java lub fetchType.LAZY na @ManyToOneBooks.java.


1

publiczne wyliczanie FetchType rozszerza java.lang.Enum Definiuje strategie pobierania danych z bazy danych. Strategia EAGER jest wymogiem w środowisku wykonawczym dostawcy trwałości, że dane muszą być chętnie pobierane. Strategia LAZY jest wskazówką dla środowiska wykonawczego dostawcy trwałości, że dane powinny być pobierane leniwie przy pierwszym dostępie. Implementacja może chętnie pobierać dane, dla których określono wskazówkę strategiczną LAZY. Przykład: @Basic (fetch = LAZY) chroniony ciąg getName () {return name; }

Źródło


1

Chcę dodać tę notatkę do tego, co powiedział „Kyung Hwan Min” powyżej.

Załóżmy, że używasz Spring Rest z tym prostym architektem:

Kontroler <-> Usługa <-> Repozytorium

I chcesz zwrócić niektóre dane do interfejsu, jeśli używasz FetchType.LAZY, otrzymasz wyjątek po zwróceniu danych do metody kontrolera, ponieważ sesja jest zamknięta w usłudze, więc JSON Mapper Objectnie można uzyskać danych.

Istnieją trzy typowe opcje rozwiązania tego problemu, w zależności od projektu, wydajności i dewelopera:

  1. Najłatwiejszy w użyciu FetchType.EAGER, aby sesja nadal żyła metodą kontrolera.
  2. Rozwiązania anty-wzorce , aby sesja była aktywna aż do zakończenia wykonywania, jest ogromnym problemem w systemie.
  3. Najlepszą praktyką jest stosowanie FetchType.LAZYmetody konwertera do przesyłania danych z Entityinnego obiektu danych DTOi wysyłania go do kontrolera, więc nie ma wyjątku, jeśli sesja zostanie zamknięta.


0

@ drop-shadow, jeśli używasz Hibernacji, możesz wywoływać Hibernate.initialize()po wywołaniu getStudents()metody:

Public class UniversityDaoImpl extends GenericDaoHibernate<University, Integer> implements UniversityDao {
    //...
    @Override
    public University get(final Integer id) {
        Query query = getQuery("from University u where idUniversity=:id").setParameter("id", id).setMaxResults(1).setFetchSize(1);
        University university = (University) query.uniqueResult();
        ***Hibernate.initialize(university.getStudents());***
        return university;
    }
    //...
}

0

LAZY: Leniwie pobiera encje potomne, tzn. W momencie pobierania encji nadrzędnej pobiera tylko proxy (utworzone przez cglib lub dowolne inne narzędzie) encji potomnych, a kiedy uzyskujesz dostęp do dowolnej właściwości encji podrzędnej, jest ona faktycznie pobierana przez hibernację.

EAGER: pobiera byty potomne wraz z rodzicem.

Aby lepiej zrozumieć, przejdź do dokumentacji Jboss lub możesz użyć hibernate.show_sql=trueaplikacji i sprawdzić zapytania wygenerowane przez hibernację.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.