在 MyBatis 开发中,SqlSessionFactory 和 SqlSession 的线程安全性直接影响应用的稳定性和性能。SqlSessionFactory 是线程安全的,而 SqlSession 则不是,这个区别经常被开发者忽视,导致生产环境出现数据不一致、连接泄漏等严重问题。
快速参考
✅ 正确用法
java
// 1. 使用try-with-resources(推荐)
try (SqlSession session = factory.openSession()) {
UserMapper mapper = session.getMapper(UserMapper.class);
return mapper.selectById(id);
}
// 2. Spring注入Mapper(企业级推荐)
@Autowired
private UserMapper userMapper;
// 3. SqlSessionManager(非Spring环境)
SqlSessionManager manager = SqlSessionManager.newInstance(factory);
❌ 错误用法
java
// 1. 多线程共享SqlSession - 绝对禁止!
private SqlSession sharedSession = factory.openSession();
// 2. 忘记关闭资源
SqlSession session = factory.openSession();
// 使用后未关闭 - 导致连接泄漏!
// 3. 在finally中关闭但没有null检查
finally {
session.close(); // 可能抛出NPE
}
版本兼容性说明
MyBatis 版本 | Spring 版本 | Spring Boot 版本 | 推荐的集成方式 |
---|---|---|---|
3.5.13+ | 6.0.x | 3.0.x | mybatis-spring-boot-starter 3.0.x |
3.5.6+ | 5.3.x | 2.5.x+ | mybatis-spring-boot-starter 2.3.x |
3.4.x | 5.0.x | 2.0.x | mybatis-spring-boot-starter 2.0.x |
3.3.x | 4.3.x | 1.5.x | mybatis-spring-boot-starter 1.3.x |
SqlSessionFactory:经过精心设计的线程安全工厂
SqlSessionFactory 不仅采用了不可变对象模式,其内部还使用了多重线程安全机制来确保多线程环境下的稳定运行。
线程安全机制深度剖析
java
public class SqlSessionFactoryThreadSafetyDemo {
private static final Logger logger = LoggerFactory.getLogger(SqlSessionFactoryThreadSafetyDemo.class);
private static SqlSessionFactory sqlSessionFactory;
static {
try {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
logger.info("SqlSessionFactory初始化完成");
} catch (IOException e) {
logger.error("SqlSessionFactory初始化失败", e);
throw new RuntimeException("初始化SqlSessionFactory失败", e);
}
}
public User getUserById(Long id) {
// 添加参数验证
if (id == null || id <= 0) {
throw new IllegalArgumentException("用户ID必须为正整数");
}
if (logger.isDebugEnabled()) {
logger.debug("开始查询用户,userId: {}", id);
}
long startTime = System.currentTimeMillis();
try (SqlSession session = sqlSessionFactory.openSession()) {
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.selectById(id);
if (logger.isDebugEnabled()) {
long duration = System.currentTimeMillis() - startTime;
logger.debug("查询用户成功,userId: {}, 耗时: {}ms", id, duration);
}
return user;
} catch (Exception e) {
logger.error("查询用户失败,userId: {}", id, e);
throw new ServiceException("查询用户失败", e);
}
}
}
SqlSessionFactory 的线程安全性体现在:
- Configuration 对象构建后只读 - 所有配置信息不可变
- 内部使用 ConcurrentHashMap 等线程安全数据结构 - 确保并发访问安全
- openSession()方法通过工厂模式创建新实例 - 每次调用返回独立的 SqlSession

SqlSession:状态丰富的会话管理
SqlSession 内部维护着复杂的状态信息,这些状态在并发环境下容易产生竞态条件。
SqlSession 内部状态详解
java
public class SqlSessionStateAnalysis {
private static final Logger logger = LoggerFactory.getLogger(SqlSessionStateAnalysis.class);
public void demonstrateSqlSessionStates() {
try (SqlSession session = sqlSessionFactory.openSession()) {
logger.info("SqlSession内部状态包括:");
logger.info("1. 数据库连接(Connection)- 管理物理数据库连接");
logger.info("2. 一级缓存(Local Cache)- 存储查询结果,默认开启");
logger.info("3. 事务状态(Transaction State)- 管理事务的提交/回滚");
logger.info("4. 执行器(Executor)- 负责SQL执行和缓存管理");
logger.info("5. 脏数据标记(Dirty Flag)- 标记是否有未提交的修改");
UserMapper mapper = session.getMapper(UserMapper.class);
// 一级缓存演示
logger.info("=== 一级缓存演示 ===");
User user1 = mapper.selectById(1L); // 数据库查询
User user2 = mapper.selectById(1L); // 一级缓存命中
logger.info("两次查询是否为同一对象: {}", user1 == user2);
logger.info("第一次查询: {}", user1);
logger.info("第二次查询(缓存): {}", user2);
}
}
}
二级缓存的线程安全性
java
// 二级缓存在多SqlSession间共享,需要特别注意线程安全
@CacheNamespace(
implementation = PerpetualCache.class,
eviction = LruCache.class,
flushInterval = 60000,
size = 1024,
readWrite = true // true表示返回对象的拷贝,线程安全
)
public interface UserMapper {
@Select("SELECT * FROM user WHERE id = #{id}")
User selectById(Long id);
@Update("UPDATE user SET name = #{name} WHERE id = #{id}")
int updateUser(User user);
}
动态 SQL 的线程安全注意事项
java
public class DynamicSqlThreadSafety {
private static final Logger logger = LoggerFactory.getLogger(DynamicSqlThreadSafety.class);
// 错误:在多线程环境下修改共享的参数对象
private Map<String, Object> sharedParams = new HashMap<>();
public List<User> searchUsersIncorrect(String name, Integer age) {
// 危险:多线程会修改同一个Map
sharedParams.put("name", name);
sharedParams.put("age", age);
try (SqlSession session = sqlSessionFactory.openSession()) {
return session.selectList("searchUsers", sharedParams);
}
}
// 正确:每次调用使用新的参数对象
public List<User> searchUsers(String name, Integer age) {
Map<String, Object> params = new HashMap<>();
params.put("name", name);
params.put("age", age);
try (SqlSession session = sqlSessionFactory.openSession()) {
List<User> users = session.selectList("searchUsers", params);
logger.info("搜索用户完成,条件:name={}, age={}, 结果数量:{}",
name, age, users.size());
return users;
}
}
}
并发问题的具体表现
java
public class ConcurrencyIssueDemo {
private static final Logger logger = LoggerFactory.getLogger(ConcurrencyIssueDemo.class);
@Test
public void demonstrateCacheConcurrencyIssue() throws InterruptedException {
SqlSession sharedSession = sqlSessionFactory.openSession();
CountDownLatch latch = new CountDownLatch(2);
AtomicReference<Exception> exceptionRef = new AtomicReference<>();
// 线程1:查询和缓存操作
Thread reader = new Thread(() -> {
try {
UserMapper mapper = sharedSession.getMapper(UserMapper.class);
User user = mapper.selectById(1L); // 缓存用户数据
logger.info("线程1查询到用户: {}", user.getName());
Thread.sleep(100); // 模拟处理时间
// 再次查询,期望从缓存获取
User cachedUser = mapper.selectById(1L);
logger.info("线程1从缓存获取用户: {}", cachedUser.getName());
} catch (Exception e) {
exceptionRef.set(e);
logger.error("线程1执行失败", e);
} finally {
latch.countDown();
}
}, "Reader-Thread");
// 线程2:修改操作
Thread writer = new Thread(() -> {
try {
Thread.sleep(50); // 确保reader先执行
UserMapper mapper = sharedSession.getMapper(UserMapper.class);
User user = new User(1L, "Updated Name");
mapper.updateUser(user);
// 这里的commit操作可能导致线程1的事务状态混乱
sharedSession.commit();
logger.info("线程2更新用户成功");
} catch (Exception e) {
exceptionRef.set(e);
logger.error("线程2执行失败", e);
} finally {
latch.countDown();
}
}, "Writer-Thread");
reader.start();
writer.start();
latch.await();
if (sharedSession != null) {
try {
sharedSession.close();
} catch (Exception e) {
logger.warn("关闭共享SqlSession时发生异常", e);
}
}
if (exceptionRef.get() != null) {
logger.error("并发测试发现异常: {}", exceptionRef.get().getMessage());
}
}
}
企业级解决方案
1. Spring Boot 自动配置方案(推荐)
java
@Configuration
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
@EnableConfigurationProperties(MybatisProperties.class)
public class MybatisAutoConfiguration {
private static final Logger logger = LoggerFactory.getLogger(MybatisAutoConfiguration.class);
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource,
MybatisProperties properties) throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(dataSource);
// 配置MyBatis设置
org.apache.ibatis.session.Configuration configuration =
new org.apache.ibatis.session.Configuration();
configuration.setMapUnderscoreToCamelCase(true);
configuration.setCacheEnabled(true);
configuration.setLazyLoadingEnabled(true);
configuration.setDefaultExecutorType(ExecutorType.REUSE);
configuration.setDefaultStatementTimeout(30);
factory.setConfiguration(configuration);
// 设置mapper扫描路径
if (properties.getMapperLocations() != null) {
factory.setMapperLocations(properties.resolveMapperLocations());
}
logger.info("SqlSessionFactory自动配置完成");
return factory.getObject();
}
@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
ExecutorType executorType = ExecutorType.SIMPLE;
SqlSessionTemplate template = new SqlSessionTemplate(sqlSessionFactory, executorType);
logger.info("SqlSessionTemplate自动配置完成,执行器类型: {}", executorType);
return template;
}
}
2. 多数据源配置方案
java
@Configuration
@EnableTransactionManagement
@MapperScan(basePackages = "com.example.mapper.primary",
sqlSessionFactoryRef = "primarySqlSessionFactory")
public class MultiDataSourceConfig {
private static final Logger logger = LoggerFactory.getLogger(MultiDataSourceConfig.class);
@Bean
@Primary
@ConfigurationProperties(prefix = "spring.datasource.primary")
public HikariDataSource primaryDataSource() {
HikariConfig config = new HikariConfig();
config.setMaximumPoolSize(20);
config.setMinimumIdle(5);
config.setConnectionTimeout(30000);
config.setIdleTimeout(600000);
config.setMaxLifetime(1800000);
config.setLeakDetectionThreshold(60000); // 连接泄漏检测
logger.info("配置主数据源HikariCP连接池完成");
return new HikariDataSource(config);
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.secondary")
public HikariDataSource secondaryDataSource() {
HikariConfig config = new HikariConfig();
config.setMaximumPoolSize(10);
config.setMinimumIdle(2);
config.setConnectionTimeout(30000);
config.setIdleTimeout(600000);
config.setMaxLifetime(1800000);
config.setLeakDetectionThreshold(60000);
logger.info("配置从数据源HikariCP连接池完成");
return new HikariDataSource(config);
}
@Bean
@Primary
public SqlSessionFactory primarySqlSessionFactory(
@Qualifier("primaryDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
org.apache.ibatis.session.Configuration configuration =
new org.apache.ibatis.session.Configuration();
configuration.setMapUnderscoreToCamelCase(true);
configuration.setDefaultExecutorType(ExecutorType.REUSE);
bean.setConfiguration(configuration);
logger.info("主数据源SqlSessionFactory配置完成");
return bean.getObject();
}
@Bean
public SqlSessionFactory secondarySqlSessionFactory(
@Qualifier("secondaryDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
org.apache.ibatis.session.Configuration configuration =
new org.apache.ibatis.session.Configuration();
configuration.setMapUnderscoreToCamelCase(true);
configuration.setDefaultExecutorType(ExecutorType.SIMPLE);
bean.setConfiguration(configuration);
logger.info("从数据源SqlSessionFactory配置完成");
return bean.getObject();
}
@Bean
@Primary
public SqlSessionTemplate primarySqlSessionTemplate(
@Qualifier("primarySqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
SqlSessionTemplate template = new SqlSessionTemplate(sqlSessionFactory);
logger.info("主数据源SqlSessionTemplate配置完成");
return template;
}
}
3. 批处理优化方案
java
@Service
public class BatchOperationService {
private static final Logger logger = LoggerFactory.getLogger(BatchOperationService.class);
private static final int BATCH_SIZE = 1000;
@Autowired
private SqlSessionFactory sqlSessionFactory;
@Transactional
public void batchInsertUsers(List<User> users) {
if (users == null || users.isEmpty()) {
logger.warn("批量插入用户列表为空");
return;
}
logger.info("开始批量插入{}个用户", users.size());
long startTime = System.currentTimeMillis();
try (SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH)) {
UserMapper mapper = session.getMapper(UserMapper.class);
int count = 0;
for (User user : users) {
if (user == null) {
logger.warn("跳过空用户对象");
continue;
}
mapper.insertUser(user);
if (++count % BATCH_SIZE == 0) {
session.flushStatements();
session.clearCache(); // 清理缓存防止内存溢出
if (logger.isInfoEnabled()) {
logger.info("已处理 {} 条记录", count);
}
}
}
// 处理剩余的记录
if (count % BATCH_SIZE != 0) {
session.flushStatements();
}
session.commit();
long duration = System.currentTimeMillis() - startTime;
logger.info("批量插入完成,总计: {} 条,耗时: {}ms", users.size(), duration);
} catch (Exception e) {
logger.error("批量插入失败", e);
throw new ServiceException("批量插入失败", e);
}
}
}
4. SqlSessionManager:官方线程安全方案
java
public class SqlSessionManagerExample {
private static final Logger logger = LoggerFactory.getLogger(SqlSessionManagerExample.class);
private static SqlSessionManager sessionManager;
static {
String resource = "mybatis-config.xml";
try (InputStream inputStream = Resources.getResourceAsStream(resource)) {
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
sessionManager = SqlSessionManager.newInstance(factory);
logger.info("SqlSessionManager初始化完成");
} catch (IOException e) {
logger.error("SqlSessionManager初始化失败", e);
throw new RuntimeException(e);
}
}
public void performManagedTransaction() {
try {
// 启动受管理的会话
sessionManager.startManagedSession();
UserMapper mapper = sessionManager.getMapper(UserMapper.class);
// 执行多个操作,自动管理事务
User user = new User(null, "Managed User");
mapper.insertUser(user);
if (user.getId() != null) {
user.setName("Updated Managed User");
mapper.updateUser(user);
}
sessionManager.commit();
logger.info("受管理事务执行成功,用户ID: {}", user.getId());
} catch (Exception e) {
try {
sessionManager.rollback();
logger.warn("受管理事务已回滚");
} catch (Exception rollbackEx) {
logger.error("事务回滚失败", rollbackEx);
}
logger.error("受管理事务执行失败", e);
throw new ServiceException("事务执行失败", e);
} finally {
try {
sessionManager.close();
} catch (Exception e) {
logger.warn("关闭SqlSessionManager时发生异常", e);
}
}
}
}
5. 改进的 ThreadLocal 模式
java
public class EnhancedThreadLocalSqlSessionManager {
private static final Logger logger = LoggerFactory.getLogger(EnhancedThreadLocalSqlSessionManager.class);
private static final ThreadLocal<SqlSession> SESSION_HOLDER = new ThreadLocal<SqlSession>() {
@Override
protected void finalize() throws Throwable {
remove();
super.finalize();
}
};
private static final ThreadLocal<Integer> REFERENCE_COUNT = new ThreadLocal<>();
private static SqlSessionFactory sqlSessionFactory;
public static SqlSession getCurrentSession() {
SqlSession session = SESSION_HOLDER.get();
if (session == null || !isSessionValid(session)) {
session = sqlSessionFactory.openSession();
SESSION_HOLDER.set(session);
REFERENCE_COUNT.set(1);
if (logger.isDebugEnabled()) {
logger.debug("为线程{}创建新的SqlSession", Thread.currentThread().getName());
}
} else {
// 引用计数增加
Integer count = REFERENCE_COUNT.get();
REFERENCE_COUNT.set(count == null ? 1 : count + 1);
}
return session;
}
public static void closeCurrentSession() {
Integer count = REFERENCE_COUNT.get();
if (count != null && count > 1) {
REFERENCE_COUNT.set(count - 1);
return;
}
SqlSession session = SESSION_HOLDER.get();
if (session != null) {
try {
session.close();
if (logger.isDebugEnabled()) {
logger.debug("关闭线程{}的SqlSession", Thread.currentThread().getName());
}
} catch (Exception e) {
logger.warn("关闭SqlSession时发生异常", e);
} finally {
SESSION_HOLDER.remove();
REFERENCE_COUNT.remove();
}
}
}
// 确保在try-finally中使用
public static <T> T executeInSession(Function<SqlSession, T> function) {
SqlSession session = getCurrentSession();
try {
return function.apply(session);
} finally {
closeCurrentSession();
}
}
private static boolean isSessionValid(SqlSession session) {
try {
Connection connection = session.getConnection();
return connection != null && connection.isValid(1);
} catch (Exception e) {
logger.warn("检查SqlSession有效性失败", e);
return false;
}
}
}
分布式环境下的最佳方案
java
@Component
public class DistributedSqlSessionManager {
private static final Logger logger = LoggerFactory.getLogger(DistributedSqlSessionManager.class);
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Autowired
private SqlSessionFactory sqlSessionFactory;
// 分布式锁保证关键操作的原子性
public void executeWithDistributedLock(String lockKey, Runnable task) {
String lockValue = UUID.randomUUID().toString();
Boolean acquired = redisTemplate.opsForValue()
.setIfAbsent(lockKey, lockValue, 10, TimeUnit.SECONDS);
if (Boolean.TRUE.equals(acquired)) {
try (SqlSession session = sqlSessionFactory.openSession()) {
task.run();
session.commit();
logger.info("分布式锁执行成功,lockKey: {}", lockKey);
} catch (Exception e) {
logger.error("分布式锁执行失败,lockKey: {}", lockKey, e);
throw e;
} finally {
// 释放分布式锁
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
redisTemplate.execute(new DefaultRedisScript<>(script, Long.class),
Collections.singletonList(lockKey), lockValue);
}
} else {
throw new ConcurrentModificationException("无法获取分布式锁: " + lockKey);
}
}
}
性能监控与诊断工具
增强的 SqlSession 泄漏检测
java
@Component
@ManagedResource(objectName = "com.example:type=MyBatisMonitor,name=SqlSessionLeakDetector")
public class SqlSessionLeakDetector {
private static final Logger logger = LoggerFactory.getLogger(SqlSessionLeakDetector.class);
// 使用ReferenceQueue提高清理效率
private static final ReferenceQueue<SqlSession> referenceQueue = new ReferenceQueue<>();
private static final Map<WeakReference<SqlSession>, SessionInfo> activeSessionsMap =
new ConcurrentHashMap<>();
private static final AtomicLong totalSessions = new AtomicLong();
private static final AtomicLong activeSessions = new AtomicLong();
private static final AtomicLong leakedSessions = new AtomicLong();
private static final ScheduledExecutorService scheduler =
Executors.newScheduledThreadPool(1, r -> {
Thread t = new Thread(r, "SqlSession-Leak-Detector");
t.setDaemon(true);
return t;
});
static {
// 定期检查Session泄漏
scheduler.scheduleAtFixedRate(() -> {
try {
cleanupStaleReferences();
checkLeaks();
} catch (Exception e) {
logger.error("泄漏检测过程中发生异常", e);
}
}, 30, 30, TimeUnit.SECONDS);
}
public static void trackSession(SqlSession session) {
cleanupStaleReferences();
SessionInfo info = new SessionInfo(
Thread.currentThread().getName(),
System.currentTimeMillis(),
Thread.currentThread().getStackTrace()
);
WeakReference<SqlSession> weakRef = new WeakReference<>(session, referenceQueue);
activeSessionsMap.put(weakRef, info);
totalSessions.incrementAndGet();
activeSessions.incrementAndGet();
if (logger.isDebugEnabled()) {
logger.debug("跟踪SqlSession,线程: {}", info.threadName);
}
}
public static void untrackSession(SqlSession session) {
WeakReference<SqlSession> targetRef = null;
for (WeakReference<SqlSession> ref : activeSessionsMap.keySet()) {
if (ref.get() == session) {
targetRef = ref;
break;
}
}
if (targetRef != null) {
SessionInfo info = activeSessionsMap.remove(targetRef);
if (info != null) {
long duration = System.currentTimeMillis() - info.createTime;
activeSessions.decrementAndGet();
if (logger.isDebugEnabled()) {
logger.debug("SqlSession关闭,线程: {},存活时间: {}ms",
info.threadName, duration);
}
}
}
}
private static void cleanupStaleReferences() {
Reference<? extends SqlSession> ref;
while ((ref = referenceQueue.poll()) != null) {
SessionInfo info = activeSessionsMap.remove(ref);
if (info != null) {
activeSessions.decrementAndGet();
}
}
}
private static void checkLeaks() {
long currentTime = System.currentTimeMillis();
int leakCount = 0;
for (Map.Entry<WeakReference<SqlSession>, SessionInfo> entry : activeSessionsMap.entrySet()) {
SessionInfo info = entry.getValue();
long age = currentTime - info.createTime;
if (age > 300000) { // 5分钟
leakCount++;
leakedSessions.incrementAndGet();
logger.warn("检测到可能的SqlSession泄漏,线程: {},存活时间: {}ms",
info.threadName, age);
// 输出创建时的关键堆栈信息
String stackTrace = Arrays.stream(info.stackTrace)
.filter(stack -> stack.getClassName().contains("com.example"))
.map(StackTraceElement::toString)
.collect(Collectors.joining("\n"));
if (!stackTrace.isEmpty()) {
logger.warn("创建堆栈(业务代码):\n{}", stackTrace);
}
}
}
if (leakCount > 0) {
logger.error("检测到{}个可能的SqlSession泄漏", leakCount);
}
}
@ManagedAttribute(description = "总创建的SqlSession数量")
public static long getTotalSessions() {
return totalSessions.get();
}
@ManagedAttribute(description = "当前活跃的SqlSession数量")
public static long getActiveSessions() {
cleanupStaleReferences();
return activeSessions.get();
}
@ManagedAttribute(description = "检测到的泄漏SqlSession数量")
public static long getLeakedSessions() {
return leakedSessions.get();
}
@ManagedOperation(description = "强制检查泄漏")
public static String forceLeakCheck() {
cleanupStaleReferences();
checkLeaks();
return String.format("泄漏检查完成,当前活跃Session数: %d,总泄漏数: %d",
getActiveSessions(), getLeakedSessions());
}
@ManagedOperation(description = "重置统计数据")
public static void resetStatistics() {
totalSessions.set(0);
leakedSessions.set(0);
logger.info("SqlSession统计数据已重置");
}
private static class SessionInfo {
final String threadName;
final long createTime;
final StackTraceElement[] stackTrace;
SessionInfo(String threadName, long createTime, StackTraceElement[] stackTrace) {
this.threadName = threadName;
this.createTime = createTime;
this.stackTrace = stackTrace;
}
}
}
完整的性能监控
java
public class PerformanceMonitoringSqlSession implements SqlSession {
private static final Logger logger = LoggerFactory.getLogger(PerformanceMonitoringSqlSession.class);
private final SqlSession delegate;
private final String sessionId;
private final long createTime;
private final Map<String, OperationMetrics> metricsMap = new ConcurrentHashMap<>();
public PerformanceMonitoringSqlSession(SqlSession delegate) {
this.delegate = delegate;
this.sessionId = UUID.randomUUID().toString().substring(0, 8);
this.createTime = System.currentTimeMillis();
if (logger.isDebugEnabled()) {
logger.debug("创建监控SqlSession: {}", sessionId);
}
// 注册到泄漏检测器
SqlSessionLeakDetector.trackSession(this);
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj instanceof SqlSession) {
return delegate.equals(obj);
}
return false;
}
@Override
public int hashCode() {
return delegate.hashCode();
}
private void recordMetrics(String operation, long duration, boolean success) {
metricsMap.compute(operation, (k, v) -> {
if (v == null) {
v = new OperationMetrics();
}
v.recordOperation(duration, success);
return v;
});
}
@Override
public <T> T selectOne(String statement) {
return selectOne(statement, null);
}
@Override
public <T> T selectOne(String statement, Object parameter) {
long startTime = System.currentTimeMillis();
try {
T result = delegate.selectOne(statement, parameter);
long duration = System.currentTimeMillis() - startTime;
recordMetrics("selectOne", duration, true);
if (logger.isDebugEnabled()) {
logger.debug("Session[{}] selectOne耗时: {}ms, statement: {}",
sessionId, duration, statement);
}
return result;
} catch (Exception e) {
long duration = System.currentTimeMillis() - startTime;
recordMetrics("selectOne-error", duration, false);
logger.error("Session[{}] selectOne失败, statement: {}", sessionId, statement, e);
throw e;
}
}
@Override
public <E> List<E> selectList(String statement) {
return selectList(statement, null);
}
@Override
public <E> List<E> selectList(String statement, Object parameter) {
long startTime = System.currentTimeMillis();
try {
List<E> result = delegate.selectList(statement, parameter);
long duration = System.currentTimeMillis() - startTime;
recordMetrics("selectList", duration, true);
if (logger.isDebugEnabled()) {
logger.debug("Session[{}] selectList耗时: {}ms, statement: {}, 结果数量: {}",
sessionId, duration, statement, result != null ? result.size() : 0);
}
return result;
} catch (Exception e) {
long duration = System.currentTimeMillis() - startTime;
recordMetrics("selectList-error", duration, false);
logger.error("Session[{}] selectList失败, statement: {}", sessionId, statement, e);
throw e;
}
}
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
return delegate.selectList(statement, parameter, rowBounds);
}
@Override
public <K, V> Map<K, V> selectMap(String statement, String mapKey) {
return delegate.selectMap(statement, mapKey);
}
@Override
public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey) {
return delegate.selectMap(statement, parameter, mapKey);
}
@Override
public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {
return delegate.selectMap(statement, parameter, mapKey, rowBounds);
}
@Override
public <T> Cursor<T> selectCursor(String statement) {
return delegate.selectCursor(statement);
}
@Override
public <T> Cursor<T> selectCursor(String statement, Object parameter) {
return delegate.selectCursor(statement, parameter);
}
@Override
public <T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds) {
return delegate.selectCursor(statement, parameter, rowBounds);
}
@Override
public void select(String statement, Object parameter, ResultHandler handler) {
delegate.select(statement, parameter, handler);
}
@Override
public void select(String statement, ResultHandler handler) {
delegate.select(statement, handler);
}
@Override
public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
delegate.select(statement, parameter, rowBounds, handler);
}
@Override
public int insert(String statement) {
return insert(statement, null);
}
@Override
public int insert(String statement, Object parameter) {
long startTime = System.currentTimeMillis();
try {
int result = delegate.insert(statement, parameter);
long duration = System.currentTimeMillis() - startTime;
recordMetrics("insert", duration, true);
if (logger.isDebugEnabled()) {
logger.debug("Session[{}] insert耗时: {}ms, statement: {}, 影响行数: {}",
sessionId, duration, statement, result);
}
return result;
} catch (Exception e) {
long duration = System.currentTimeMillis() - startTime;
recordMetrics("insert-error", duration, false);
logger.error("Session[{}] insert失败, statement: {}", sessionId, statement, e);
throw e;
}
}
@Override
public int update(String statement) {
return update(statement, null);
}
@Override
public int update(String statement, Object parameter) {
long startTime = System.currentTimeMillis();
try {
int result = delegate.update(statement, parameter);
long duration = System.currentTimeMillis() - startTime;
recordMetrics("update", duration, true);
if (logger.isDebugEnabled()) {
logger.debug("Session[{}] update耗时: {}ms, statement: {}, 影响行数: {}",
sessionId, duration, statement, result);
}
return result;
} catch (Exception e) {
long duration = System.currentTimeMillis() - startTime;
recordMetrics("update-error", duration, false);
logger.error("Session[{}] update失败, statement: {}", sessionId, statement, e);
throw e;
}
}
@Override
public int delete(String statement) {
return delete(statement, null);
}
@Override
public int delete(String statement, Object parameter) {
long startTime = System.currentTimeMillis();
try {
int result = delegate.delete(statement, parameter);
long duration = System.currentTimeMillis() - startTime;
recordMetrics("delete", duration, true);
if (logger.isDebugEnabled()) {
logger.debug("Session[{}] delete耗时: {}ms, statement: {}, 影响行数: {}",
sessionId, duration, statement, result);
}
return result;
} catch (Exception e) {
long duration = System.currentTimeMillis() - startTime;
recordMetrics("delete-error", duration, false);
logger.error("Session[{}] delete失败, statement: {}", sessionId, statement, e);
throw e;
}
}
@Override
public void commit() {
commit(false);
}
@Override
public void commit(boolean force) {
long startTime = System.currentTimeMillis();
try {
delegate.commit(force);
long duration = System.currentTimeMillis() - startTime;
recordMetrics("commit", duration, true);
if (logger.isDebugEnabled()) {
logger.debug("Session[{}] commit耗时: {}ms, force: {}", sessionId, duration, force);
}
} catch (Exception e) {
long duration = System.currentTimeMillis() - startTime;
recordMetrics("commit-error", duration, false);
logger.error("Session[{}] commit失败, force: {}", sessionId, force, e);
throw e;
}
}
@Override
public void rollback() {
rollback(false);
}
@Override
public void rollback(boolean force) {
long startTime = System.currentTimeMillis();
try {
delegate.rollback(force);
long duration = System.currentTimeMillis() - startTime;
recordMetrics("rollback", duration, true);
if (logger.isDebugEnabled()) {
logger.debug("Session[{}] rollback耗时: {}ms, force: {}", sessionId, duration, force);
}
} catch (Exception e) {
long duration = System.currentTimeMillis() - startTime;
recordMetrics("rollback-error", duration, false);
logger.error("Session[{}] rollback失败, force: {}", sessionId, force, e);
throw e;
}
}
@Override
public List<BatchResult> flushStatements() {
long startTime = System.currentTimeMillis();
try {
List<BatchResult> result = delegate.flushStatements();
long duration = System.currentTimeMillis() - startTime;
recordMetrics("flushStatements", duration, true);
if (logger.isDebugEnabled()) {
logger.debug("Session[{}] flushStatements耗时: {}ms", sessionId, duration);
}
return result;
} catch (Exception e) {
long duration = System.currentTimeMillis() - startTime;
recordMetrics("flushStatements-error", duration, false);
logger.error("Session[{}] flushStatements失败", sessionId, e);
throw e;
}
}
@Override
public void close() {
long totalTime = System.currentTimeMillis() - createTime;
// 输出性能统计
if (logger.isInfoEnabled() && !metricsMap.isEmpty()) {
StringBuilder sb = new StringBuilder("Session[").append(sessionId).append("] 性能统计:\n");
metricsMap.forEach((operation, metrics) -> {
sb.append(String.format(" %s: 调用次数=%d, 成功次数=%d, 平均耗时=%.2fms, 最大耗时=%dms\n",
operation, metrics.totalCount, metrics.successCount,
metrics.getAverageTime(), metrics.maxTime));
});
sb.append(String.format(" 总存活时间: %dms", totalTime));
logger.info(sb.toString());
}
delegate.close();
SqlSessionLeakDetector.untrackSession(this);
}
@Override
public void clearCache() {
delegate.clearCache();
}
@Override
public Configuration getConfiguration() {
return delegate.getConfiguration();
}
@Override
public <T> T getMapper(Class<T> type) {
return delegate.getMapper(type);
}
@Override
public Connection getConnection() {
return delegate.getConnection();
}
private static class OperationMetrics {
private long totalCount = 0;
private long successCount = 0;
private long totalTime = 0;
private long maxTime = 0;
synchronized void recordOperation(long duration, boolean success) {
totalCount++;
if (success) {
successCount++;
}
totalTime += duration;
maxTime = Math.max(maxTime, duration);
}
double getAverageTime() {
return totalCount > 0 ? (double) totalTime / totalCount : 0;
}
}
}
故障案例分析
案例 1:生产环境连接池耗尽问题
java
public class ConnectionPoolExhaustionCase {
private static final Logger logger = LoggerFactory.getLogger(ConnectionPoolExhaustionCase.class);
// 错误的实现 - 导致连接泄漏
public static class ProblematicService {
public List<User> getAllUsers() {
SqlSession session = null;
try {
session = sqlSessionFactory.openSession();
UserMapper mapper = session.getMapper(UserMapper.class);
return mapper.selectAll();
} catch (Exception e) {
// 问题1:异常时未关闭session,导致连接泄漏
logger.error("查询失败", e);
return Collections.emptyList();
}
// 问题2:正常情况下也忘记关闭session
}
}
// 正确的实现方式1:try-with-resources
public static class FixedServiceV1 {
public List<User> getAllUsers() {
try (SqlSession session = sqlSessionFactory.openSession()) {
UserMapper mapper = session.getMapper(UserMapper.class);
List<User> users = mapper.selectAll();
logger.info("查询到{}个用户", users.size());
return users;
} catch (Exception e) {
logger.error("查询用户失败", e);
throw new ServiceException("查询用户失败", e);
}
}
}
// 正确的实现方式2:手动管理
public static class FixedServiceV2 {
public List<User> getAllUsers() {
SqlSession session = null;
try {
session = sqlSessionFactory.openSession();
UserMapper mapper = session.getMapper(UserMapper.class);
List<User> users = mapper.selectAll();
logger.info("查询到{}个用户", users.size());
return users;
} catch (Exception e) {
logger.error("查询用户失败", e);
throw new ServiceException("查询用户失败", e);
} finally {
if (session != null) {
try {
session.close();
} catch (Exception e) {
logger.warn("关闭SqlSession时发生异常", e);
}
}
}
}
}
}
案例 2:事务状态混乱导致数据不一致
java
public class TransactionStateConfusionCase {
private static final Logger logger = LoggerFactory.getLogger(TransactionStateConfusionCase.class);
// 问题代码:多线程共享SqlSession导致事务状态混乱
public static class ProblematicTransactionService {
private final SqlSession sharedSession;
public ProblematicTransactionService() {
this.sharedSession = sqlSessionFactory.openSession();
}
public void transferMoney(Long fromUserId, Long toUserId, BigDecimal amount) {
try {
AccountMapper mapper = sharedSession.getMapper(AccountMapper.class);
// 问题:在多线程环境下,这些操作可能被其他线程的操作打断
Account fromAccount = mapper.selectById(fromUserId);
if (fromAccount.getBalance().compareTo(amount) < 0) {
throw new InsufficientFundsException("余额不足");
}
mapper.debit(fromUserId, amount);
mapper.credit(toUserId, amount);
sharedSession.commit(); // 可能提交其他线程的操作
logger.info("转账成功:{} -> {},金额:{}", fromUserId, toUserId, amount);
} catch (Exception e) {
sharedSession.rollback(); // 可能回滚其他线程的操作
logger.error("转账失败:{} -> {},金额:{}", fromUserId, toUserId, amount, e);
throw new ServiceException("转账失败", e);
}
}
}
// 正确实现:每个操作使用独立的SqlSession
public static class FixedTransactionService {
@Transactional(rollbackFor = Exception.class)
public void transferMoney(Long fromUserId, Long toUserId, BigDecimal amount) {
// 参数验证
if (fromUserId == null || toUserId == null || amount == null) {
throw new IllegalArgumentException("参数不能为空");
}
if (amount.compareTo(BigDecimal.ZERO) <= 0) {
throw new IllegalArgumentException("转账金额必须大于0");
}
if (fromUserId.equals(toUserId)) {
throw new IllegalArgumentException("不能向自己转账");
}
try (SqlSession session = sqlSessionFactory.openSession()) {
AccountMapper mapper = session.getMapper(AccountMapper.class);
// 加锁查询,防止并发修改
Account fromAccount = mapper.selectByIdForUpdate(fromUserId);
Account toAccount = mapper.selectByIdForUpdate(toUserId);
if (fromAccount == null) {
throw new AccountNotFoundException("转出账户不存在");
}
if (toAccount == null) {
throw new AccountNotFoundException("转入账户不存在");
}
if (fromAccount.getBalance().compareTo(amount) < 0) {
throw new InsufficientFundsException("余额不足");
}
mapper.debit(fromUserId, amount);
mapper.credit(toUserId, amount);
session.commit();
logger.info("转账成功:{} -> {},金额:{}", fromUserId, toUserId, amount);
} catch (Exception e) {
logger.error("转账失败:{} -> {},金额:{}", fromUserId, toUserId, amount, e);
throw new ServiceException("转账失败", e);
}
}
}
}
性能调优检查
代码层面优化
- 启用批处理执行器 (ExecutorType.BATCH)
- 合理设置 fetchSize 避免内存溢出
- 使用懒加载避免 N+1 问题
- 优化动态 SQL 减少字符串拼接
- 合理使用一级和二级缓存
- 避免在循环中执行数据库操作
配置层面优化
- 配置合适的连接池大小
- 启用预编译语句缓存
- 设置合理的超时时间
- 配置连接泄漏检测
- 启用 SQL 执行统计
监控层面优化
- 监控慢查询并优化
- 定期分析执行计划
- 监控连接池使用情况
- 跟踪 SqlSession 生命周期
- 设置 JMX 监控告警
选择决策

总结对比
方案 | 线程安全性 | 性能 | 复杂度 | 适用场景 | 推荐指数 |
---|---|---|---|---|---|
Spring Boot + @Transactional | ✅ 完全安全 | ⭐⭐⭐⭐ | ⭐⭐ | 企业级应用推荐方案 | ⭐⭐⭐⭐⭐ |
SqlSessionManager | ✅ 完全安全 | ⭐⭐⭐ | ⭐⭐⭐ | 非 Spring 环境的最佳选择 | ⭐⭐⭐⭐ |
ThreadLocal + executeInSession | ✅ 完全安全 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | 需要跨方法事务的场景 | ⭐⭐⭐ |
try-with-resources | ✅ 完全安全 | ⭐⭐⭐⭐⭐ | ⭐ | 简单查询操作 | ⭐⭐⭐⭐⭐ |
手动 finally 管理 | ✅ 完全安全 | ⭐⭐⭐⭐ | ⭐⭐ | 需要精确控制的场景 | ⭐⭐⭐ |
共享 SqlSession | ❌ 非线程安全 | ⭐ | ⭐ | 绝对不推荐 | ❌ |
核心建议
-
企业级应用首选 Spring Boot 集成方案
- 利用成熟的事务管理机制和连接池
- 声明式事务配置,代码简洁
- 自动资源管理,避免泄漏风险
- 完善的监控和健康检查
-
非 Spring 环境使用 SqlSessionManager
- MyBatis 官方提供的线程安全方案
- API 设计合理,使用简单
- 适合微服务或轻量级应用
-
简单查询操作使用 try-with-resources
- 代码最简洁,资源管理最安全
- Java 7+原生支持
- 适合单一操作场景
-
生产环境必备监控
- 配置连接池泄漏检测
- 启用 SqlSession 泄漏检测器
- 监控 SQL 执行性能
- 设置 JMX 监控和告警
-
开发阶段严格检查
- 代码审查重点关注资源管理
- 单元测试覆盖并发场景
- 集成测试验证事务边界
- 性能测试验证系统容量