Java Persistence API中的FetchType LAZY和EAGER之间的区别?


551

我是Java Persistence API和Hibernate的新手。

FetchType.LAZYFetchType.EAGERJava Persistence API 之间有什么区别?


1
EAGER加载集合意味着在获取父项时将其完全获取。在加载EAGER时,我的所有孩子都被拿走了。在Persistent Bag内的PersistentSet和PersistentList(或PersistentBag)中提取该子项,该子项显示为数组列表。正确吗?..
geetha

Answers:


1063

有时您有两个实体,并且它们之间存在关系。例如,您可能有一个名为的实体University,另一个实体名为,Student而一所大学可能有很多学生:

University实体可能具有一些基本属性,例如id,名称,地址等,还有一个名为students的集合属性,该属性返回给定大学的学生列表:

一所大学有很多学生

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

   // setters and getters
}

现在,当您从数据库加载大学时,JPA会为您加载其ID,名称和地址字段。但是对于如何加载学生,您有两个选择:

  1. 要将其与其余字段一起加载(即急切地),或者
  2. 在调用大学的getStudents()方法时按需(即延迟)加载它。

当一所大学有很多学生时,将所有学生与其一起加载是没有效率的,尤其是在不需要他们的情况下,在这种情况下,您可以声明希望在实际需要他们的时候加载学生。这称为延迟加载。

这是一个示例,其中students将其明确标记为紧急加载:

@Entity
public class University {

    @Id
    private String id;

    private String name;

    private String address;

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

    // etc.    
}

这是一个示例,其中students显式标记为延迟加载:

@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可以列出一些实际的区别,或者每种加载类型的优缺点(除了您提到的效率)。为什么要使用紧急加载?
ADTC

73
@ADTC为了使延迟加载工作正常进行,当要通过调用getter方法(例如getStudents())将目标实体加载到内存中时,JDBC会话仍必须处于打开状态,但是有时这是不可能的,因为到此方法时调用时,会话已经关闭并且实体已分离。同样,有时我们具有客户端/服务器体系结构(例如,Swing客户端/ JEE服务器),并且实体/ DTO通过电线传输到客户端,并且在大多数情况下,由于实体的方式,延迟加载无法正常工作通过电线进行序列化。
TheFooProgrammer

4
我想在本书的答案中添加更多信息-为了节省内存,延迟加载通常用于一对多和多对多关系。对于一对一,通常使用Eager。
艾伦·莫拉德

2
在延迟加载中,当我getStudents()第一次调用该方法时,结果是否被缓存?以便下次可以更快地访问这些结果?
JavaTechnical 2014年

2
@JavaTechnical取决于是否启用二级缓存(默认情况下启用)
Ced

285

基本上,

LAZY = fetch when needed
EAGER = fetch immediately

11
非常清楚,但仅在阅读@Behang的答案之后。感谢您的明确总结。:-)
纳宾

66

EAGER集合的加载意味着在获取其父项时将其完全获取。所以,如果你有Course,它有 List<Student>,所有的学生都获取从数据库在当时Course被取出。

LAZY另一方面意味着List仅当您尝试访问时,才会获取的内容。例如,通过调用course.getStudents().iterator()。调用上的任何访问方法List将启动对数据库的调用以检索元素。这是通过在List(或Set)周围创建一个代理来实现的。因此,对于您的惰性集合,具体类型不是ArrayListHashSet,而是PersistentSetPersistentList(或PersistentBag


我在获取子实体的详细信息时使用了该概念,但看不到它们之间的任何区别。当我指定Eager fetch时,它将获取所有内容,当我调试它时,在子实体上看到“ Bean deferred”。当我说时course.getStudents(),它会触发一个SQL查询(在控制台上看到了)。在懒惰获取类型中,同样的事情也会发生。那么,有什么区别呢?
Neha Choudhary

加载拥有实体时,将立即获取早期集合。访问惰性集合时,将获取它们。如果这不是您所看到的行为,则您的环境可能存在问题(例如,运行旧版本的类)
Bozho 2013年

1
@Bozho您仅指定了集合的延迟加载。是否可以延迟加载简单的字符串字段?
vikiiii

否。您需要使用查询或其他映射的实体来获取列的子集
Bozho 2013年

@Bozho,嘿,你能不能请那么如果它的设定回答这个fetchtype = LAZY默认的,即使尝试获得与吸气hibernete集合抛出一个错误,告诉我它无法评估
ВсеЕдно

16

我可能会考虑性能和内存利用率。最大的不同是EAGER的提取策略允许无需会话就使用提取的数据对象。为什么?
连接会话时,如果在对象中渴望标记数据,则将提取所有数据。但是,在延迟加载策略的情况下,如果会话断开连接(session.close()语句之后),则延迟加载标记对象不会检索数据。休眠代理可以完成所有这些操作。急切的策略使数据在关闭会话后仍然可用。


11

据我所知,两种类型的访存都取决于您的要求。

FetchType.LAZY 是按需的(即当我们需要数据时)。

FetchType.EAGER 是即时的(即在我们的要求到来之前,我们不必要地获取记录)


11

默认情况下,对于所有收集和映射对象,提取规则均为该规则,FetchType.LAZY而对于其他实例,它遵循该FetchType.EAGER策略。
简言之,@OneToMany@ManyToMany关系不implicictly获取相关对象(采集和地图),但检索操作通过现场级联@OneToOne@ManyToOne的。

(礼貌:-objectdbcom)


9

双方FetchType.LAZYFetchType.EAGER用来定义默认提取计划

不幸的是,您只能为LAZY提取覆盖默认的提取计划。EAGER提取的灵活性较差,可能导致许多性能问题

我的建议是限制使关联变得更EAGER的冲动,因为获取是查询时的责任。因此,所有查询都应使用fetch指令来仅检索当前业务案例所需的内容。


2
“ EAGER的获取不够灵活,可能会导致许多性能问题。” ...更真实的说法是“使用或不使用EAGER访存可能会导致性能问题”。在那种特殊情况下,当延迟初始化的字段访问起来昂贵且不经常使用时,延迟获取将有利于性能。但是,在频繁使用变量的情况下,延迟初始化实际上会比急切的初始化需要更多的数据库访问次数,从而实际上降低了性能。我建议正确而不是教条地应用FetchType。
斯科特,

您在这里宣传您的书吗!!但是是的,我觉得这取决于用例,以及基数关系中引用的对象大小。
John Doe

6

Javadoc

EAGER策略是对持久性提供程序运行时的要求,必须热切地获取数据。LAZY策略向持久性提供程序运行时提供了提示,即首次访问数据时应延迟获取数据。

例如,渴望比懒惰更积极主动。懒惰仅在首次使用时发生(如果提供者接受了提示),而急切的事情(可能)会被预先获取。


1
“首次使用”是什么意思?
里昂2010年

@leon:假设您有一个带有渴望字段和懒惰字段的实体。当您获得实体时,到收到实体引用时,渴望字段将已从DB装入,但是惰性字段可能尚未被加载。仅当您尝试通过字段访问器访问该字段时,才会提取该字段。
TJ Crowder 2010年

@TJ Crowder,未定义fetchtype时的默认值是什么?
Mahmoud Saleh 2012年

@MahmoudSaleh:我不知道。它可能会因某些情况而有所不同。我没有在实际的项目中使用过JPA,所以我还没有深入了解它。
TJ Crowder 2012年

2
@MahmoudS:默认提取类型:OneToMany:LAZY,ManyToOne:EAGER,ManyToMany:LAZY,OneToOne:EAGER,列:EAGER
Markus Pscheidt 2015年

5

Lazy提取类型默认情况下由Hibernate选择,除非你明确地标记Eager取型。为了更加准确和简洁,可以将差异说明如下。

FetchType.LAZY =除非您通过getter方法调用关系,否则不会加载关系。

FetchType.EAGER =这将加载所有关系。

这两种类型的利弊。

Lazy initialization 通过避免不必要的计算来提高性能并减少内存需求。

Eager initialization 需要更多的内存消耗并且处理速度很慢。

话虽如此,根据情况,可以使用这些初始化中的任何一种。


1
需要特别注意的一点是,“除非您通过getter方法调用它,否则不会加载关系”,这在我看来还是很迟钝的设计决策……我只是遇到了一种情况,即我假设它将在访问和获取获取它它没有,因为我没有为此专门调用getter函数。顺便说一下,什么构成“吸气剂”功能?JPA是否将延迟加载属性,直到getMember调用一个与成员名称模式完全匹配的函数为止?
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();
        }
    }

    }

检查Main.java的retrieve()方法。当我们得到Subject时,它的集合listBooks(用注释@OneToMany)将被延迟加载。但是,另一方面,与图书相关的馆藏主题关联,用标注@ManyToOne,则更早地加载(通过[default][1]for @ManyToOnefetchType=EAGER)。我们可以通过在@OneToManySubject.java上放置fetchType.EAGER 或在Books.java上放置fetchType.LAZY来更改行为@ManyToOne


1

公共枚举FetchType扩展java.lang.Enum定义用于从数据库中获取数据的策略。EAGER策略是对持久性提供程序运行时的要求,必须热切地获取数据。LAZY策略向持久性提供程序运行时提供了提示,即首次访问数据时应延迟获取数据。该实现被允许急切地获取已为其指定LAZY策略提示的数据。示例:@Basic(fetch = LAZY)受保护的字符串getName(){返回名称;}

资源


1

我想将此注释添加到上面的“庆焕民”中。

假设您正在使用带有此简单架构师的Spring Rest:

控制器<->服务<->存储库

而且您想将一些数据返回到前端,如果您正在使用FetchType.LAZY,则在将数据返回到controller方法后,由于服务中的会话已关闭,因此JSON Mapper Object无法获取数据,因此您将获得异常。

有三种常见的解决方案,取决于设计,性能和开发人员:

  1. 最简单的方法是使用FetchType.EAGER,以使该会话在控制器方法下仍然有效。
  2. 反模式解决方案,要使会话一直存在直到执行结束,这在系统中是一个巨大的性能问题。
  3. 最佳实践是FetchType.LAZY与转换器方法一起使用,以将数据从Entity另一个数据对象传输DTO并将其发送到控制器,因此,如果会话关闭,也不例外。


0

@ drop-shadow(如果使用的是Hibernate),则可以Hibernate.initialize()在调用该getStudents()方法时进行调用:

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:延迟获取子实体,即,在获取父实体时,它仅获取子实体的代理(由cglib或任何其他实用程序创建),并且当您访问子实体的任何属性时,它实际上是由休眠方式获取的。

EAGER:它会与父级一起获取子级实体。

为了更好的理解,请转到Jboss文档,或者您可以将其hibernate.show_sql=true用于您的应用程序并检查由休眠发出的查询。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.