Spring事务For循环中的代码单独为一个事务,循环一次提交一次事务

这篇博客探讨了在Java业务中如何处理for循环内的事务,确保每个操作的原子性和独立性。作者提出了两种解决方案:一是通过设置事务隔离级别为`Propagation.REQUIRES_NEW`,使得每个循环内部开启新的事务,异常发生时仅影响单个事务;二是利用保存点(savepoint)来控制事务的回滚范围,异常时回滚到指定保存点,从而实现事务的局部回滚。这两种方法都避免了外部代码环境受到影响。

最近业务碰到for循环事务,也就是一个不成功,所有的操作全部回滚。需求做到每个for循环中的代码要单独回滚单独提交,不能影响外部代码环境,研究过之后,记录开发过程

  @Transactional(rollbackFor = Exception.class)
    public void aaa(List<String> toDoIds){
        for (String toDoId : toDoIds) {
            try {
                //内层新建事务,for循环内单个事务单个提交或者回滚
                sissCommonService.updateOrderInfoPersonOutOrgan(toDoId);
            }catch (Exception e){
                e.printStackTrace();
            }
        }

    }

解决方案一: 【事务隔离级别】

新建了一个公共Service用来解决事务嵌套的问题,在单独提出来的方法上加上了事务,并且制定了事务的传播机制为Propagation.REQUIRES_NEW,具体代码

@Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW)
    public void updateOrderInfoPersonOutOrgan(String toDoId) throws Exception{
}

Propagation.REQUIRES_NEW的意思是,如果调用者当前有事务,就将事务挂起,重新开启事务。这样调用这个方法后,这个方法单独开启事务,每次执行完,单独提交,遇到异常单独回滚,外部方法收到抛出的异常并不处理就好,这样内外事务互不影响。

解决方案二:【设置回滚点】
使用Object savePoint = TransactionAspectSupport.currentTransactionStatus().createSavepoint(); 
设置回滚点

使用TransactionAspectSupport.currentTransactionStatus().rollbackToSavepoint(savePoint);

回滚到savePoint。 

       Object savePoint = null;
            try {
                markOrderService.handleMarketSell(strategyOrderParam);
                //设置回滚点
                savePoint = TransactionAspectSupport.currentTransactionStatus().createSavepoint();
            } catch (Exception e) {
                //有异常则把方法里面的事务进行回滚
TransactionAspectSupport.currentTransactionStatus().rollbackToSavepoint(savePoint);
                e.printStackTrace();
            }

<think>我们正在讨论的问题核心是:在for循环中多次执行相同的SQL查询,这些查询是否属于同一个SQL会话? 根据之前的知识,SQL会话(SqlSession)的关键特征包括:连接标识相同、事务状态一致、一级缓存共享。 现在考虑for循环中执行相同SQL查询的场景: 1. 如果这些查询是在同一个SqlSession实例中执行的,那么它们属于同一个SQL会话。 2. 如果每次查询都创建了新的SqlSession,那么它们属于不同的会话。 在代码中,我们通常通过以下方式控制SqlSession: - 在MyBatis中,我们可以手动管理SqlSession(通过sqlSessionFactory.openSession()获取,然后在该会话中执行多个操作,最后关闭)。 - 在Spring中,我们可能使用事务管理(如@Transactional)来自动管理SqlSession。 因此,我们需要看具体的代码实现。 假设我们有以下两种常见情况: 情况1:在同一个SqlSession中执行循环查询 ```java try (SqlSession session = sqlSessionFactory.openSession()) { UserMapper mapper = session.getMapper(UserMapper.class); for (int i = 0; i < 10; i++) { User user = mapper.selectById(1); // 多次执行相同的查询 } } ``` 在这个例子中,所有的查询都在同一个SqlSession中执行,因此属于同一个SQL会话。这意味着: - 第一次查询会访问数据库,并将结果放入一级缓存。 - 后续的查询(第2次到第10次)会直接从一级缓存中获取结果,不会访问数据库(除非中间有更新操作使缓存失效)。 - 所有查询使用相同的数据库连接(相同的连接ID)。 情况2:每次循环都创建新的SqlSession ```java for (int i = 0; i < 10; i++) { try (SqlSession session = sqlSessionFactory.openSession()) { UserMapper mapper = session.getMapper(UserMapper.class); User user = mapper.selectById(1); // 每次都在新的SqlSession中查询 } } ``` 在这个例子中,每次循环都创建并关闭一个新的SqlSession,因此每次查询都是独立的SQL会话。这意味着: - 每次查询都会访问数据库(因为每个会话的一级缓存都是独立的,且每次都是新的,所以缓存中没有数据)。 - 每个查询使用不同的数据库连接(连接ID不同)。 因此,判断循环中的多次查询是否属于同一个SQL会话,取决于代码中SqlSession的管理方式。 另外,引用[4]中提到,在循环中查询数据库是一种常见的性能问题(循环查询),并建议使用IN查询等方式进行优化。这是因为每次查询都会建立和关闭连接(如果是情况2),即使使用连接池,建立和释放连接也有开销,而且多次查询增加了数据库的负担。 总结: - 如果在循环中使用同一个SqlSession实例,则属于同一个SQL会话。 - 如果在循环中每次创建新的SqlSession,则每次查询属于不同的SQL会话。 验证方法: 1. 查看数据库连接ID:在每次查询时打印或记录连接ID(如MySQL的CONNECTION_ID()),如果相同则是同一会话。 2. 观察日志:如果使用了MyBatis日志,可以看到只有第一次查询发送了SQL,后续命中缓存(同一会话);如果每次都有SQL日志,则是不同会话。 因此,回答用户的问题:不一定,取决于代码中SqlSession的生命周期管理。 注意:在Web应用中,如果使用Spring事务管理,并且方法被@Transactional注解,那么整个方法中的数据库操作(包括循环中的操作)默认都在同一个SqlSession(以及同一个事务)中执行。例如: ```java @Transactional public void myMethod() { for (int i = 0; i < 10; i++) { userRepository.findById(1); // 在同一个事务中,因此同一个SqlSession } } ``` 此时,循环中的查询属于同一个SQL会话。 所以,最终结论是:需要根据代码中SqlSession的创建和关闭时机来判断。如果循环内的查询操作都在同一个SqlSession实例内执行,则属于同一个SQL会话;否则,属于不同的会话。 引用说明: - 引用[4]提到了循环查询的性能问题,并建议使用IN查询替代循环查询,这从侧面说明在循环中查询数据库可能每次都会创建新的会话(或者即使同一会话,但多次查询也不如一次批量查询高效)。但引用[4]并没有直接说明会话的问题,而是关注性能优化。 我们可以在回答中引用[4]来提醒用户注意循环查询的性能问题。 根据用户提供的引用[1](循环执行SQL)和引用[4](循环查询的性能问题),我们可以这样组织回答: 回答结构: 1. 直接回答问题:不一定,取决于SqlSession的管理方式。 2. 分两种情况讨论(同会话和不同会话)。 3. 给出验证方法(连接ID、日志)。 4. 结合引用[4]提醒性能优化。 同时,根据用户上一次的问题(如何判断多个操作是否在同一SqlSession),我们也可以关联回答。 最后,生成相关问题。</think>### 循环中多次执行相同 SQL 查询是否属于同一个 SQL 会话? 判断 **for 循环中多次执行相同 SQL 查询是否属于同一个 SQL 会话**,取决于代码中数据库连接的管理方式。以下是关键分析: --- #### 1. **相同 SQL 会话的典型场景** 当满足以下条件时,循环中的查询属于同一个 SQL 会话: ```java // MyBatis 示例(同一会话) try (SqlSession session = sqlSessionFactory.openSession()) { UserMapper mapper = session.getMapper(UserMapper.class); for (int i = 0; i < 10; i++) { // 所有查询共享同一会话 User user = mapper.selectById(1); } session.commit(); // 统一提交 } ``` **特征**: - **连接标识相同**:所有查询使用相同的数据库连接 ID(可通过 `SELECT CONNECTION_ID()` 验证)[^2]。 - **一级缓存生效**:第一次查询后,后续查询直接从缓存返回结果(无数据库访问日志)[^1]。 - **事务统一管理**:循环结束后统一提交事务,中间修改对其他会话不可见。 --- #### 2. **不同 SQL 会话的典型场景** 当每次循环都创建新会话时: ```java // 每次循环创建新会话(不同会话) for (int i = 0; i < 10; i++) { try (SqlSession session = sqlSessionFactory.openSession()) { UserMapper mapper = session.getMapper(UserMapper.class); User user = mapper.selectById(1); // 每次都是独立会话 session.commit(); // 每次循环单独提交 } } ``` **特征**: - **连接标识不同**:每次查询使用新的数据库连接 ID。 - **无缓存共享**:每次查询都访问数据库(日志显示 10 次相同 SQL)。 - **事务独立提交**:每次循环提交后数据立即可见。 --- #### 3. **验证方法** 通过以下方式确认是否同一会话: 1. **日志分析**: - 相同会话:只有第一次查询输出 SQL 日志,后续命中缓存无日志。 - 不同会话:每次循环都输出 SQL 日志。 2. **连接 ID 检查**: ```sql /* MySQL */ SELECT CONNECTION_ID() AS session_id; ``` 在循环中打印此 ID,相同则表示同一会话[^2]。 3. **更新可见性测试**: ```java // 在循环中更新数据 mapper.updateName(1, "Loop_" + i); // 立即查询 User u = mapper.selectById(1); ``` - 相同会话:查询结果立即反映更新(未提交时仅当前会话可见)。 - 不同会话:其他会话需等待提交后才可见更新。 --- #### 4. **性能警告与优化** - **循环查询的性能问题**: 即使是同一会话,循环中重复查询也会消耗应用层资源。引用[^4]指出: > *"循环查询会导致不必要的性能损耗,应优先使用 `IN` 查询或批量操作替代"* 例如将循环查询改为: ```sql SELECT * FROM users WHERE id IN (1, 2, 3, ...); ``` - **缓存失效风险**: 若循环中包含更新操作(如 `INSERT/UPDATE`),会清空当前会话的一级缓存,导致后续查询重新访问数据库[^1]。 --- ### 总结 | 场景 | 是否同一 SQL 会话 | 判断依据 | |--------------------------|------------------|-----------------------------| | 循环使用同一 `SqlSession` 实例 | ✅ 是 | 相同连接 ID、共享一级缓存 | | 每次循环新建 `SqlSession` | ❌ 否 | 不同连接 ID、无缓存共享 | | Spring 的 `@Transactional` 方法内 | ✅ 是 | 自动统一会话管理[^1] | > 💡 **最佳实践**:避免在循环中执行相同查询,改用批量操作。若必须循环,确保复用同一 `SqlSession` 并监控连接 ID。 ---
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值