我是 Java Persistence API 和 Hibernate 的新手。
Java Persistence API 中的 FetchType.LAZY
和 FetchType.EAGER
有什么区别?
有时您有两个实体,并且它们之间存在关系。例如,您可能有一个名为 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.
}
基本上,
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
)
course.getStudents()
时,它会触发一个 SQL 查询(在控制台上看到)。在 Lazy fetch 类型中,同样的事情也会发生。那么,有什么区别呢??
fetchtype = LAZY
上,即使尝试使用 getter hibernete 获取集合也会抛出一个错误,告诉我它无法评估
我可能会考虑性能和内存利用率。一个很大的区别是 EAGER 获取策略允许在没有会话的情况下使用获取的数据对象。为什么?
连接会话时,当对象中的 Eager 标记数据时,会获取所有数据。但是,在延迟加载策略的情况下,如果会话断开(在 session.close()
语句之后),延迟加载标记的对象不会检索数据。所有这些都可以通过休眠代理完成。 Eager 策略让数据在关闭会话后仍然可用。
两种获取类型的主要区别在于数据加载到内存中的时刻。我附上了两张照片以帮助您理解这一点。
https://i.stack.imgur.com/ws0yq.jpg
https://i.stack.imgur.com/e0EFV.jpg
默认情况下,对于所有集合和映射对象,获取规则是 FetchType.LAZY
,而对于其他实例,它遵循 FetchType.EAGER
策略。
简而言之,@OneToMany
和 @ManyToMany
关系不会获取相关对象(集合和 map) 隐含但检索操作通过 @OneToOne
和 @ManyToOne
中的字段级联。
据我所知,两种获取类型都取决于您的要求。
FetchType.LAZY
按需提供(即当我们需要数据时)。
FetchType.EAGER
是即时的(即在我们的要求到来之前,我们不必要地获取记录)
FetchType.LAZY
和 FetchType.EAGER
都用于定义默认提取计划。
不幸的是,您只能覆盖 LAZY 提取的默认提取计划。 EAGER fetching 不太灵活,可能会导致许多性能问题。
我的建议是抑制让你的关联变得渴望的冲动,因为获取是查询时的责任。因此,您的所有查询都应使用 fetch 指令来仅检索当前业务案例所需的内容。
除非您明确标记 Eager
Fetch 类型,否则 Hibernate 默认选择 Lazy
Fetch 类型。为了更准确和简洁,可以将差异表述如下。
FetchType.LAZY
= 这不会加载关系,除非您通过 getter 方法调用它。
FetchType.EAGER
= 这会加载所有关系。
这两种获取类型的优缺点。
Lazy initialization
通过避免不必要的计算和减少内存需求来提高性能。
Eager initialization
占用内存较多,处理速度较慢。
话虽如此,根据情况可以使用这些初始化中的任何一个。
getMember
的函数?
我想将此注释添加到上面所说的内容中。
假设您使用 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
}
从 Javadoc:
EAGER 策略是对持久性提供程序运行时的要求,即必须急切地获取数据。 LAZY 策略是对持久性提供程序运行时的提示,即在首次访问数据时应该延迟获取数据。
例如,渴望比懒惰更主动。懒惰只在第一次使用时发生(如果提供者接受了提示),而在急切的情况下(可能)会被预取。
图书.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]
加载 @ManyToOne
、fetchType=EAGER
)。我们可以通过将 fetchType.EAGER 放在 Books.java 中的 @OneToMany
Subject.java 或 fetchType.LAZY 上 @ManyToOne
来更改行为。
加入是最重要的
以简单的方式接受它:
假设我们有一个名为 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 框架执行上述查询的类似查询。
哪种获取类型更好?
由于 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
了解它们之间区别的最好方法是如果您了解惰性,可以。 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()
方法允许您从实体中惰性获取数据。简而言之,您使用什么以及何时使用取决于对方想让你做什么。
public enum FetchType extends java.lang.Enum 定义从数据库中获取数据的策略。 EAGER 策略是对持久性提供程序运行时的要求,即必须急切地获取数据。 LAZY 策略是对持久性提供程序运行时的提示,即在首次访问数据时应该延迟获取数据。允许该实现急切地获取已指定 LAZY 策略提示的数据。示例:@Basic(fetch=LAZY) protected String getName() { return name; }
@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 或任何其他实用程序创建),当您访问子实体的任何属性时,它实际上是由休眠获取的。
EAGER:它与父实体一起获取子实体。
为了更好地理解,请转到 Jboss 文档,或者您可以将 hibernate.show_sql=true
用于您的应用程序并检查休眠发出的查询。
不定期副业成功案例分享
getStudents()
)加载到内存中时,JDBC 会话必须仍然打开,但有时这是不可能的,因为通过调用此方法时,会话已经关闭并且实体已分离。类似地,有时我们有一个客户端/服务器架构(例如 Swing 客户端/JEE 服务器),并且实体/DTO 通过线路传输到客户端,并且在这些场景中,由于实体的方式,延迟加载通常不起作用通过网络进行序列化。getStudents()
方法时,结果是否被缓存?这样我下次可以更快地访问这些结果?