[英]difference between @Fetch(FetchMode.SELECT) and fetch = FetchType.LAZY
[英]Difference between FetchType LAZY and EAGER in Java Persistence API?
有时您有两个实体,并且它们之间存在关系。 例如,您可能有一个名为University
的实体和另一个名为Student
实体,一个 University 可能有许多学生:
University 实体可能有一些基本属性,例如 id、name、address 等,以及一个称为 Students 的集合属性,它返回给定大学的学生列表:
public class University {
private String id;
private String name;
private String address;
private List<Student> students;
// setters and getters
}
现在,当您从数据库加载大学时,JPA 会为您加载其 id、名称和地址字段。 但是对于如何加载学生,您有两种选择:
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.
}
基本上,
LAZY = fetch when needed
EAGER = fetch immediately
EAGER
加载集合意味着在获取它们的父项时完全获取它们。 因此,如果您有Course
并且它有List<Student>
,则在获取Course
时将从数据库中获取所有学生。
另一方面, LAZY
意味着只有当您尝试访问它们时才会获取List
的内容。 例如,通过调用course.getStudents().iterator()
。 调用List
上的任何访问方法将启动对数据库的调用以检索元素。 这是通过围绕List
(或Set
)创建代理来实现的。 因此,对于您的惰性集合,具体类型不是ArrayList
和HashSet
,而是PersistentSet
和PersistentList
(或PersistentBag
)
我可能会考虑性能和内存利用率。 一个很大的区别是 EAGER fetch 策略允许在没有会话的情况下使用已获取的数据对象。 为什么?
当会话连接时在对象中预先标记数据时获取所有数据。 但是,在延迟加载策略的情况下,如果会话断开连接(在session.close()
语句之后session.close()
,延迟加载标记的对象不会检索数据。 所有这些都可以通过休眠代理完成。 Eager 策略让数据在关闭会话后仍然可用。
默认情况下,对于所有集合和映射对象,获取规则是FetchType.LAZY
,对于其他实例,它遵循FetchType.EAGER
策略。
简而言之, @OneToMany
和@ManyToMany
关系不会@ManyToMany
获取相关对象(集合和映射),而是通过@OneToOne
和@ManyToOne
的字段级联检索操作。
据我所知,两种类型的提取都取决于您的要求。
FetchType.LAZY
按需使用(即当我们需要数据时)。
FetchType.EAGER
是即时的(即在我们的需求到来之前,我们不必要地获取记录)
FetchType.LAZY
和FetchType.EAGER
都用于定义默认的获取计划。
不幸的是,您只能覆盖 LAZY 提取的默认提取计划。 EAGER 获取不太灵活,可能会导致许多性能问题。
我的建议是抑制让你的关联变得 EAGER 的冲动,因为获取是查询时的责任。 因此,您的所有查询都应使用fetch指令来仅检索当前业务案例所需的内容。
除非您明确标记Eager
Fetch 类型,否则 Hibernate 默认选择Lazy
Fetch 类型。 为了更准确和简洁,差异可以说明如下。
FetchType.LAZY
= 这不会加载关系,除非您通过 getter 方法调用它。
FetchType.EAGER
= 这将加载所有关系。
这两种获取类型的优缺点。
Lazy initialization
通过避免不必要的计算和减少内存需求来提高性能。
Eager initialization
需要更多的内存消耗并且处理速度很慢。
话虽如此,根据情况可以使用这些初始化中的任何一个。
从Javadoc :
EAGER 策略是对持久性提供程序运行时的一项要求,即必须急切地获取数据。 LAZY 策略是对持久性提供程序运行时的一个提示,即在第一次访问数据时应该延迟获取数据。
例如,渴望比懒惰更主动。 懒惰只在第一次使用时发生(如果提供者接受提示),而急切的东西(可能)会被预取。
我想将此注释添加到上面所说的内容中。
假设您正在通过这个简单的架构师使用 Spring(MVC 和数据):
控制器 <-> 服务 <-> 存储库
并且您想向前端返回一些数据,如果您使用的是FetchType.LAZY
,则在将数据返回给控制器方法后会出现异常,因为会话在 Service 中已关闭,因此JSON Mapper Object
不能获取数据。
解决此问题的常用选项有以下三种,取决于设计、性能和开发人员:
FetchType.EAGER
,这样 session 在控制器方法上仍然会活着,但是这个方法会影响性能。FetchType.LAZY
与映射像MapStruct
来传输数据从Entity
到另一个数据对象DTO
,然后发回给控制器,所以没有异常,如果本次会议闭幕。书.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;
}
}
主程序
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
,负载eargerly(按[default][1]
为@ManyToOne
, fetchType=EAGER
)。 我们可以通过把fetchType.EAGER上改变行为@OneToMany
上Subject.java或fetchType.LAZY @ManyToOne
在Books.java。
了解它们之间区别的最好方法是如果您了解惰性,可以。 FetchType.LAZY 告诉 hibernate 在您使用关系时仅从数据库中获取相关实体。
PS:在我做的很多项目中,我看到很多软件开发者不重视,甚至有自称资深的。如果你做的项目不是大数据交换数据量大,这里有 EAGER 没问题。 但是考虑到可能出现n+1个问题的问题,在了解了默认关系的fetch类型后,需要注意这些问题。
在这里您可以看到默认值: Hibernate 中的一对一、多对一和一对多的默认获取类型
此外,即使在了解获取类型之后,它也不会就此结束。 要了解何时使用 LAZY 以及何时使用 EAGER,还需要了解单向和双向的概念。 此外,spring 引导存储库有一些方法允许它为您惰性或急切地读取数据。例如, getOne()
或 getById( getById()
方法允许您从实体中惰性获取数据。简而言之,您使用什么以及何时使用取决于对方想让你做什么。
public enum FetchType 扩展 java.lang.Enum 定义从数据库中获取数据的策略。 EAGER 策略是对持久性提供程序运行时的一项要求,即必须急切地获取数据。 LAZY 策略是对持久性提供程序运行时的一个提示,即在第一次访问数据时应该延迟获取数据。 允许实现急切地获取已为其指定 LAZY 策略提示的数据。 示例:@Basic(fetch=LAZY) protected String getName() { return name; }
JOIN
是大问题用简单的方法:
假设我们有一个名为User
的类和另一个名为Address
类,并且假设每个用户都有一个或多个地址,如果您执行:
FetchType.LAZY
像没有join
一样执行 sql 命令:
SELECT * FROM users
FetchType.EAGER
在join
内执行 sql 命令:
SELECT * FROM users u join address a on a.user_id = u.user_id
注意:以上查询只是为您澄清图像,但实际上 Hibernate 框架执行上述查询的类似查询。
哪种提取类型更好?
如果您使用的是Spring Boot Framework,那么转到application.properties
文件并添加以下命令以了解究竟发生了什么。
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
@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;
}
//...
}
LAZY:它懒惰地获取子实体,即在获取父实体时,它只获取子实体的代理(由 cglib 或任何其他实用程序创建),当您访问子实体的任何属性时,它实际上是由 hibernate 获取的。
EAGER:它与父实体一起获取子实体。
为了更好地理解,请转到 Jboss 文档,或者您可以对您的应用程序使用hibernate.show_sql=true
并检查由 hibernate 发出的查询。
有一个小评论:如果你使用惰性类型,如果你关闭 session 以后你将无法从数据库中获取数据(参见下面的 Output)。
但是对于Eager类型,您在获取 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();
}
}
Output 例外:
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)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.