Différence entre FetchType LAZY et EAGER dans Java Persistence API?


554

Je suis un débutant pour Java Persistence API et Hibernate.

Quelle est la différence entre FetchType.LAZYet FetchType.EAGERdans l'API Java Persistence?


1
Le chargement EAGER des collections signifie qu'elles sont récupérées complètement au moment où leur parent est récupéré. Pendant le chargement EAGER, alors tout mon enfant est récupéré. L'enfant est récupéré dans PersistentSet et PersistentList (ou PersistentBag), à l'intérieur du sac persistant, il s'affiche sous forme de liste de tableaux. Est-ce correct ?? ..
geetha

Réponses:


1066

Parfois, vous avez deux entités et il y a une relation entre elles. Par exemple, vous pouvez avoir une entité appelée Universityet une autre entité appelée Studentet une université peut avoir plusieurs étudiants:

L'entité University peut avoir certaines propriétés de base telles que id, nom, adresse, etc. ainsi qu'une propriété de collection appelée student qui renvoie la liste des étudiants pour une université donnée:

Une université compte de nombreux étudiants

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

   // setters and getters
}

Désormais, lorsque vous chargez une université à partir de la base de données, JPA charge ses champs d'ID, de nom et d'adresse pour vous. Mais vous avez deux options pour charger les étudiants:

  1. Pour le charger avec le reste des champs (c'est-à-dire avec impatience), ou
  2. Pour le charger à la demande (c'est-à-dire paresseusement) lorsque vous appelez la getStudents()méthode de l'université .

Lorsqu'une université compte de nombreux étudiants, il n'est pas efficace de charger tous ses étudiants avec elle, en particulier lorsqu'ils ne sont pas nécessaires et dans ce cas, vous pouvez déclarer que vous souhaitez que les étudiants soient chargés lorsqu'ils sont réellement nécessaires. C'est ce qu'on appelle le chargement paresseux.

Voici un exemple, où studentsest explicitement marqué pour être chargé avec impatience:

@Entity
public class University {

    @Id
    private String id;

    private String name;

    private String address;

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

    // etc.    
}

Et voici un exemple où studentsest explicitement marqué pour être chargé paresseusement:

@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 pouvez-vous énumérer quelques différences pratiques, ou les avantages et inconvénients de chaque type de chargement (autre que l'efficacité que vous avez mentionnée). Pourquoi voudrait-on utiliser un chargement enthousiaste?
ADTC

73
@ADTC Pour que le chargement différé fonctionne, la session JDBC doit toujours être ouverte lorsque les entités cibles veulent être chargées dans la mémoire en appelant la méthode getter (par exemple getStudents()), mais parfois ce n'est pas possible, car au moment où cette méthode est appelée, la session est déjà fermée et l'entité détachée. De même, nous avons parfois une architecture client / serveur (par exemple Swing client / serveur JEE) et les entités / DTO sont transférées via le câble au client et, le plus souvent dans ces scénarios, le chargement paresseux ne fonctionnera pas en raison de la façon dont les entités sont sérialisés sur le fil.
TheFooProgrammer

4
Je voudrais ajouter plus d'informations à cette réponse de mon livre - Pour économiser de la mémoire, le chargement paresseux est généralement utilisé pour les relations une à plusieurs et plusieurs à plusieurs. Pour un à un, généralement Eager est utilisé.
Erran Morad

2
Dans le chargement paresseux, lorsque j'appelle la getStudents()méthode pour la première fois, les résultats sont-ils mis en cache? afin que je puisse accéder à ces résultats plus rapidement la prochaine fois?
JavaTechnical

2
@JavaTechnical dépend si vous activez le cache de deuxième niveau (activé par défaut)
Ced

285

Fondamentalement,

LAZY = fetch when needed
EAGER = fetch immediately

11
Très clair mais seulement après avoir lu la réponse de @ Behang. Merci pour un résumé clair. :-)
Nabin

66

EAGERle chargement des collections signifie qu'elles sont entièrement récupérées au moment où leur parent est récupéré. Donc, si vous l'avez fait Courseet que c'est le cas List<Student>, tous les étudiants sont extraits de la base de données au moment où ils Coursesont récupérés.

LAZYd'autre part signifie que le contenu du Listest récupéré uniquement lorsque vous essayez d'y accéder. Par exemple, en appelant course.getStudents().iterator(). L'appel de n'importe quelle méthode d'accès sur le Listlancera un appel à la base de données pour récupérer les éléments. Ceci est implémenté en créant un proxy autour du List(ou Set). Donc pour vos collections paresseuses, les types concrets ne sont pas ArrayListet HashSet, mais PersistentSetet PersistentList(ou PersistentBag)


J'ai utilisé ce concept pour récupérer les détails d'une entité enfant, mais je ne vois aucune différence entre eux. Lorsque je spécifie l'extraction désirée, il récupère tout et lorsque je le débogue, je vois "Bean différé" sur l'entité enfant. Quand je dis course.getStudents(), il déclenche une requête SQL (vu cela sur console). Dans le type Lazy fetch également, la même chose se produit. Alors, quelle est la différence ??
Neha Choudhary

les collections avides sont récupérées lorsque l'entité propriétaire est chargée. Les collections paresseuses sont récupérées lorsque vous y accédez. Si ce n'est pas le comportement que vous avez vu, il y a probablement eu un problème avec votre environnement (par exemple en exécutant les anciennes versions d'une classe)
Bozho

1
@Bozho Vous avez spécifié le chargement paresseux des collections uniquement. Un champ de chaîne simple peut-il être chargé paresseusement?
vikiiii

Non. Vous devez utiliser une requête ou une autre entité mappée pour obtenir un sous-ensemble des colonnes
Bozho

@Bozho, hé pouvez-vous s'il vous plaît répondre à cela alors s'il est défini sur fetchtype = LAZYcelui par défaut même si essayez d'obtenir la collection avec le getter hibernete jette une erreur me disant qu'il ne peut pas évaluer
Все Едно

16

Je peux considérer les performances et l'utilisation de la mémoire. Une grande différence est que la stratégie de récupération EAGER permet d'utiliser un objet de données récupéré sans session. Pourquoi?
Toutes les données sont extraites lorsqu'elles sont marquées avec empressement dans l'objet lorsque la session est connectée. Cependant, en cas de stratégie de chargement différé, l'objet marqué de chargement différé ne récupère pas les données si la session est déconnectée (après l' session.close()instruction). Tout cela peut être fait par un proxy d'hibernation. Une stratégie désireuse permet aux données d'être toujours disponibles après la fermeture de la session.


11

Selon ma connaissance, les deux types de récupération dépendent de vos besoins.

FetchType.LAZY est sur demande (c'est-à-dire lorsque nous avons demandé les données).

FetchType.EAGER est immédiat (c'est-à-dire avant que notre exigence ne vienne, nous récupérons inutilement l'enregistrement)


11

Par défaut, pour tous les objets de collection et de mappage, la règle d'extraction est FetchType.LAZYet pour les autres instances, elle suit la FetchType.EAGERpolitique.
En bref, @OneToManyet les @ManyToManyrelations ne récupèrent pas implicitement les objets associés (collection et carte) mais l'opération de récupération est répercutée en cascade dans le champ in @OneToOneet @ManyToOneones.

(courtoisie: - objectdbcom)


9

Les deux FetchType.LAZYet FetchType.EAGERsont utilisés pour définir le plan de récupération par défaut .

Malheureusement, vous ne pouvez remplacer que le plan de récupération par défaut pour la récupération paresseuse. La récupération EAGER est moins flexible et peut entraîner de nombreux problèmes de performances .

Mon conseil est de limiter l'envie de rendre vos associations EAGER car aller chercher est une responsabilité en temps de requête. Ainsi, toutes vos requêtes doivent utiliser la directive fetch pour récupérer uniquement ce qui est nécessaire pour l'analyse de rentabilisation actuelle.


2
"La récupération EAGER est moins flexible et peut entraîner de nombreux problèmes de performances." ... Une déclaration plus vraie est "Utiliser ou ne pas utiliser la récupération EAGER peut entraîner des problèmes de performances". Dans ce cas particulier, lorsqu'un champ initialisé paresseusement est coûteux à accéder ET rarement utilisé, la récupération paresseuse bénéficiera aux performances. Mais, dans le cas où une variable est fréquemment utilisée, l'initialisation paresseuse peut en fait dégrader les performances en nécessitant plus de déplacements vers la base de données qu'une initialisation rapide. Je suggère d'appliquer correctement FetchType, pas de manière dogmatique.
scottb

Faites-vous la promotion de votre livre ici !!. Mais oui, je pense que cela dépend du cas d'utilisation et de la taille de l'objet auquel il est fait référence dans la relation de cardinalité.
John Doe

6

Depuis le Javadoc :

La stratégie EAGER est une exigence sur le runtime du fournisseur de persistance que les données doivent être récupérées avec impatience. La stratégie LAZY indique au moteur d'exécution du fournisseur de persistance que les données doivent être récupérées paresseusement lors de leur premier accès.

Par exemple, désireux est plus proactif que paresseux. Lazy ne se produit que lors de la première utilisation (si le fournisseur prend le conseil), alors qu'avec les choses désireuses (peut) être récupérées à l'avance.


1
qu'entendez-vous par «première utilisation»?
leon

@leon: Supposons que vous ayez une entité avec un champ impatient et un champ paresseux. Lorsque vous obtenez l'entité, le champ désireux a été chargé à partir de la base de données au moment où vous recevez la référence d'entité, mais le champ paresseux ne l'a peut-être pas été. Il ne serait récupéré que lorsque vous tenteriez d' accéder au champ via son accesseur.
TJ Crowder

@TJ Crowder, quelle est la valeur par défaut lorsqu'aucun type d'extraction n'est défini?
Mahmoud Saleh

@MahmoudSaleh: Je n'en ai aucune idée. Cela varie probablement en fonction de quelque chose. Je n'ai pas utilisé JPA dans un vrai projet donc je ne suis pas entré dans les tripes.
TJ Crowder

2
@MahmoudS: Types de récupération par défaut: OneToMany: LAZY, ManyToOne: EAGER, ManyToMany: LAZY, OneToOne: EAGER, Colonnes: EAGER
Markus Pscheidt

5

Le Lazytype d'extraction est par défaut sélectionné par Hibernate sauf si vous marquez explicitement le Eagertype d'extraction. Pour être plus précis et concis, la différence peut être indiquée comme ci-dessous.

FetchType.LAZY = Cela ne charge pas les relations sauf si vous l'invoquez via la méthode getter.

FetchType.EAGER = Cela charge toutes les relations.

Avantages et inconvénients de ces deux types de récupération.

Lazy initialization améliore les performances en évitant les calculs inutiles et en réduisant les besoins en mémoire.

Eager initialization prend plus de mémoire et la vitesse de traitement est lente.

Cela dit, selon la situation, l'une ou l'autre de ces initialisations peut être utilisée.


1
Il est important de noter qu'il "ne charge pas les relations à moins que vous ne les invoquiez via la méthode getter", et aussi une décision de conception assez retardée à mon avis ... Je viens de rencontrer un cas où je supposais qu'il le récupérerait lors de l'accès et il ne l'a pas fait, car je n'ai pas appelé de manière explicite une fonction getter pour cela. Au fait, qu'est-ce qui constitue une fonction «getter»? JPA reportera-t-il le chargement de la propriété jusqu'à ce qu'une fonction appelée getMembersoit appelée qui corresponde exactement au modèle de nom du membre?
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();
        }
    }

    }

Vérifiez la méthode retrieve () de Main.java. Lorsque nous obtenons Subject, sa liste de collections Books , annotée avec @OneToMany, sera chargée paresseusement. Mais, d'autre part, l'association liée au sujet de la collection Books , annotée avec @ManyToOne, se charge avec précaution (par [default][1]pour @ManyToOne, fetchType=EAGER). Nous pouvons changer le comportement en plaçant fetchType.EAGER sur @OneToManySubject.java ou fetchType.LAZY @ManyToOnedans Books.java.


1

enum public FetchType étend java.lang.Enum Définit les stratégies de récupération des données de la base de données. La stratégie EAGER est une exigence sur le runtime du fournisseur de persistance que les données doivent être récupérées avec impatience. La stratégie LAZY indique au moteur d'exécution du fournisseur de persistance que les données doivent être récupérées paresseusement lors de leur premier accès. L'implémentation est autorisée à récupérer avec impatience les données pour lesquelles l'indicateur de stratégie LAZY a été spécifié. Exemple: @Basic (fetch = LAZY) protected String getName () {return name; }

La source


1

Je veux ajouter cette note à ce que "Kyung Hwan Min" a dit ci-dessus.

Supposons que vous utilisez Spring Rest avec cet architecte simple:

Contrôleur <-> Service <-> Référentiel

Et vous souhaitez renvoyer certaines données à l'interface, si vous utilisez FetchType.LAZY, vous obtiendrez une exception après avoir renvoyé des données à la méthode du contrôleur puisque la session est fermée dans le service afin que le JSON Mapper Objectne puisse pas obtenir les données.

Il existe trois options courantes pour résoudre ce problème, cela dépend de la conception, des performances et du développeur:

  1. Le plus simple est d'utiliser FetchType.EAGER, pour que la session soit toujours en vie à la méthode du contrôleur.
  2. Des solutions anti-patterns , pour faire vivre la session jusqu'à la fin de l'exécution, cela pose un énorme problème de performances dans le système.
  3. La meilleure pratique consiste à utiliser FetchType.LAZYavec la méthode du convertisseur pour transférer des données d' Entityun autre objet de données DTOet les envoyer au contrôleur, il n'y a donc aucune exception si la session est fermée.


0

@ drop-shadow si vous utilisez Hibernate, vous pouvez appeler Hibernate.initialize()lorsque vous appelez la getStudents()méthode:

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: Il récupère les entités enfants paresseusement, c'est-à-dire qu'au moment de la récupération de l'entité parent, il récupère simplement le proxy (créé par cglib ou tout autre utilitaire) des entités enfants et lorsque vous accédez à une propriété de l'entité enfant, il est en fait récupéré par hibernate.

EAGER: il récupère les entités enfants avec le parent.

Pour une meilleure compréhension, consultez la documentation Jboss ou vous pouvez utiliser hibernate.show_sql=truepour votre application et vérifier les requêtes émises par l'hibernate.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.