FetchType LAZY和EAGER在持久化API中Java区别?

我是Java持久化API和Hibernate的新手。

持久化API中的FetchType.LAZYFetchType.EAGER有什么区别Java?

678027 次浏览

基本上,

LAZY = fetch when needed
EAGER = fetch immediately

Javadoc

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

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

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

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

一所大学有很多学生

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

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

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

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

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

(礼貌:-对象dbcom)

如果您使用的是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;
}
//...
}

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

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

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

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

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

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

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.的检索()方法当我们得到主题时,它的集合列表,注释为@OneToMany,将被延迟加载。但是,另一方面,与集合主题相关的Books关联,注释为@ManyToOne,加载得很早(通过[default][1]@ManyToOnefetchType=EAGER)。我们可以通过将fetchType.EAGER放在@OneToManySubject.java或fetchType.LAZY放在Books.java.的@ManyToOne上来改变行为

获取类型 扩展java.lang.枚举 定义从数据库中获取数据的策略。EAGER策略是对持久性提供程序运行时的一项要求,即必须急切地获取数据。LAZY策略是对持久性提供程序运行时的一个提示,即首次访问数据时应延迟获取数据。允许实现急切地获取已指定LAZY策略提示的数据。 示例: @BASIC(FETCH=LAZY) protected String getName(){返回名称;}

源代码

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

EAGER:它获取子实体和父实体。

为了更好地理解,请转到JBoss留档,或者您可以为您的应用程序使用hibernate.show_sql=true并检查hibernate发出的查询。

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

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

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

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

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

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

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

我想把这个注释添加到上面所说的内容中。

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

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

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

有两种常见的选择来解决这个问题,具体取决于设计、性能和开发人员:

  1. 最简单的一种是使用FetchType.EAGER或任何其他反模式解决方案,以便会话在控制器方法处仍然活着,但这些方法会影响性能。
  2. 最好的做法是使用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
}

两种类型的读取之间的主要区别是数据加载到内存中的时刻。
我附上了两张照片来帮助你理解这一点。

急切获取
急切获取

懒惰获取 延迟读取

JOIN是个大问题

用简单的方法:

假设我们有一个名为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框架执行上述查询的类似查询。

什么样的Fetch类型更好?

  • 由于Eager获取会自动加载所有关系,这是一个很大的性能消耗
  • 懒惰获取不会加载任何关系,除非被告知,这会带来更好的性能
  • 急切获取使编程更容易,因为需要的代码更少
  • 如果整个系统没有正确测试,延迟加载可能会导致错误(异常)
  • 考虑到所有因素,您仍然应该更喜欢Lazy加载而不是Eager,因为它的性能更高

如果您使用的是Spring Boot框架,那么转到application.properties文件并添加以下命令以了解到底发生了什么。

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

理解它们之间区别的最好方法是,如果你理解懒惰-好吧。 FetchType.LAZY告诉hibernate在使用关系时只从数据库中获取相关实体。

P. S.:在我做过的许多项目中,我看到许多软件开发人员并不关注它们,甚至有自称资深的人。如果你正在做的项目不是对大量数据进行数据交换,这里有EAGER是可以的。但是,考虑到可能出现n+1问题的问题,你需要在默认情况下知道关系的获取类型后注意这些。

在这里您可以看到默认值: Hibernate中一对一、多对一和一对多的默认获取类型

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

有一个小评论: 如果您使用懒惰类型,如果您关闭会话,稍后将无法从数据库中获取数据(请参阅下面的输出)。

但是使用渴望类型,您在获取Instructor时获取数据,因此在session.close()之后,您将能够使用/显示这些CourseList数据。

  @OneToMany(//fetch = FetchType.EAGER,
fetch = FetchType.LAZY,
mappedBy = "instructor",
cascade = {CascadeType.DETACH, CascadeType.MERGE,
CascadeType.PERSIST, CascadeType.REFRESH})
private List<Course> courseList;

我建议在调试模式下尝试它们。在这种情况下,我使用懒惰类型,如您所见。

 try {
//start the transaction
session.beginTransaction();


//Get instructor from database
int instructorId = 7;


Instructor tempInstructor = session.get(Instructor.class,instructorId);


System.out.println("Instructor: "+tempInstructor);




//commit transaction
session.getTransaction().commit();


//close session
session.close();


//since courselist is lazy loaded... this should fail
//so in here we are not able to fetch courselist data
//get courses
System.out.println("Courses "+tempInstructor.getCourseList() );




System.out.println("Done!");


} finally {
session.close();
factory.close();
}
}

输出异常:

Exception in thread "main" org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.exercise.hibernate.entity.Instructor.courseList, could not initialize proxy - no Session
at org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:606)
at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:218)
at org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:585)
at org.hibernate.collection.internal.AbstractPersistentCollection.read(AbstractPersistentCollection.java:149)
at org.hibernate.collection.internal.PersistentBag.toString(PersistentBag.java:621)
at java.base/java.lang.StringConcatHelper.stringOf(StringConcatHelper.java:453)
at java.base/java.lang.StringConcatHelper.simpleConcat(StringConcatHelper.java:408)
at com.exercise.hibernate.main.EagerLazyLoading.main(EagerLazyLoading.java:56)