Hibernate 中的 load() 与 get()

1、简介

Hibernate 中,load()get() 是用于从数据库检索数据的两种方法。本文将带你了解这两种方法之间的区别。

2、加载策略

Hibernate 中的 load() 方法采用了一种懒加载策略。调用该方法时,它会返回一个实体的代理对象,延迟数据库查询,直到访问对象的属性或方法时才执行查询。

如下:

Person person = new Person("John Doe", 30);

Session session = sessionFactory.getCurrentSession();
session.saveOrUpdate(person);

Person entity = session.load(Person.class, person.getId());

assertNotNull(entity);
assertEquals(person.getName(), entity.getName());
assertEquals(person.getAge(), entity.getAge());

首先,创建一个新的 Person 对象并将其保存到数据库中。然后,使用 load() 根据 id 检索 Person 实体。虽然实体看起来是一个 Person 对象,但它只是 Hibernate 提供的一个代理对象。

当访问代理对象的属性(如 nameage)时,Hibernate 会拦截调用,并在必要时从数据库动态加载实际数据。相反,get()方法采用了急切加载策略,会立即查询数据库并返回实际实体对象:

Person entity = session.get(Person.class, person.getId());

assertNotNull(entity);
assertEquals(person.getName(), entity.getName());
assertEquals(person.getAge(), entity.getAge());

3、数据存在时

当调用 load() 方法时,Hibernate 会用提供的主键 id 创建一个实体的代理对象。这个代理对象是实体数据的占位符,只填充了 id。实体的其余属性未初始化,将在首次访问时从数据库加载。如果试图在未初始化代理对象的情况下访问它的任何属性,就会抛出 LazyInitializationException 异常:

Session session = sessionFactory.openSession();
session = sessionFactory.openSession();
Person entity = session.load(Person.class, person.getId());

// 关闭 Session
session.close();

assertThrows(LazyInitializationException.class, () -> {
    entity.getName();
});

get() 方法直接从数据库中获取实际的实体数据。这意味着,get() 方法返回的实体对象包含了所有初始化属性和从数据库获取的实际值。因此,即使在 Hibernate Session 关闭后,仍能访问实体的属性,而不会出现任何异常:

Session session = sessionFactory.openSession();
session = sessionFactory.openSession();
Person entity = session.get(Person.class, person.getId());

// 关闭 Session
session.close();

// 即使 Session 关闭后仍可访问实体属性
assertEquals(person.getName(), entity.getName());
assertEquals(person.getAge(), entity.getAge());

4、数据不存在时

使用 load() 时,最初会返回一个代理对象。实际的数据库查询将推迟到访问该对象的某个属性时进行。如果实体不存在,尝试访问对象的属性将导致 ObjectNotFoundException 异常。因此,需要明确处理这种情况:

Session session = sessionFactory.getCurrentSession();
Person entity = session.load(Person.class, 100L);

 // 访问实体属性,触发数据库查询
assertThrows(ObjectNotFoundException.class, () -> {
    entity.getName();
});

相反,使用 get() 方法时,如果实体存在于数据库或缓存中,则会立即检索并返回实际的实体对象。但是,如果实体不存在,get() 方法会优雅地返回 null 值:

Session session = sessionFactory.getCurrentSession();
Person entity = session.get(Person.class, 100L);
assertNull(entity);

5、缓存

load()get() 方法都利用一级缓存来缓存当前 Session 中检索到的实体。它存储了当前 Session 中最近被访问或操作过的实体。如果对象已存在于缓存中,get() 方法将立即返回缓存对象。但是,load() 仍然会返回一个代理对象,即使数据可能已被缓存。下面的示例可以说明这种行为:

Person person = new Person("John Doe", 30);

Session session = sessionFactory.openSession();
session.saveOrUpdate(person);
Person entity = session.get(Person.class, person.getId());

// 从 Session 缓存中驱逐实体,以模拟新的 Session
session.evict(entity);

Person cacheEntity = session.get(Person.class, person.getId());

将一个 Person 实体对象保存到 Session 中时,Hibernate 会将该实体对象缓存起来。接下来,调用第一个 get() 方法。由于实体对象已经在缓存中,Hibernate不会访问数据库。然后,将实体对象从 Session 缓存中清除,以模拟开始一个新的 Session。

然后,使用 get() 方法再次检索实体。这一次,由于实体不在缓存中,Hibernate 将访问数据库。从控制台的输出可以看到,Hibernate 只打印了一条 SELECT SQL 语句:

Hibernate: select p1_0.id,p1_0.age,p1_0.name from Person p1_0 where p1_0.id=?

6、总结

Hibernate 中 get()load() 方法之间的主要区别如下:

特性 get() load()
加载策略 急切加载(立即查询数据库) 懒加载(代理对象,访问时获取数据)
返回值(如果存在) 实际对象 代理 object
数据库查询 立即执行 访问属性时执行
返回值(如果不存在) null 访问属性时抛出 ObjectNotFoundException 异常
Hibernate 缓存 如果有缓存,则从缓存中读取 即使已缓存,仍返回代理

7、最后

本文介绍了 Hibernate 中 get() 方法和 load() 方法的基本区别。get() 方法在需要立即访问对象并确保其处于最新状态时非常有用,而,load() 方法在只需要引用对象并优化数据库调用时非常有用。


Ref:https://www.baeldung.com/hibernate-load-get-difference