0%

hibernate session接口

Hibernate Session 接口深度解析:缓存、对象状态与核心方法

Hibernate 的 Session 接口是持久化操作的核心,封装了与数据库交互的所有关键能力 —— 包括缓存管理、对象状态转换、CRUD 操作执行等。本文基于 Session 接口的核心功能,从缓存操作、对象四种状态、关键方法对比、原生 JDBC 集成四个维度,系统拆解 Session 的工作机制与实战用法,帮助开发者规避常见陷阱。

Session 核心基础:缓存管理

Session 内置一级缓存(Session 缓存),是 Hibernate 提升查询性能的关键。缓存中存储的对象称为 “持久化对象”,Session 提供 flushrefreshclear 等方法管理缓存与数据库的同步。

1. flush ():缓存同步到数据库

核心作用

Session 缓存中未同步的持久化对象状态,通过 INSERT/UPDATE/DELETE SQL 同步到数据库,确保缓存与数据库一致。

触发时机(重要)

flush 并非仅在显式调用时执行,Hibernate 会在以下场景自动触发:

  1. 事务提交时tx.commit() 会先执行 flush(),再提交事务;
  2. 执行查询前:执行 HQL/QBC 查询(如 session.createQuery())时,会先 flush 缓存,避免查询到旧数据;
  3. 显式调用session.flush()
代码示例
1
2
3
4
5
6
7
8
9
10
Session session = sessionFactory.getCurrentSession();
Transaction tx = session.beginTransaction();

User user = session.get(User.class, 1L); // 从数据库加载,存入缓存(持久化状态)
user.setName("李四"); // 修改缓存中的对象(未同步到数据库)

// 显式调用 flush:生成 UPDATE SQL,同步到数据库
session.flush();

tx.commit(); // 事务提交时,因已 flush,不会重复同步
注意事项
  • flush 不会清空缓存,仅同步状态;
  • 若缓存中存在多个未同步对象,flush 会按 “插入→更新→删除” 的顺序执行 SQL,确保外键约束不冲突。

2. refresh ():数据库同步到缓存

核心作用

从数据库重新加载指定对象的最新状态,覆盖 Session 缓存中的旧状态,确保缓存与数据库一致(与 flush 方向相反)。

适用场景
  • 数据库中的数据被其他程序修改(如定时任务、其他服务),需获取最新数据;
  • 执行原生 SQL 后,需同步 Hibernate 缓存(如 session.createSQLQuery() 修改了数据)。
代码示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Session session = sessionFactory.getCurrentSession();
Transaction tx = session.beginTransaction();

// 1. 首次加载,缓存中存储旧状态(name=张三)
User user = session.get(User.class, 1L);
System.out.println(user.getName()); // 输出:张三

// 2. 其他程序修改数据库中该用户的 name 为“王五”(模拟外部修改)
// ...

// 3. 调用 refresh:从数据库重新加载,覆盖缓存
session.refresh(user);
System.out.println(user.getName()); // 输出:王五

tx.commit();

3. clear ():清空缓存

核心作用

清空 Session 缓存中所有持久化对象,所有对象变为 “游离状态”(脱离 Session 管理),后续操作需重新加载。

适用场景
  • 处理大量数据时(如批量插入 1000 条记录),清空缓存避免内存溢出;
  • 需强制重新从数据库加载对象,不依赖缓存。
代码示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Session session = sessionFactory.getCurrentSession();
Transaction tx = session.beginTransaction();

// 1. 加载对象,存入缓存
User user1 = session.get(User.class, 1L);
User user2 = session.get(User.class, 2L);
System.out.println(session.contains(user1)); // true(在缓存中)

// 2. 清空缓存
session.clear();
System.out.println(session.contains(user1)); // false(已移除)

// 3. 再次获取:需重新查询数据库
User user1Again = session.get(User.class, 1L); // 执行 SELECT SQL

tx.commit();

4. evict (Object obj):移除缓存中指定对象

核心作用

仅从 Session 缓存中移除单个指定对象(而非全部),该对象变为 “游离状态”,后续操作不影响缓存。

与 clear () 的区别
方法 作用范围 对象状态变化 适用场景
clear() 所有对象 全部变为游离状态 批量操作、清空整个缓存
evict(obj) 单个指定对象 仅该对象变为游离状态 保留其他对象缓存,移除特定对象
代码示例
1
2
3
4
5
6
7
8
9
10
11
12
Session session = sessionFactory.getCurrentSession();
Transaction tx = session.beginTransaction();

User user1 = session.get(User.class, 1L);
User user2 = session.get(User.class, 2L);

// 移除 user1 从缓存,user2 仍在缓存中
session.evict(user1);
System.out.println(session.contains(user1)); // false
System.out.println(session.contains(user2)); // true

tx.commit();

对象的四种状态与转换

Hibernate 将对象分为临时(Transient)、持久化(Persistent)、游离(Detached)、删除(Removed) 四种状态,Session 的核心方法(如 saveupdatedelete)本质是触发状态转换。

1. 四种状态的核心特征

状态 OID(主键) Session 缓存 数据库记录 典型场景
临时状态 null 不在 new 出的对象(new User()
持久化状态 非 null session.get()/save() 后的对象
游离状态 非 null 不在 有(可能) session.close()/clear() 后的对象
删除状态 非 null 在(即将移除) 无(即将删除) session.delete() 后的对象

2. 状态转换流程图

3. 关键方法的状态转换逻辑

(1)save () vs persist ():临时→持久化

两者均将 “临时对象” 转为 “持久化对象”,但存在关键差异:

对比维度 save() persist()
OID 非 null 时 正常执行(可能生成 UPDATE SQL) PersistentObjectException
事务外执行 立即生成 INSERT SQL(非事务安全) 延迟到事务提交时生成 SQL(安全)
返回值 返回生成的 OID(Serializable 无返回值(void

示例:OID 非 null 时的差异

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Session session = sessionFactory.getCurrentSession();
Transaction tx = session.beginTransaction();

User user = new User();
user.setId(1L); // 手动设置 OID(非 null,模拟游离对象误传)
user.setName("张三");

// 1. save():正常执行,生成 UPDATE SQL(若数据库存在 ID=1 的记录)
session.save(user);

// 2. persist():抛异常(不允许 OID 非 null 的临时对象)
// session.persist(user); // 抛 PersistentObjectException

tx.commit();
(2)get () vs load ():数据库→持久化

两者均从数据库加载对象到缓存(转为持久化状态),核心差异在加载时机异常处理

对比维度 get() load()
加载时机 立即加载(调用时执行 SELECT SQL) 延迟加载(返回代理,使用时才执行 SQL)
无记录时 返回 null ObjectNotFoundException
返回对象 真实对象(非代理) 代理对象(CGLIB 动态生成)
懒加载异常 不会抛(立即加载) 可能抛(Session 关闭后使用代理)

示例:懒加载异常(load () 特有)

1
2
3
4
5
6
7
8
9
10
Session session = sessionFactory.getCurrentSession();
Transaction tx = session.beginTransaction();

// 1. load() 返回代理对象,未执行 SQL
User userProxy = session.load(User.class, 1L);
tx.commit();
session.close(); // Session 关闭

// 2. 此时使用代理对象的属性,触发 SQL,但 Session 已关,抛异常
System.out.println(userProxy.getName()); // 抛 LazyInitializationException
(3)update () vs merge ():游离→持久化

两者均将 “游离对象”(OID 非 null,不在缓存)转为 “持久化对象”,差异在对象引用与缓存处理

对比维度 update() merge()
缓存冲突 若缓存中已存在相同 OID 的对象,抛 NonUniqueObjectException 合并游离对象的状态到缓存中的对象,不抛异常
返回值 无返回值(void),修改原游离对象 返回新的持久化对象,原游离对象不变
OID 不存在 StaleObjectStateException ObjectNotFoundException

示例:缓存冲突场景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Session session = sessionFactory.getCurrentSession();
Transaction tx = session.beginTransaction();

// 1. 加载对象到缓存(持久化状态)
User persistentUser = session.get(User.class, 1L);

// 2. 创建游离对象(OID 与缓存中的对象相同)
User detachedUser = new User();
detachedUser.setId(1L);
detachedUser.setName("王五");

// 3. update():缓存中已存在相同 OID 对象,抛异常
// session.update(detachedUser); // 抛 NonUniqueObjectException

// 4. merge():合并 detachedUser 的状态到 persistentUser,不抛异常
User mergedUser = session.merge(detachedUser);
System.out.println(persistentUser.getName()); // 输出:王五(缓存对象被更新)
System.out.println(mergedUser == persistentUser); // true(返回缓存中的对象)

tx.commit();
(4)delete ():持久化→删除

将 “持久化对象” 转为 “删除状态”,事务提交时执行 DELETE SQL,删除数据库记录并清空缓存中的对象。

注意

  • 不能删除 “临时对象”(OID 为 null),否则抛 IllegalArgumentException
  • 若删除 “游离对象”,需先通过 update()/merge() 转为持久化状态,或直接调用 delete()(Hibernate 会自动先加载到缓存)。

Session 核心方法与异常处理

1. 常用方法汇总

方法 作用描述 状态转换
save(Object obj) 临时对象→持久化,返回 OID Transient → Persistent
persist(Object obj) 临时对象→持久化,延迟执行 SQL Transient → Persistent
get(Class cls, ID id) 数据库加载→持久化,立即执行 SQL 无 → Persistent
load(Class cls, ID id) 数据库加载→持久化,延迟执行 SQL 无 → Persistent
update(Object obj) 游离对象→持久化,执行 UPDATE SQL Detached → Persistent
merge(Object obj) 游离对象→持久化,合并状态到缓存 Detached → Persistent
delete(Object obj) 持久化对象→删除状态,执行 DELETE SQL Persistent → Removed
saveOrUpdate(Object obj) 临时→持久化(save),游离→持久化(update) Transient/Detached → Persistent

2. 常见异常与解决方案

(1)NonUniqueObjectException:同一 Session 存在相同 OID 的对象
  • 原因Session 缓存中已存在 OID 为 X 的对象,又尝试将另一个 OID 为 X 的对象纳入缓存(如 update()saveOrUpdate())。
  • 解决方案:
    • 先通过 evict() 移除缓存中的旧对象;
    • 使用 merge() 合并状态,而非 update()
(2)LazyInitializationException:懒加载异常
  • 原因load() 返回的代理对象在 Session 关闭后被使用(如访问 user.getName()),此时无法触发 SQL 加载数据。
  • 解决方案:
    • 改用 get() 立即加载;
    • Session 关闭前提前初始化代理对象(Hibernate.initialize(userProxy));
    • 延长 Session 生命周期(如 Web 应用中使用 “Open Session In View” 模式)。
(3)StaleObjectStateException:对象版本冲突
  • 原因:乐观锁场景下,对象的版本号与数据库不一致(如其他事务已修改该对象)。
  • 解决方案:
    • 重试事务(重新加载对象并修改);
    • 检查乐观锁配置(@Version 注解是否正确)。

Session 集成原生 JDBC

当 Hibernate 无法满足复杂 SQL 需求(如存储过程、批量更新)时,Session 提供 doWork()doReturningWork() 方法直接操作原生 JDBC 连接,且连接由 Hibernate 管理(与 Session 的事务一致)。

1. doWork ():无返回值的原生操作

适用场景

执行无返回值的 JDBC 操作(如 INSERT/UPDATE 存储过程)。

代码示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Session session = sessionFactory.getCurrentSession();
Transaction tx = session.beginTransaction();

// 调用原生 JDBC 执行批量更新
session.doWork(new Work() {
@Override
public void execute(Connection connection) throws SQLException {
// 1. 获取 JDBC Statement
String sql = "UPDATE t_user SET age = age + 1 WHERE id > ?";
PreparedStatement pstmt = connection.prepareStatement(sql);

// 2. 设置参数
pstmt.setLong(1, 100L);

// 3. 执行 SQL
int rows = pstmt.executeUpdate();
System.out.println("批量更新行数:" + rows);

// 4. 关闭资源(Hibernate 会自动管理,也可手动关闭)
pstmt.close();
}
});

tx.commit();

2. doReturningWork ():有返回值的原生操作

适用场景

执行有返回值的 JDBC 操作(如查询存储过程返回结果、获取自增 ID)。

代码示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Session session = sessionFactory.getCurrentSession();
Transaction tx = session.beginTransaction();

// 调用原生 JDBC 查询并返回结果
Integer maxAge = session.doReturningWork(new ReturningWork<Integer>() {
@Override
public Integer execute(Connection connection) throws SQLException {
String sql = "SELECT MAX(age) FROM t_user";
PreparedStatement pstmt = connection.prepareStatement(sql);
ResultSet rs = pstmt.executeQuery();

int max = 0;
if (rs.next()) {
max = rs.getInt(1);
}

rs.close();
pstmt.close();
return max;
}
});

System.out.println("用户最大年龄:" + maxAge);
tx.commit();

注意事项

  • 原生 JDBC 操作与 Session 事务一致:事务提交 / 回滚时,原生 SQL 的操作也会同步提交 / 回滚;
  • 执行原生 SQL 后,若修改了数据,需调用 session.refresh() 同步 Session 缓存,避免后续查询到旧数据。

Session 核心总结

  1. Session 本质:是 Hibernate 的 “持久化管理器”,封装 JDBC 连接,管理缓存与对象状态,非线程安全;
  2. 缓存是核心:一级缓存(Session 缓存)是性能优化关键,flush/refresh/clear 控制缓存与数据库同步;
  3. 状态转换是灵魂:所有 CRUD 方法本质是触发对象状态转换,理解四种状态可规避 80% 的异常;
  4. 灵活集成原生 JDBC:复杂场景下可通过 doWork() 操作原生连接,兼顾 Hibernate 事务管理与 SQL 灵活性。

欢迎关注我的其它发布渠道