ChatGPT解决这个技术问题 Extra ChatGPT

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

我是 Java Persistence API 和 Hibernate 的新手。

Java Persistence API 中的 FetchType.LAZYFetchType.EAGER 有什么区别?

EAGER 加载集合意味着它们在获取父级时被完全获取。在 EAGER 加载时,我所有的孩子都被取走了。在 Persistent Bag 内部的 PersistentSet 和 PersistentList(或 PersistentBag)中获取子项,它显示为一个数组列表。这是对的吗??..

B
Behrang

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

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

https://i.stack.imgur.com/N1PL3.png

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

   // setters and getters
}

现在,当您从数据库中加载大学时,JPA 会为您加载其 id、name 和 address 字段。但是对于如何加载学生,您有两种选择:

将其与其他字段一起加载(即急切地),或者当您调用大学的 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.
}

@BehrangSaeedzadeh 您能否列出一些实际差异,或者每种加载类型的优缺点(除了您提到的效率)。为什么要使用急切加载?
@ADTC 为了使延迟加载工作,当目标实体想要通过调用 getter 方法(例如 getStudents())加载到内存中时,JDBC 会话必须仍然打开,但有时这是不可能的,因为通过调用此方法时,会话已经关闭并且实体已分离。类似地,有时我们有一个客户端/服务器架构(例如 Swing 客户端/JEE 服务器),并且实体/DTO 通过线路传输到客户端,并且在这些场景中,由于实体的方式,延迟加载通常不起作用通过网络进行序列化。
我想从我的书中为这个答案添加更多信息 - 为了节省内存,延迟加载通常用于一对多和多对多关系。一对一,一般使用Eager。
在延迟加载中,当我第一次调用getStudents()方法时,结果是否被缓存?这样我下次可以更快地访问这些结果?
@JavaTechnical 取决于您是否启用二级缓存(默认启用)
u
unbeli

基本上,

LAZY = fetch when needed
EAGER = fetch immediately

B
Bozho

EAGER 集合的加载意味着它们在其父项被提取时被完全提取。因此,如果您有 Course 并且它有 List<Student>,则在提取 Course 时会从数据库中提取所有学生

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


我在获取子实体的详细信息时使用了这个概念,但我看不出它们之间有任何区别。当我指定 Eager fetch 时,它会获取所有内容,当我调试它时,我在子实体中看到“Bean deferred”。当我说 course.getStudents() 时,它会触发一个 SQL 查询(在控制台上看到)。在 Lazy fetch 类型中,同样的事情也会发生。那么,有什么区别呢??
加载拥有实体时会获取急切的集合。访问惰性集合时会获取它们。如果这不是您看到的行为,那么您的环境可能有问题(例如运行旧版本的类)
@Bozho 您只指定了延迟加载集合。可以延迟加载简单的字符串字段吗?
否。您需要使用查询或不同的映射实体来获取列的子集
@Bozho,嘿,你能回答这个问题,如果它设置在 fetchtype = LAZY 上,即使尝试使用 getter hibernete 获取集合也会抛出一个错误,告诉我它无法评估
r
rtruszk

我可能会考虑性能和内存利用率。一个很大的区别是 EAGER 获取策略允许在没有会话的情况下使用获取的数据对象。为什么?
连接会话时,当对象中的 Eager 标记数据时,会获取所有数据。但是,在延迟加载策略的情况下,如果会话断开(在 session.close() 语句之后),延迟加载标记的对象不会检索数据。所有这些都可以通过休眠代理完成。 Eager 策略让数据在关闭会话后仍然可用。


是的,它使我的应用程序崩溃,直到我切换到渴望。我想会话管理本身就是一门艺术和科学,但 JawsDB 在免费层提供 10 个连接,而在付费层则不多。
A
Ali Yeganeh

两种获取类型的主要区别在于数据加载到内存中的时刻。我附上了两张照片以帮助您理解这一点。

https://i.stack.imgur.com/ws0yq.jpg

https://i.stack.imgur.com/e0EFV.jpg


r
rtruszk

默认情况下,对于所有集合和映射对象,获取规则是 FetchType.LAZY,而对于其他实例,它遵循 FetchType.EAGER 策略。
简而言之,@OneToMany@ManyToMany 关系不会获取相关对象(集合和 map) 隐含但检索操作通过 @OneToOne@ManyToOne 中的字段级联。

(courtesy :- objectdbcom)


J
JDGuide

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

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

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


V
Vlad Mihalcea

FetchType.LAZYFetchType.EAGER 都用于定义默认提取计划。

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

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


“EAGER fetching 不太灵活,可能会导致许多性能问题。” ...更真实的说法是“使用或不使用 EAGER 获取可能会导致性能问题”。在这种特殊情况下,当延迟初始化的字段访问成本高且不经常使用时,延迟获取将提高性能。但是,在频繁使用变量的情况下,延迟初始化实际上会降低性能,因为它需要比急切初始化更多的访问数据库。我建议正确地应用 FetchType,而不是教条。
你在这里推销你的书吗!!但是是的,我觉得这取决于用例,以及基数关系中提到的对象大小。
D
Du-Lacoste

除非您明确标记 Eager Fetch 类型,否则 Hibernate 默认选择 Lazy Fetch 类型。为了更准确和简洁,可以将差异表述如下。

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

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

这两种获取类型的优缺点。

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

Eager initialization 占用内存较多,处理速度较慢。

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


它“除非您通过 getter 方法调用它,否则不会加载关系”的声明很重要,并且在我看来也是一个相当迟钝的设计决策......我刚刚遇到了一个我认为它会获取它的案例 在访问时 并没有,因为我没有明确地为它调用getter 函数。顺便问一下,什么是“getter”函数? JPA 是否会推迟加载属性,直到调用与成员名称模式完全匹配的名为 getMember 的函数?
E
Ebraheem Alrabeea

我想将此注释添加到上面所说的内容中。

假设您使用 Spring(MVC 和数据)和这个简单的架构师:

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

如果您使用 FetchType.LAZY,您希望将一些数据返回到前端,您将在将数据返回到控制器方法后得到一个 LazyInitializationException,因为会话在 Service 中关闭,因此 JSON Mapper Object 可以获取数据。

根据设计、性能和开发人员的不同,有两种常见的选项可以解决此问题:

最简单的一种是使用 FetchType.EAGER 或任何其他反模式解决方案,这样会话在控制器方法处仍然是活动的,但这些方法会影响性能。最佳实践是使用 FetchType.LAZY 和映射器(如 MapStruct)将数据从 Entity 传输到另一个数据对象 DTO,然后将其发送回控制器,因此如果会话关闭也不例外。

有一个简单的例子:

@RestController
@RequestMapping("/api")
public class UserResource {

    @GetMapping("/users")
    public Page<UserDTO> getAllUsers(Pageable pageable) {
        return userService.getAllUsers(pageable);
    }
}

@Service
@Transactional
public class UserService {

    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Transactional(readOnly = true)
    public Page<UserDTO> getAllUsers(Pageable pageable) {
        return userRepository.findAll(pageable).map(UserDTO::new);
    }
}

@Repository
public interface UserRepository extends JpaRepository<User, String> {

    Page<User> findAll(Pageable pageable);
}

public class UserDTO {

    private Long id;

    private String firstName;

    private String lastName;

    private String email;
    
    private Set<String> addresses;

    public UserDTO() {
        // Empty constructor needed for Jackson.
    }

    public UserDTO(User user) {
        this.id = user.getId();
        this.firstName = user.getFirstName();
        this.lastName = user.getLastName();
        this.email = user.getEmail();
        this.addresses = user.getAddresses().stream()
            .map(Address::getAddress)
            .collect(Collectors.toSet());
    }

    // Getters, setters, equals, and hashCode
}

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

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column
    private String firstName;

    @Column
    private String lastName;

    @Column(unique = true)
    private String email;
    
    @OneToMany(mappedBy = "address", fetch = FetchType.LAZY)
    private Set<Address> addresses = new HashSet<>();

    // Getters, setters, equals, and hashCode
}

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

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column
    private String address;

    @ManyToOne
    @JsonIgnoreProperties(value = "addresses", allowSetters = true)
    private User user;

    // Getters, setters, equals, and hashCode
}

您能否举例说明如何实施第三种解决方案,例如在@Behrang 的回答中?
@Naik,我已经更新了答案并添加了一个示例。
T
T.J. Crowder

Javadoc

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

例如,渴望比懒惰更主动。懒惰只在第一次使用时发生(如果提供者接受了提示),而在急切的情况下(可能)会被预取。


“首次使用”是什么意思?
@leon:假设你有一个实体,它有一个急切的字段和一个惰性的字段。当您获取实体时,在您收到实体引用时,eager 字段将已从数据库加载,但惰性字段可能尚未加载。只有当您尝试通过其访问器访问该字段时,它才会被获取。
@TJ Crowder,未定义 fetchtype 时的默认值是什么?
@MahmoudSaleh:我不知道。它可能会因某些事情而有所不同。我没有在实际项目中使用过 JPA,所以我还没有深入了解它。
@MahmoudS:默认提取类型:OneToMany:LAZY,ManyToOne:EAGER,ManyToMany:LAZY,OneToOne:EAGER,列:EAGER
K
Khalil M

图书.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;
        }

        }

主题.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;
}
} 

主.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 注释的集合 subject 的图书相关关联提前加载(通过 [default][1] 加载 @ManyToOnefetchType=EAGER)。我们可以通过将 fetchType.EAGER 放在 Books.java 中的 @OneToMany Subject.java 或 fetchType.LAZY 上 @ManyToOne 来更改行为。


A
Abd Abughazaleh

加入是最重要的

以简单的方式接受它:

假设我们有一个名为 User 的类和另一个名为 Address 的类,并假设每个 用户 有一个或多个 地址 表示关系(一对多)如果你执行这里:

FetchType.LAZY 在没有 join 的情况下执行 sql 命令:

SELECT * FROM users 

FetchType.EAGERjoin 中执行 sql 命令:

SELECT * FROM users u join address a on a.user_id = u.user_id

注意:上述查询只是为了为您澄清图像,但实际中的 Hibernate 框架执行上述查询的类似查询。

哪种获取类型更好?

由于 Eager fetching 会自动加载所有关系,因此它是一个很大的性能消耗

除非被告知,否则延迟获取不会加载任何关系,这会带来更好的性能

Eager fetching 使得编程更容易,因为需要更少的代码

如果整个系统没有经过适当的测试,延迟加载可能会导致错误(异常)

考虑到所有因素,您仍然应该更喜欢延迟加载而不是 Eager,因为它的性能更高

如果您使用的是 Spring Boot Framework,请转到 application.properties 文件并添加以下命令以了解具体情况。

logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE

d
debugmode

了解它们之间区别的最好方法是如果您了解惰性,可以。 FetchType.LAZY 告诉 hibernate 仅在您使用关系时从数据库中获取相关实体。

PS:在我做的很多项目中,我看到很多软件开发者不重视,甚至有自称资深的。如果你做的项目不是大数据交换数据量大,这里有 EAGER 没问题。但是考虑到可能出现n+1个问题的问题,在了解了默认关系的fetch类型后,需要注意这些问题。

在这里您可以看到默认值:Default fetch type for one-to-one, many-to-one and one-to-many in Hibernate

此外,即使在了解获取类型之后,它也不会就此结束。要了解何时使用 LAZY 以及何时使用 EAGER,还需要了解单向和双向的概念。此外,spring boot 存储库有一些方法允许它为您惰性或急切地读取数据。例如,getOne()getById() 方法允许您从实体中惰性获取数据。简而言之,您使用什么以及何时使用取决于对方想让你做什么。


A
Anas Lachheb

public enum FetchType extends java.lang.Enum 定义从数据库中获取数据的策略。 EAGER 策略是对持久性提供程序运行时的要求,即必须急切地获取数据。 LAZY 策略是对持久性提供程序运行时的提示,即在首次访问数据时应该延迟获取数据。允许该实现急切地获取已指定 LAZY 策略提示的数据。示例:@Basic(fetch=LAZY) protected String getName() { return name; }

Source


r
rtruszk

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

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;
    }
    //...
}

m
marc_s

LAZY:它懒惰地获取子实体,即在获取父实体时它只是获取子实体的代理(由 cglib 或任何其他实用程序创建),当您访问子实体的任何属性时,它实际上是由休眠获取的。

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

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