Unterschied zwischen FetchType LAZY und EAGER in der Java Persistence API?

552

Ich bin ein Neuling in Java Persistence API und Hibernate.

Was ist der Unterschied zwischen FetchType.LAZYund FetchType.EAGERin der Java Persistence API?

Leon
quelle
1
Das EAGER-Laden von Sammlungen bedeutet, dass sie zum Zeitpunkt des Abrufs ihres übergeordneten Elements vollständig abgerufen werden. Während EAGER lädt, wird mein ganzes Kind geholt. Das untergeordnete Element wird in PersistentSet und PersistentList (oder PersistentBag) in der Persistent Bag abgerufen und als Array-Liste angezeigt. Ist es richtig? ..
Geetha

Antworten:

1064

Manchmal haben Sie zwei Entitäten und es gibt eine Beziehung zwischen ihnen. Beispielsweise könnte eine Entität aufgerufen Universityund eine andere Entität aufgerufen werden, Studentund eine Universität könnte viele Studenten haben:

Die Universitätsentität verfügt möglicherweise über einige grundlegende Eigenschaften wie ID, Name, Adresse usw. sowie über eine Sammlungseigenschaft namens "Studenten", die die Liste der Studenten für eine bestimmte Universität zurückgibt:

Eine Universität hat viele Studenten

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

   // setters and getters
}

Wenn Sie jetzt eine Universität aus der Datenbank laden, lädt JPA die Felder für ID, Name und Adresse für Sie. Sie haben jedoch zwei Möglichkeiten, wie Schüler geladen werden sollen:

  1. Um es zusammen mit dem Rest der Felder zu laden (dh eifrig), oder
  2. Laden Sie es bei Bedarf (dh träge), wenn Sie die getStudents()Methode der Universität aufrufen .

Wenn eine Universität viele Studenten hat, ist es nicht effizient, alle ihre Studenten zusammen mit ihnen zu laden, insbesondere wenn sie nicht benötigt werden. In solchen Fällen können Sie erklären, dass Studenten geladen werden sollen, wenn sie tatsächlich gebraucht werden. Dies wird als verzögertes Laden bezeichnet.

Hier ist ein Beispiel, in dem studentsexplizit markiert ist, dass es eifrig geladen werden soll:

@Entity
public class University {

    @Id
    private String id;

    private String name;

    private String address;

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

    // etc.    
}

Und hier ist ein Beispiel, in dem studentsexplizit markiert ist, dass es träge geladen werden soll:

@Entity
public class University {

    @Id
    private String id;

    private String name;

    private String address;

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

    // etc.
}
Behrang Saeedzadeh
quelle
5
@BehrangSaeedzadeh können Sie einige praktische Unterschiede oder die Vor- und Nachteile der einzelnen Ladungsarten (mit Ausnahme der von Ihnen genannten Effizienz) auflisten. Warum sollte man eifriges Laden verwenden wollen?
ADTC
73
@ADTC Damit das verzögerte Laden funktioniert, muss die JDBC-Sitzung noch geöffnet sein, wenn die Zielentitäten durch Aufrufen der Getter-Methode (z. B. getStudents()) in den Speicher geladen werden sollen. Dies ist jedoch manchmal nicht möglich, da diese Methode zu diesem Zeitpunkt nicht verfügbar ist aufgerufen wird, ist die Sitzung bereits geschlossen und die Entität getrennt. In ähnlicher Weise haben wir manchmal eine Client / Server-Architektur (z. B. Swing-Client / JEE-Server) und die Entitäten / DTOs werden über die Leitung an den Client übertragen, und in diesen Szenarien funktioniert das verzögerte Laden aufgrund der Art und Weise der Entitäten meistens nicht werden über das Kabel serialisiert.
TheFooProgrammer
4
Ich möchte dieser Antwort aus meinem Buch weitere Informationen hinzufügen. Um Speicherplatz zu sparen, wird das verzögerte Laden im Allgemeinen für eine bis viele und viele bis viele Beziehungen verwendet. Für eins zu eins wird im Allgemeinen Eager verwendet.
Erran Morad
2
Werden beim verzögerten Laden getStudents()die Ergebnisse zwischengespeichert , wenn ich die Methode zum ersten Mal aufrufe? damit ich beim nächsten Mal schneller auf diese Ergebnisse zugreifen kann?
JavaTechnical
2
@ JavaTechnical hängt davon ab, ob Sie den Cache der zweiten Ebene aktivieren (standardmäßig aktiviert)
Ced
285

Grundsätzlich,

LAZY = fetch when needed
EAGER = fetch immediately
unbeli
quelle
11
Sehr klar, aber erst nach dem Lesen von @ Behangs Antwort. Vielen Dank für eine klare Zusammenfassung. :-)
Nabin
66

EAGERDas Laden von Sammlungen bedeutet, dass sie zum Zeitpunkt des Abrufs ihres übergeordneten Elements vollständig abgerufen werden. Wenn Sie also haben Courseund haben List<Student>, werden alle Schüler zum Zeitpunkt des Abrufs aus der DatenbankCourse abgerufen.

LAZYAuf der anderen Seite bedeutet dies, dass der Inhalt von Listnur abgerufen wird, wenn Sie versuchen, darauf zuzugreifen. Zum Beispiel durch einen Anruf course.getStudents().iterator(). Durch Aufrufen einer beliebigen Zugriffsmethode auf Listwird ein Aufruf der Datenbank zum Abrufen der Elemente ausgelöst. Dies wird implementiert, indem ein Proxy um das List(oder Set) erstellt wird. Für Ihre faulen Sammlungen sind die konkreten Typen also nicht ArrayListund HashSet, sondern PersistentSetund PersistentList(oder PersistentBag)

Bozho
quelle
Ich habe dieses Konzept verwendet, um die Details einer untergeordneten Entität abzurufen, aber ich kann keinen Unterschied zwischen ihnen feststellen. Wenn ich Eager Fetch spezifiziere, wird alles abgerufen, und wenn ich es debugge, wird bei der untergeordneten Entität "Bean deferred" angezeigt. Wenn ich sage course.getStudents(), wird eine SQL-Abfrage ausgelöst (siehe auf der Konsole). Auch beim Lazy-Fetch-Typ passiert dasselbe. Also, was ist der Unterschied?
Neha Choudhary
Eifrige Sammlungen werden abgerufen, wenn die besitzende Entität geladen wird. Faule Sammlungen werden abgerufen, wenn Sie darauf zugreifen. Wenn dies nicht das Verhalten ist, das Sie gesehen haben, ist wahrscheinlich etwas mit Ihrer Umgebung nicht in Ordnung (z. B. Ausführen alter Versionen einer Klasse)
Bozho
1
@Bozho Sie haben angegeben, dass nur Sammlungen verzögert geladen werden sollen. Kann ein einfaches Stringfeld faul geladen werden?
Vikiiii
Nein. Sie müssen eine Abfrage oder eine andere zugeordnete Entität verwenden, um eine Teilmenge der Spalten zu erhalten
Bozho,
@Bozho, hey, können Sie dies bitte beantworten, wenn es auf fetchtype = LAZYdie Standardeinstellung gesetzt ist, auch wenn der Versuch, die Sammlung mit dem Getter Hibernete zu erhalten, einen Fehler auslöst, der mir sagt, dass es nicht ausgewertet werden kann
Все Едно
16

Ich kann Leistung und Speichernutzung berücksichtigen. Ein großer Unterschied besteht darin, dass die EAGER-Abrufstrategie die Verwendung von abgerufenen Datenobjekten ohne Sitzung ermöglicht. Warum?
Alle Daten werden abgerufen, wenn eifrig markierte Daten im Objekt vorhanden sind, wenn die Sitzung verbunden ist. Im Falle einer Strategie zum verzögerten Laden ruft das markierte Objekt zum verzögerten Laden jedoch keine Daten ab, wenn die Sitzung getrennt wird (nach der session.close()Anweisung). All dies kann durch einen Ruhezustand-Proxy erfolgen. Mit einer eifrigen Strategie können Daten nach Abschluss der Sitzung weiterhin verfügbar sein.

Kyung Hwan min
quelle
11

Nach meinem Wissen hängen beide Arten des Abrufs von Ihren Anforderungen ab.

FetchType.LAZY ist auf Anfrage (dh wenn wir die Daten benötigt haben).

FetchType.EAGER ist unmittelbar (dh bevor unsere Anforderung kommt, holen wir den Datensatz unnötig ab)

JDGuide
quelle
11

Standardmäßig gilt für alle Sammlungs- und Zuordnungsobjekte die Abrufregel FetchType.LAZYund für andere Fälle folgt sie der FetchType.EAGERRichtlinie.
Kurz gesagt , @OneToManyund @ManyToManyBeziehungen rufen die zugehörigen Objekte (Sammlung und Karte) nicht implizit ab, sondern die Abrufoperation wird durch das Feld in @OneToOneund @ManyToOnediejenigen kaskadiert .

(mit freundlicher Genehmigung: - objectdbcom)

babai
quelle
9

Beide FetchType.LAZYund FetchType.EAGERwerden verwendet, um den Standardabrufplan zu definieren .

Leider können Sie nur den Standardabrufplan für das LAZY-Abrufen überschreiben. Das Abrufen von EAGER ist weniger flexibel und kann zu vielen Leistungsproblemen führen .

Mein Rat ist, den Drang zu unterdrücken, Ihre Assoziationen zu EAGER zu machen, da das Abrufen in der Verantwortung der Abfragezeit liegt. Daher sollten alle Ihre Abfragen die Abrufanweisung verwenden, um nur das abzurufen, was für den aktuellen Geschäftsfall erforderlich ist.

Vlad Mihalcea
quelle
2
"Das Abrufen von EAGER ist weniger flexibel und kann zu vielen Leistungsproblemen führen." ... Eine wahrere Aussage lautet: "Die Verwendung oder Nichtverwendung des EAGER-Abrufs kann zu Leistungsproblemen führen." In diesem speziellen Fall, in dem der Zugriff auf ein träge initialisiertes Feld teuer ist UND nur selten verwendet wird, wird das verzögerte Abrufen der Leistung zugute kommen. In dem Fall, in dem eine Variable häufig verwendet wird, kann eine verzögerte Initialisierung die Leistung tatsächlich beeinträchtigen, indem mehr Fahrten zur Datenbank erforderlich sind als eine eifrige Initialisierung. Ich würde vorschlagen, FetchType korrekt und nicht dogmatisch anzuwenden.
Scottb
Bewerben Sie Ihr Buch hier !!. Aber ja, ich denke, es hängt vom Anwendungsfall und der Objektgröße ab, auf die in der Kardinalitätsbeziehung Bezug genommen wird.
John Doe
6

Aus dem Javadoc :

Die EAGER-Strategie ist eine Anforderung an die Laufzeit des Persistenzanbieters, dass Daten eifrig abgerufen werden müssen. Die LAZY-Strategie ist ein Hinweis auf die Laufzeit des Persistenzanbieters, dass Daten beim ersten Zugriff träge abgerufen werden sollten.

ZB ist eifriger proaktiv als faul. Faulheit tritt nur beim ersten Gebrauch auf (wenn der Anbieter den Hinweis versteht), wohingegen bei eifrigen Dingen (möglicherweise) Dinge vorab abgerufen werden.

TJ Crowder
quelle
1
Was meinst du mit "Erstgebrauch"?
leon
@leon: Angenommen, Sie haben eine Entität mit einem eifrigen Feld und einem faulen Feld. Wenn Sie die Entität erhalten, wurde das eifrige Feld zum Zeitpunkt des Empfangs der Entitätsreferenz aus der Datenbank geladen, das verzögerte Feld jedoch möglicherweise nicht. Es wird nur abgerufen, wenn Sie versuchen, über seinen Accessor auf das Feld zuzugreifen .
TJ Crowder
@TJ Crowder, was ist die Standardeinstellung, wenn kein Fetchtyp definiert ist?
Mahmoud Saleh
@ MahmoudSaleh: Ich habe keine Ahnung. Es hängt wahrscheinlich von etwas ab. Ich habe JPA nicht in einem echten Projekt verwendet, also bin ich nicht in den Mut geraten.
TJ Crowder
2
@ MahmoudS: Standard-Fetchtypen: OneToMany: LAZY, ManyToOne: EAGER, ManyToMany: LAZY, OneToOne: EAGER, Spalten: EAGER
Markus Pscheidt
5

Der LazyAbruftyp wird standardmäßig im Ruhezustand ausgewählt, es sei denn, Sie markieren den EagerAbruftyp explizit . Um genauer und prägnanter zu sein, kann der Unterschied wie folgt angegeben werden.

FetchType.LAZY = Dadurch werden die Beziehungen nur geladen, wenn Sie sie über die Getter-Methode aufrufen.

FetchType.EAGER = Dies lädt alle Beziehungen.

Vor- und Nachteile dieser beiden Abruftypen.

Lazy initialization Verbessert die Leistung, indem unnötige Berechnungen vermieden und der Speicherbedarf reduziert werden.

Eager initialization nimmt mehr Speicherplatz in Anspruch und die Verarbeitungsgeschwindigkeit ist langsam.

Having said that, hängt von der Situation entweder eines dieser Initialisierung verwendet werden.

Dulith De Costa
quelle
1
Die Aussage, dass es "die Beziehungen nicht lädt, es sei denn, Sie rufen es über die Getter-Methode auf", ist wichtig zu beachten, und meiner Meinung nach auch eine ziemlich verzögerte Entwurfsentscheidung ... Ich bin gerade auf einen Fall gestoßen, in dem ich davon ausgegangen bin, dass es sie beim Zugriff und abrufen würde es tat es nicht, weil ich nicht explizit eine Getter-Funktion dafür aufrief. Was macht übrigens eine "Getter" -Funktion aus? Verschiebt JPA das Laden der Eigenschaft, bis eine aufgerufene Funktion aufgerufen getMemberwird, die genau dem Namensmuster des Mitglieds entspricht?
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();
        }
    }

    }

Überprüfen Sie die Methode retrieve () von Main.java. Wenn wir den Betreff erhalten , werden die mit Anmerkungen versehenen Sammlungslistenbücher träge@OneToMany geladen. Aber auf der anderen Seite, Bücher im Zusammenhang Vereinigung der Sammlung Themas , mit Anmerkungen versehen mit @ManyToOne, Lasten eargerly (von [default][1]für @ManyToOne, fetchType=EAGER). Wir können das Verhalten ändern, indem wir fetchType.EAGER auf @OneToManySubject.java oder fetchType.LAZY auf @ManyToOneBooks.java setzen.

Deepak
quelle
1

public enum FetchType erweitert java.lang.Enum Definiert Strategien zum Abrufen von Daten aus der Datenbank. Die EAGER-Strategie ist eine Anforderung an die Laufzeit des Persistenzanbieters, dass Daten eifrig abgerufen werden müssen. Die LAZY-Strategie ist ein Hinweis auf die Laufzeit des Persistenzanbieters, dass Daten beim ersten Zugriff träge abgerufen werden sollten. Die Implementierung darf eifrig Daten abrufen, für die der LAZY-Strategiehinweis angegeben wurde. Beispiel: @Basic (fetch = LAZY) protected String getName () {return name; }}

Quelle

Anas Lachheb
quelle
1

Ich möchte diese Notiz zu dem hinzufügen, was "Kyung Hwan Min" oben gesagt hat.

Angenommen, Sie verwenden Spring Rest mit diesem einfachen Architekten:

Controller <-> Service <-> Repository

Wenn Sie einige Daten an das Front-End zurückgeben möchten, erhalten Sie bei Verwendung FetchType.LAZYeine Ausnahme, nachdem Sie Daten an die Controller-Methode zurückgegeben haben, da die Sitzung im Service geschlossen JSON Mapper Objectist und die Daten nicht abgerufen werden können.

Es gibt drei gängige Optionen zur Lösung dieses Problems, die vom Design, der Leistung und dem Entwickler abhängen:

  1. Am einfachsten ist die Verwendung FetchType.EAGER, damit die Sitzung bei der Controller-Methode noch aktiv ist.
  2. Anti-Pattern- Lösungen, um die Sitzung bis zum Ende der Ausführung live zu schalten, verursachen ein großes Leistungsproblem im System.
  3. Die beste Vorgehensweise besteht darin, FetchType.LAZYmit der Konvertermethode Daten von Entityeinem anderen Datenobjekt zu übertragen DTOund an den Controller zu senden. Es gibt also keine Ausnahme, wenn die Sitzung geschlossen wird.
Ebraheem Alrabee '
quelle
1

Hallo, ich habe 2 Fotos angehängt, um Ihnen das Verständnis zu erleichtern. Geben Sie hier die Bildbeschreibung ein

Geben Sie hier die Bildbeschreibung ein

Ali Yeganeh
quelle
0

@ drop-shadow Wenn Sie den Ruhezustand verwenden, können Hibernate.initialize()Sie beim Aufrufen der getStudents()Methode Folgendes aufrufen :

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;
    }
    //...
}
Jules Martel
quelle
0

LAZY: Es ruft die untergeordneten Entitäten träge ab, dh zum Zeitpunkt des Abrufs der übergeordneten Entität ruft es nur den Proxy (erstellt von cglib oder einem anderen Dienstprogramm) der untergeordneten Entitäten ab, und wenn Sie auf eine Eigenschaft der untergeordneten Entität zugreifen, wird es tatsächlich im Ruhezustand abgerufen.

EAGER: Es ruft die untergeordneten Entitäten zusammen mit den übergeordneten Entitäten ab.

Zum besseren Verständnis gehen Sie zur Jboss-Dokumentation oder verwenden Sie sie hibernate.show_sql=truefür Ihre App und überprüfen Sie die vom Ruhezustand ausgegebenen Abfragen.

user1157635
quelle