MyBatis 中 SqlSessionFactory 和 SqlSession 的线程安全性深度分析

在 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 的线程安全性体现在:

  1. Configuration 对象构建后只读 - 所有配置信息不可变
  2. 内部使用 ConcurrentHashMap 等线程安全数据结构 - 确保并发访问安全
  3. 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 ❌ 非线程安全 绝对不推荐

核心建议

  1. 企业级应用首选 Spring Boot 集成方案

    • 利用成熟的事务管理机制和连接池
    • 声明式事务配置,代码简洁
    • 自动资源管理,避免泄漏风险
    • 完善的监控和健康检查
  2. 非 Spring 环境使用 SqlSessionManager

    • MyBatis 官方提供的线程安全方案
    • API 设计合理,使用简单
    • 适合微服务或轻量级应用
  3. 简单查询操作使用 try-with-resources

    • 代码最简洁,资源管理最安全
    • Java 7+原生支持
    • 适合单一操作场景
  4. 生产环境必备监控

    • 配置连接池泄漏检测
    • 启用 SqlSession 泄漏检测器
    • 监控 SQL 执行性能
    • 设置 JMX 监控和告警
  5. 开发阶段严格检查

    • 代码审查重点关注资源管理
    • 单元测试覆盖并发场景
    • 集成测试验证事务边界
    • 性能测试验证系统容量
相关推荐
coderSong256833 分钟前
Java高级 |【实验八】springboot 使用Websocket
java·spring boot·后端·websocket
Mr_Air_Boy1 小时前
SpringBoot使用dynamic配置多数据源时使用@Transactional事务在非primary的数据源上遇到的问题
java·spring boot·后端
豆沙沙包?2 小时前
2025年- H77-Lc185--45.跳跃游戏II(贪心)--Java版
java·开发语言·游戏
年老体衰按不动键盘2 小时前
快速部署和启动Vue3项目
java·javascript·vue
咖啡啡不加糖2 小时前
Redis大key产生、排查与优化实践
java·数据库·redis·后端·缓存
liuyang-neu2 小时前
java内存模型JMM
java·开发语言
UFIT3 小时前
NoSQL之redis哨兵
java·前端·算法
刘 大 望3 小时前
数据库-联合查询(内连接外连接),子查询,合并查询
java·数据库·sql·mysql
怀旧,3 小时前
【数据结构】6. 时间与空间复杂度
java·数据结构·算法
江城开朗的豌豆3 小时前
JavaScript篇:函数间的悄悄话:callee和caller的那些事儿
javascript·面试