MyBatis 的 SqlSession 到底线程安全吗?SqlSessionFactory 有哪些潜在问题?
- 工作日记
- 7小时前
- 52热度
- 0评论
MyBatis 中 SqlSession 与 SqlSessionFactory 线程安全性深度剖析
在 MyBatis 开发实践中,SqlSession 的线程安全问题和SqlSessionFactory 的潜在风险是导致生产环境事故的高发区。曾有团队因共享 SqlSession 导致用户数据错乱,也有系统因工厂配置错误引发数据库连接池雪崩。本文将用真实场景还原这两个核心组件的运作机制,帮助开发者避开 90% 的并发陷阱。
一、SqlSession 线程安全陷阱
1.1 为什么说 SqlSession 非线程安全?
通过源码分析发现:每个 SqlSession 实例都维护着独立的 JDBC Connection 和事务上下文。当多个线程共享同一个 SqlSession 时:
// 错误示例:全局共享 SqlSession
public class UserDao {
private SqlSession sharedSession; // 定时炸弹
public User getById(Long id) {
return sharedSession.selectOne("selectUser", id);
}
}
这种共享会导致:
- 事务交叉污染:线程A的提交可能包含线程B的修改
- 缓存混乱:一级缓存可能返回错误版本数据
- 连接泄漏:异常场景下连接无法正常回收
1.2 正确使用姿势
黄金法则:每个请求单独创建,方法内立即关闭
// 正确用法:使用局部变量
public User getUser(Long id) {
try (SqlSession session = sqlSessionFactory.openSession()) {
UserMapper mapper = session.getMapper(UserMapper.class);
return mapper.selectById(id);
}
}
推荐组合方案:
- try-with-resources 自动关闭(Java7+)
- ThreadLocal 绑定(需配合请求拦截器)
- Spring 事务管理(推荐企业级方案)
二、SqlSessionFactory 的隐秘风险
2.1 线程安全背后的隐患
虽然官方文档声明 SqlSessionFactory 是线程安全的,但在实际使用中仍存在三大雷区:
风险类型 | 典型案例 | 后果 |
---|---|---|
配置错误 | mybatis-config.xml 中 environment 配置缺失 | 无法获取 DataSource |
资源泄漏 | 未正确关闭 SqlSessionFactory | 数据库连接池耗尽 |
单例滥用 | Web 应用中重复创建工厂实例 | JVM 堆内存溢出 |
2.2 工厂创建最佳实践
// 推荐初始化方式
public class MyBatisUtil {
private static final SqlSessionFactory factory;
static {
String resource = "mybatis-config.xml";
try (InputStream is = Resources.getResourceAsStream(resource)) {
factory = new SqlSessionFactoryBuilder().build(is);
}
}
public static SqlSession getSession() {
return factory.openSession();
}
}
关键注意事项:
- 配置验证:通过 factory.getConfiguration().validate() 检查配置完整性
- 连接监控:集成 Druid 等连接池的监控功能
- 版本兼容:MyBatis 3.5+ 要求 JDBC 4.2+ 驱动
三、性能优化进阶方案
3.1 二级缓存调优
当 SqlSessionFactory 启用二级缓存时:
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
需要特别注意:
- 缓存策略:LRU vs FIFO 的选择依据
- 序列化:分布式环境必须使用 Redis 等支持序列化的缓存
- 失效控制:通过 flushCache 属性管理缓存更新
3.2 批处理优化
大数据量插入时的性能对比:
处理方式 | 10万条耗时 | 内存占用 |
---|---|---|
逐条插入 | 120s | 500MB |
批处理模式 | 8s | 150MB |
正确使用方式:
try (SqlSession session = factory.openSession(ExecutorType.BATCH)) {
UserMapper mapper = session.getMapper(UserMapper.class);
for (User user : userList) {
mapper.insert(user);
}
session.commit();
}
四、避坑指南
根据线上事故总结的常见问题排查表:
- 连接泄漏检测:通过
SELECT FROM sys.dm_exec_sessions
监控数据库连接状态 - 线程竞争排查:使用 VisualVM 的线程分析功能定位共享 SqlSession
- 配置验证脚本:编写单元测试验证工厂初始化流程
互动讨论:你在项目中是否遇到过 MyBatis 的线程安全问题?当时是如何排查解决的?欢迎分享你的实战经验!