1.4 SqlSession会话的生命周期


文档摘要

1.4 SqlSession:会话的生命周期 本节摘要:SqlSession 是一次数据库会话的载体,提供执行 SQL、获取 Mapper、管理事务三族方法。本节讲清 openSession 的参数族与方法族,给出标准的使用模板,复盘一次"忘提交导致数据消失"的真实事故,并解释它为什么不是线程安全的。 工厂是器械柜,会话就是从柜里取出来的一次性手术通道。这一节的目标是让"开、用、关"三个动作形成肌肉记忆。 openSession 的四种打开方式 openSession 带参数的重载决定了这条通道的初始状态: 四种方式可以组合(重载见名知义)。日常开发用得最多的是方式一;方式三在批量写入时登场(2.3 节);方式四偶见于需要覆盖数据库默认隔离级别的场景。

1.4 SqlSession:会话的生命周期

本节摘要:SqlSession 是一次数据库会话的载体,提供执行 SQL、获取 Mapper、管理事务三族方法。本节讲清 openSession 的参数族与方法族,给出标准的使用模板,复盘一次"忘提交导致数据消失"的真实事故,并解释它为什么不是线程安全的。

工厂是器械柜,会话就是从柜里取出来的一次性手术通道。这一节的目标是让"开、用、关"三个动作形成肌肉记忆。

openSession 的四种打开方式

openSession 带参数的重载决定了这条通道的初始状态:

// 方式一:默认打开,autoCommit 为 false,写操作需手动 commit SqlSession s1 = factory.openSession(); // 方式二:显式声明自动提交 SqlSession s2 = factory.openSession(true); // 每条语句独立成事务 // 方式三:从指定 Executor 类型打开(SIMPLE、REUSE、BATCH) SqlSession s3 = factory.openSession(ExecutorType.BATCH); // 方式四:事务隔离级别 SqlSession s4 = factory.openSession(TransactionIsolationLevel.READ_COMMITTED);

四种方式可以组合(重载见名知义)。日常开发用得最多的是方式一;方式三在批量写入时登场(2.3 节);方式四偶见于需要覆盖数据库默认隔离级别的场景。

autoCommit 的语义要咬准:false 不代表"不提交",而代表"提交权交给你"。这是本节事故复盘的伏笔。

方法族:会话能做什么

SqlSession 的方法可以归成三族:

try (SqlSession session = factory.openSession()) { // 族一:直接执行(不经过 Mapper 接口,语句全名 = namespace + id) User u = session.selectOne("com.example.mapper.UserMapper.findById", 1); List<User> all = session.selectList("com.example.mapper.UserMapper.findAll"); int rows = session.insert("com.example.mapper.UserMapper.insertUser", newUser); // 族二:获取 Mapper 代理(推荐用法,类型安全) UserMapper mapper = session.getMapper(UserMapper.class); User u2 = mapper.findById(1); // 族三:事务控制 session.commit(); // 提交当前事务 session.rollback(); // 回滚当前事务 // close 由 try-with-resources 代办 }

两族执行方式中,getMapper 是更好的选择:语句全名是字符串,拼错在编译期发现不了;接口调用则有类型检查,重构时 IDE 能跟上。字符串方式并非无用——写通用工具或调试时它反而直接。

事故复盘:一次"成功"的插入消失了

背景:某订单服务上线首日,客服反馈"用户说提交了订单但后台查不到"。日志显示插入接口返回了 1(受影响行数),没有任何异常。

操作:先在数据库查该订单号——不存在。再看代码:

// 事故版本:默认打开 autoCommit 为 false,却从未提交 SqlSession session = factory.openSession(); try { OrderMapper mapper = session.getMapper(OrderMapper.class); int rows = mapper.insert(order); // 返回 1,语句在本次事务内执行成功 return rows; } finally { session.close(); // 关闭未提交的事务:数据库把这次插入回滚丢弃 }

结果:语句执行成功、行数返回 1,但事务从未提交;close 时未提交的工作被静默回滚。数据"消失"得毫无痕迹。

解读:insert 返回 1 只说明语句在当前事务里执行了,不代表事务落库。默认 openSession 的 autoCommit 是 false,写完必须 commit。修复版:

// 修复版:写操作显式提交 try (SqlSession session = factory.openSession()) { OrderMapper mapper = session.getMapper(OrderMapper.class); int rows = mapper.insert(order); session.commit(); // 落库 return rows; }

变式:如果整个方法只做一件事且不需要与其他操作同事务,可以 openSession(true) 省掉 commit;但转账这类多步操作绝对不能这样做——6.1 节会把事务边界展开成完整一章的篇幅。

标准使用模板

把纪律固化成模板,三个要点:作用域最小、异常回滚、用完即关。

// 标准模板:局部作用域 + try-with-resources public void transfer(Long from, Long to, int amount) { try (SqlSession session = factory.openSession()) { // autoCommit false try { AccountMapper mapper = session.getMapper(AccountMapper.class); mapper.decrease(from, amount); // 步骤一 mapper.increase(to, amount); // 步骤二 session.commit(); // 两步一起落库 } catch (RuntimeException e) { session.rollback(); // 任一步失败,全部撤销 throw e; } } // close 自动执行 }

线程安全红线

SqlSession 不是线程安全的,官方文档把这一点放在最显眼的位置。原因有二:它持有数据库连接(连接不能被并发使用);它承载一级缓存与事务状态(并发共享会把不同请求的缓存和事务搅在一起)。

// 反面教材:把会话放进静态字段,请求之间共享 public class BadHolder { // 多个线程同时用这条会话:缓存互相污染、事务互相覆盖、连接被并发使用 public static final SqlSession SESSION = FactoryHolder.get().openSession(); }

正确姿势是一个请求(或一个工作单元)一条会话:Web 项目里在过滤器或拦截器里开、请求结束时关;Spring 集成后这件事由 SqlSessionTemplate 全权代办(5.5 节展开机制)。

⚠️ 常见坑:会话忘关会耗尽连接池,症状是系统运行一段时间后全部请求卡在"获取连接"上,直到超时。用 try-with-resources 从语法层面杜绝这条隐患。

💡 关键直觉:SqlSession 与"工作单元"同寿——一次业务动作开一条会话,动作结束会话关闭。会话的寿命一旦越过业务动作的边界,线程安全、缓存污染、连接泄漏三类问题会同时找上门。

本节要点回顾

  • 四种打开:默认手动提交、自动提交、指定执行器、指定隔离级别,日常用第一种
  • 三族方法:直接执行、获取代理、事务控制;优先 getMapper 保类型安全
  • 行数不等于落库:insert 返回 1 只代表语句执行,commit 才代表事务提交
  • 模板三要点:局部作用域、异常 rollback、try-with-resources 关闭
  • 红线:一个工作单元一条会话,绝不做共享字段

会话通道打通了,最后一块拼图是那条最容易被当成魔法的机制:没有实现类的 Mapper 接口,凭什么能执行 SQL。下一节拆开动态代理看个究竟。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U