找回密码
 立即注册
首页 业界区 业界 Spring+MyBatis环境下SqlSession管理机制详解

Spring+MyBatis环境下SqlSession管理机制详解

科元料 4 小时前
在生产环境中,我们几乎从不手动管理SqlSession,而是由Spring框架来管理。让我详细解释这套机制。
一、Spring+MyBatis的整合模式

1.1 核心组件:SqlSessionTemplate
  1. // Spring管理SqlSession的核心组件
  2. public class SqlSessionTemplate implements SqlSession, DisposableBean {
  3.    
  4.     private final SqlSessionFactory sqlSessionFactory;
  5.     private final ExecutorType executorType;
  6.    
  7.     // 关键:通过ThreadLocal为每个线程绑定SqlSession
  8.     private final ThreadLocal<SqlSession> sqlSessionHolder =
  9.         new ThreadLocal<>();
  10.    
  11.     // 执行数据库操作
  12.     public <T> T selectOne(String statement) {
  13.         // 1. 获取当前线程的SqlSession
  14.         SqlSession sqlSession = getSqlSession();
  15.         try {
  16.             // 2. 执行查询
  17.             return sqlSession.selectOne(statement);
  18.         } finally {
  19.             // 3. 不关闭!由Spring管理生命周期
  20.         }
  21.     }
  22. }
复制代码
二、Spring如何管理SqlSession生命周期

2.1 三种主要模式

模式1:Mapper代理模式(最常用)
  1. // 配置类
  2. @Configuration
  3. @MapperScan("com.example.mapper")  // 自动扫描Mapper接口
  4. public class MyBatisConfig {
  5.     @Bean
  6.     public SqlSessionFactory sqlSessionFactory(DataSource dataSource) {
  7.         SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
  8.         factoryBean.setDataSource(dataSource);
  9.         return factoryBean.getObject();
  10.     }
  11. }
  12. // Service中使用
  13. @Service
  14. public class UserService {
  15.     // 直接注入Mapper接口,不需要知道SqlSession
  16.     @Autowired
  17.     private UserMapper userMapper;
  18.    
  19.     public User getUser(Long id) {
  20.         // Spring自动管理SqlSession
  21.         return userMapper.selectById(id);
  22.     }
  23. }
复制代码
模式2:SqlSessionTemplate直接使用
  1. @Service
  2. public class UserService {
  3.    
  4.     @Autowired
  5.     private SqlSessionTemplate sqlSessionTemplate;
  6.    
  7.     public User getUser(Long id) {
  8.         // 手动获取Mapper(较少使用)
  9.         UserMapper mapper = sqlSessionTemplate.getMapper(UserMapper.class);
  10.         return mapper.selectById(id);
  11.     }
  12. }
复制代码
2.2 核心问题解答:一次请求是否对应一个SqlSession?

答案是:取决于事务配置!
三、不同场景下的SqlSession管理

3.1 无事务方法:每次Mapper方法调用都是新的SqlSession
  1. @Service
  2. public class UserService {
  3.    
  4.     @Autowired
  5.     private UserMapper userMapper;
  6.    
  7.     // 没有@Transactional注解
  8.     public void demoNoTransaction() {
  9.         System.out.println("=== 无事务方法演示 ===");
  10.         
  11.         // 第一次查询:创建新的SqlSession1
  12.         User user1 = userMapper.selectById(1);
  13.         System.out.println("查询1:创建SqlSession1,执行SQL");
  14.         
  15.         // 第二次查询:创建新的SqlSession2
  16.         User user2 = userMapper.selectById(1);
  17.         System.out.println("查询2:创建SqlSession2,执行SQL");
  18.         
  19.         // 结论:两次查询在两个不同的SqlSession中
  20.         // 一级缓存不生效!会执行两次SQL
  21.     }
  22. }
复制代码
3.2 有事务方法:整个方法使用同一个SqlSession
  1. @Service
  2. public class UserService {
  3.    
  4.     @Autowired
  5.     private UserMapper userMapper;
  6.    
  7.     @Transactional  // 关键注解!
  8.     public void demoWithTransaction() {
  9.         System.out.println("=== 有事务方法演示 ===");
  10.         
  11.         // 第一次查询:创建SqlSession
  12.         User user1 = userMapper.selectById(1);
  13.         System.out.println("查询1:创建SqlSession,执行SQL");
  14.         
  15.         // 第二次查询:复用同一个SqlSession
  16.         User user2 = userMapper.selectById(1);
  17.         System.out.println("查询2:复用SqlSession,从一级缓存读取,无SQL");
  18.         
  19.         // 第三次查询不同参数
  20.         User user3 = userMapper.selectById(2);
  21.         System.out.println("查询3:参数不同,执行SQL");
  22.         
  23.         // 第四次查询相同参数
  24.         User user4 = userMapper.selectById(2);
  25.         System.out.println("查询4:从一级缓存读取,无SQL");
  26.         
  27.         // 方法结束:提交事务,关闭SqlSession
  28.         System.out.println("方法结束:提交事务,关闭SqlSession");
  29.         
  30.         // 结论:整个方法在同一个SqlSession中
  31.         // 一级缓存生效!相同查询只执行一次SQL
  32.     }
  33. }
复制代码
3.3 Web请求场景:通常每个请求是一个事务
  1. @RestController
  2. @RequestMapping("/users")
  3. public class UserController {
  4.    
  5.     @Autowired
  6.     private UserService userService;
  7.    
  8.     @GetMapping("/{id}")
  9.     // @Transactional  // Controller层一般不直接加事务
  10.     public User getUser(@PathVariable Long id) {
  11.         // 通常Service方法有@Transactional
  12.         return userService.getUserWithCache(id);
  13.     }
  14. }
  15. @Service
  16. public class UserService {
  17.    
  18.     @Transactional  // Service层加事务,一个HTTP请求对应一个事务
  19.     public User getUserWithCache(Long id) {
  20.         // 这个Service方法中的所有数据库操作
  21.         // 都在同一个SqlSession中
  22.         // 一级缓存生效
  23.         return userMapper.selectById(id);
  24.     }
  25. }
复制代码
四、Spring事务管理机制

4.1 事务管理器与SqlSession绑定
  1. // Spring的事务管理机制
  2. public class SpringManagedTransaction implements Transaction {
  3.    
  4.     private final DataSource dataSource;
  5.     private Connection connection;
  6.     private boolean isConnectionTransactional;
  7.    
  8.     // 获取连接(关键:从事务同步管理器中获取)
  9.     @Override
  10.     public Connection getConnection() throws SQLException {
  11.         if (this.connection == null) {
  12.             openConnection();
  13.         }
  14.         return this.connection;
  15.     }
  16.    
  17.     private void openConnection() throws SQLException {
  18.         // 从Spring的事务同步管理器中获取连接
  19.         this.connection = DataSourceUtils.getConnection(this.dataSource);
  20.         this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(
  21.             this.connection, this.dataSource);
  22.     }
  23. }
复制代码
4.2 事务传播行为的影响
  1. @Service
  2. public class ComplexService {
  3.    
  4.     @Autowired
  5.     private UserMapper userMapper;
  6.     @Autowired
  7.     private OrderMapper orderMapper;
  8.    
  9.     // 外层事务
  10.     @Transactional(propagation = Propagation.REQUIRED)
  11.     public void complexBusiness(Long userId) {
  12.         // 在事务中:创建/获取SqlSession1
  13.         User user = userMapper.selectById(userId);  // SqlSession1
  14.         
  15.         // 调用内层方法(默认REQUIRED,加入现有事务)
  16.         processOrders(userId);  // 仍然使用SqlSession1
  17.         
  18.         // 结论:整个方法使用同一个SqlSession
  19.     }
  20.    
  21.     @Transactional(propagation = Propagation.REQUIRES_NEW)
  22.     public void processOrders(Long userId) {
  23.         // 创建新的事务,新的SqlSession2
  24.         List<Order> orders = orderMapper.selectByUserId(userId);  // SqlSession2
  25.         
  26.         // 与complexBusiness方法不在同一个SqlSession
  27.         // 一级缓存不共享
  28.     }
  29.    
  30.     @Transactional(propagation = Propagation.NOT_SUPPORTED)
  31.     public void logOperation(Long userId) {
  32.         // 非事务执行,没有SqlSession绑定
  33.         // 每次数据库操作可能使用不同的SqlSession
  34.     }
  35. }
复制代码
五、如何判断当前是否在同一个SqlSession中

5.1 调试方法:查看SqlSession ID
  1. @Service
  2. public class DebugService {
  3.    
  4.     @Autowired
  5.     private SqlSessionTemplate sqlSessionTemplate;
  6.    
  7.     @Transactional
  8.     public void debugSqlSession() {
  9.         // 方法1:获取当前SqlSession(仅用于调试)
  10.         SqlSession sqlSession = sqlSessionTemplate.getSqlSessionFactory()
  11.             .openSession();
  12.         
  13.         // 获取SqlSession的唯一标识
  14.         System.out.println("SqlSession hashCode: " + sqlSession.hashCode());
  15.         
  16.         // 方法2:通过反射查看ThreadLocal中的SqlSession
  17.         try {
  18.             Field field = sqlSessionTemplate.getClass()
  19.                 .getDeclaredField("sqlSessionHolder");
  20.             field.setAccessible(true);
  21.             ThreadLocal<SqlSession> holder = (ThreadLocal<SqlSession>) field.get(sqlSessionTemplate);
  22.             SqlSession currentSession = holder.get();
  23.             System.out.println("Current SqlSession: " + currentSession.hashCode());
  24.         } catch (Exception e) {
  25.             e.printStackTrace();
  26.         }
  27.     }
  28. }
复制代码
5.2 实践判断方法
  1. public class SqlSessionInspector {
  2.    
  3.     /**
  4.      * 判断两个操作是否在同一个SqlSession中的实用方法
  5.      *
  6.      * 规则:
  7.      * 1. 在同一个@Transactional方法中 → 同一个SqlSession ✓
  8.      * 2. 在同一个Service方法中(无@Transactional)→ 可能不同 ✗
  9.      * 3. 跨Service方法调用 → 取决于事务传播行为
  10.      */
  11.     public static boolean isSameSqlSession() {
  12.         // 实际判断逻辑:
  13.         // 1. 查看当前线程是否有活跃事务
  14.         TransactionStatus status = TransactionAspectSupport.currentTransactionStatus();
  15.         return status != null && !status.isCompleted();
  16.     }
  17. }
复制代码
六、生产环境配置示例

6.1 Spring Boot + MyBatis配置
  1. # application.yml
  2. mybatis:
  3.   configuration:
  4.     cache-enabled: true  # 开启二级缓存
  5.     local-cache-scope: statement  # 一级缓存作用域
  6.    
  7. spring:
  8.   datasource:
  9.     url: jdbc:mysql://localhost:3306/test
  10.     username: root
  11.     password: 123456
  12.     hikari:
  13.       maximum-pool-size: 20
  14.       
  15.   # 事务管理配置
  16.   transaction:
  17.     default-timeout: 30  # 事务超时时间30秒
复制代码
6.2 事务配置类
  1. @Configuration
  2. @EnableTransactionManagement  // 启用事务管理
  3. public class TransactionConfig {
  4.    
  5.     @Bean
  6.     public PlatformTransactionManager transactionManager(DataSource dataSource) {
  7.         return new DataSourceTransactionManager(dataSource);
  8.     }
  9.    
  10.     @Bean
  11.     public TransactionTemplate transactionTemplate(PlatformTransactionManager manager) {
  12.         TransactionTemplate template = new TransactionTemplate(manager);
  13.         template.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
  14.         template.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
  15.         return template;
  16.     }
  17. }
复制代码
七、实际案例分析

7.1 案例1:Web应用中的典型流程
  1. // HTTP请求:GET /users/1
  2. // 1. 请求到达DispatcherServlet
  3. // 2. 调用UserController.getUser(1)
  4. // 3. 调用UserService.getUserWithCache(1)
  5. @Slf4j
  6. @Service
  7. public class UserService {
  8.    
  9.     @Autowired
  10.     private UserMapper userMapper;
  11.    
  12.     @Transactional(readOnly = true)
  13.     public User getUserWithCache(Long id) {
  14.         log.info("开始查询用户 {}", id);
  15.         
  16.         // 第一次查询
  17.         long start1 = System.currentTimeMillis();
  18.         User user1 = userMapper.selectById(id);
  19.         log.info("第一次查询耗时: {}ms", System.currentTimeMillis() - start1);
  20.         
  21.         // 第二次查询(应该从一级缓存读取)
  22.         long start2 = System.currentTimeMillis();
  23.         User user2 = userMapper.selectById(id);
  24.         log.info("第二次查询耗时: {}ms", System.currentTimeMillis() - start2);
  25.         
  26.         // 验证是同一个对象(一级缓存直接返回引用)
  27.         boolean sameReference = (user1 == user2);
  28.         log.info("两次查询返回同一对象: {}", sameReference);
  29.         
  30.         return user2;
  31.     }
  32. }
  33. // 日志输出:
  34. // 第一次查询耗时: 15ms  (执行SQL)
  35. // 第二次查询耗时: 0ms   (一级缓存)
  36. // 两次查询返回同一对象: true
复制代码
7.2 案例2:批量操作优化
  1. @Service
  2. public class BatchService {
  3.    
  4.     @Autowired
  5.     private UserMapper userMapper;
  6.    
  7.     // 错误示例:非事务中批量查询
  8.     public List<User> getUsersWrong(List<Long> ids) {
  9.         List<User> users = new ArrayList<>();
  10.         for (Long id : ids) {
  11.             // 每次循环都创建新的SqlSession
  12.             User user = userMapper.selectById(id);  // N次SQL查询
  13.             users.add(user);
  14.         }
  15.         return users;
  16.     }
  17.    
  18.     // 正确示例1:使用事务包装
  19.     @Transactional(readOnly = true)
  20.     public List<User> getUsersRight1(List<Long> ids) {
  21.         List<User> users = new ArrayList<>();
  22.         for (Long id : ids) {
  23.             // 同一个SqlSession,但相同查询只执行一次
  24.             User user = userMapper.selectById(id);
  25.             users.add(user);
  26.         }
  27.         return users;
  28.     }
  29.    
  30.     // 正确示例2:批量查询方法
  31.     public List<User> getUsersRight2(List<Long> ids) {
  32.         // 一次SQL查询,返回所有结果
  33.         return userMapper.selectByIds(ids);
  34.     }
  35.    
  36.     // 正确示例3:使用IN查询
  37.     @Select("")
  38.     List<User> selectByIds(@Param("ids") List<Long> ids);
  39. }
复制代码
八、最佳实践总结

8.1 判断规则总结

场景是否同一个SqlSession一级缓存是否生效同一个@Transactional方法内✓ 是✓ 生效同一个非事务方法内✗ 否(每次调用新建)✗ 不生效跨@Transactional方法调用取决于传播行为可能不生效Web请求(Service有@Transactional)✓ 是(每个请求一个)✓ 生效异步方法(@Async)✗ 否(不同线程)✗ 不生效8.2 生产环境建议


  • Service层添加事务注解
    1. @Service
    2. @Transactional(readOnly = true)  // 类级别默认只读
    3. public class UserService {
    4.    
    5.     @Transactional  // 写操作单独标记
    6.     public User updateUser(User user) {
    7.         // ...
    8.     }
    9. }
    复制代码
  • 合理设置事务边界
    1. // 合适的事务边界:一个完整的业务操作
    2. @Transactional
    3. public Order createOrder(OrderRequest request) {
    4.     // 验证库存
    5.     // 扣减库存
    6.     // 创建订单
    7.     // 记录日志
    8.     // 所有操作在同一个事务中
    9. }
    复制代码
  • 监控SqlSession使用
    1. // 添加监控点
    2. @Aspect
    3. @Component
    4. public class SqlSessionMonitor {
    5.    
    6.     @Around("@within(org.springframework.stereotype.Service)")
    7.     public Object monitor(ProceedingJoinPoint joinPoint) throws Throwable {
    8.         String methodName = joinPoint.getSignature().getName();
    9.         long start = System.currentTimeMillis();
    10.         
    11.         try {
    12.             return joinPoint.proceed();
    13.         } finally {
    14.             long duration = System.currentTimeMillis() - start;
    15.             if (duration > 1000) {  // 超过1秒警告
    16.                 log.warn("方法 {} 执行时间过长: {}ms", methodName, duration);
    17.             }
    18.         }
    19.     }
    20. }
    复制代码
  • 一级缓存注意事项

    • 长事务可能导致一级缓存积累大量数据
    • 考虑设置localCacheScope=STATEMENT(每次查询后清空一级缓存)
    • 复杂对象修改可能影响缓存中的对象

九、常见问题解答

Q1:Spring Boot中默认的SqlSession管理策略是什么?

A:Spring Boot + MyBatis Starter默认配置:

  • 使用SqlSessionTemplate管理SqlSession
  • 默认不开启事务时,每个Mapper方法调用使用新的SqlSession
  • 开启事务时,整个事务使用同一个SqlSession
Q2:如何查看当前是否有活跃的SqlSession?
  1. // 方法1:通过TransactionSynchronizationManager
  2. boolean hasActiveTransaction = TransactionSynchronizationManager
  3.     .isActualTransactionActive();
  4. // 方法2:通过DataSourceUtils
  5. Connection conn = DataSourceUtils.getConnection(dataSource);
  6. boolean isTransactional = DataSourceUtils.isConnectionTransactional(conn, dataSource);
复制代码
Q3:一级缓存在微服务架构中有什么问题?

A:在微服务中:

  • 每个实例有自己的JVM,一级缓存不共享
  • 可能导致不同实例数据不一致
  • 建议:关闭一级缓存或设置很短的作用域,使用分布式缓存
Q4:如何强制清空一级缓存?
  1. @Service
  2. public class CacheService {
  3.    
  4.     @Autowired
  5.     private SqlSessionTemplate sqlSessionTemplate;
  6.    
  7.     public void clearLocalCache() {
  8.         // 获取当前SqlSession并清空缓存
  9.         sqlSessionTemplate.clearCache();
  10.     }
  11.    
  12.     @Transactional
  13.     public void updateAndClear() {
  14.         // 更新操作会自动清空相关缓存
  15.         userMapper.updateUser(user);
  16.         
  17.         // 如果需要手动清空
  18.         sqlSessionTemplate.clearCache();
  19.     }
  20. }
复制代码
总结

在生产环境中,Spring通过事务管理自动控制SqlSession的生命周期

  • 没有事务:每次Mapper方法调用都创建新的SqlSession
  • 有事务:整个事务使用同一个SqlSession(通常是一个HTTP请求对应一个事务)
关键判断标准

  • 查看方法是否有@Transactional注解
  • 同一个@Transactional方法内 → 同一个SqlSession → 一级缓存生效
  • 不同方法或没有事务 → 不同SqlSession → 一级缓存不生效
实际开发中,我们通常:

  • 在Service层方法添加@Transactional
  • 让Spring自动管理SqlSession
  • 通过事务边界控制一级缓存的作用范围
  • 监控长事务避免一级缓存过大
这样既能享受一级缓存的性能优势,又避免了手动管理SqlSession的复杂性。

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册