手动提交事务和在for循环里进行事务控制及声明式事务的使用实例!

本文介绍了在处理批量数据时如何进行事务控制的两种方法。在for循环中,针对每条数据执行查询、比较和更新操作,需要确保事务的正确提交和回滚。方法一是在Controller层获取数据并调用Service层进行事务控制;方法二是在Service层手动处理事务的提交和回滚。关键点在于for循环中事务的开启位置,以防止多次提交事务的错误。

事务场景:需要做一个跑批进行一个流程的实现,0.获取异常数据(多条),for循环对单条数据进行下面三个操作:1).调用别的系统进行查询,2).查询自己的库中数据,3)更改自己数据库中的数据状态。此时就需要对单条数据操作的3步骤进行事务控制,使用的过程中发现两个解决办法:第一:在controller层进数据的获取,for循环处理单条数据时调用service层进行事务的控制.第二:在service层进行事务的手动提交和回滚。(for循环需要注意:事务的开启需要在for循环中,否则会报多次提交事务:do not commit or rollback more than once per transaction)

下面贴代码:第一种:在controller层进数据的获取,for循环处理单条数据时调用service层进行事务的控制、

@Controller
public class TestController {
		@Resource
		private IClearNoUnfreezeBs iClearNoUnfreezeBs;
		//逻辑放到controller中,service进行事务控制start
		@RequestMapping("/freeze")
		public void testfreeze() throws Exception{
			try {
				//1.取数据
				ist<String> policyList1 = iClearNoUnfreezeBs.getunfreezedate();
				//2.for循环处理每条数据
				for (String policyno : policyList1) {
					n++;
					try {
						//需要进行事务控制的放到service层进行处理
						iClearNoUnfreezeBs.bs_clearNoUnfreeze( policyno,n );
					} catch (Exception e) {
						continue;
					}
				} 
			} catch (Exception e) {
				e.printStackTrace();
			}
		}		
}

第二:在service层进行事务的手动提交和回滚

@Service
public class ClearNoUnfreezeBs implements IClearNoUnfreezeBs {
	private static Logger log = Logger.getLogger(ClearNoUnfreezeBs.class);
	@Resource
	private IClearNoUnfreezeDao iClearNoUnfreezeDao;
	@Resource 
	private IWechatDao wechatDao;
	  
		String rownum = ConfigureUtil.getInstance().getString("rownum");// 访问地址
		//获取上月的当前时间
		Timestamp lastMonth = GetTimeUtil.getLastMonthTime();
		// 1).取出上月末之前承保且尚未做解冻处理的异常保单   
		List<String> policyList1 = iClearNoUnfreezeDao.findNoUnfreezePolicyno(lastMonth,rownum);
		// 2)将list1中的openid遍历查询是否有解冻过,有解冻的list2
		//list2存已解冻的
		List<String> policyList2 = new ArrayList<>();
		for (String policyno : policyList1) {
			//有解冻的返回true
			Boolean isfreeze =  iClearNoUnfreezeDao.findUnfreezePolicyno(policyno);
			if(isfreeze == true){
				policyList2.add(policyno);
			}
		}
		//还有异常多条的保单号也去掉 ,不然取保单金额多条时没办法取
		List<String> policyList3  =iClearNoUnfreezeDao.findMulti(lastMonth);
		//  3)异常的list1-list2-list3
		policyList1.removeAll(policyList2);
		policyList1.removeAll(policyList3);
		 
		for (String policyno : policyList1) {
			ApplicationContext ctx = SpringContextHolder.getApplicationContext();  
	        //获取事务  
			HibernateTransactionManager transactionManager =   
	                (HibernateTransactionManager) ctx.getBean("transactionManager");  
	        DefaultTransactionDefinition def = new DefaultTransactionDefinition();  
	        def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); // 事物隔离级别,开启新事务,这样会比较安全些。  
	        TransactionStatus status = transactionManager.getTransaction(def); // 获得事务状态 
			try {
				n++;
				if(n==4){
					int s = 1/0;
				}
				String chdrcoy =policyno.substring(0, 1);
				String chdrnum =policyno.substring(1);
				//1.调用个险csc
    		    String respinfo = getcscresult(chdrcoy,chdrnum);
				//调用解析xml map中{acctmonth=, acctyear=, rootSuccess=true, validflag=1, zappSuccess=true}
				Map<String,String>  respmap = XmlAnaly.domjxml(respinfo);
				//调用者可先判断 response 和 zappsettlement 节点的 Success 属性均为"true",再进行后续解析。
				if(respmap.get("rootSuccess").equals("true") && respmap.get("zappSuccess").equals("true")){
					System.out.println(respmap.get("validflag"));
					if(respmap.get("validflag").equals("2")){//.根据保单佣金结算状态,若佣金已全部发放完毕,则做一次出账处理,清空冻结账户中的余额
						String acctmonth =respmap.get("acctmonth");
						String acctyear =respmap.get("acctyear");
						//如validflag = 2,需要1)wxacctmove中加一条出账流水,参考:bs_conumptionLess
						//2)wxacctbaln表中balamt字段减去wxacctbalndetail中的数据中的balamt金额
						String openid = freezeLess(policyno);
						//3)将wxacctbalndetail中的数据字段accountstatus改为Y和时间
						iClearNoUnfreezeDao.updateaccountstatus(policyno);  //更新时都更新了????
						//4)推送给代理人消息,告知佣金已全额发放
					StringBuffer str = new StringBuffer();	
				    str.append("您好,保单(保单号:"+policyno+" 线下佣金中发放,故此保单冻结金额清零。");
					String result = getMsessage(openid,str.toString());
					}
				}
				 transactionManager.commit(status);  
			} catch (Exception e) {
				transactionManager.rollback(status);
			}
		}
	}	 
		
这种写法注意:
ApplicationContext ctx = SpringContextHolder.getApplicationContext();  
	        //获取事务  
			HibernateTransactionManager transactionManager =   
	                (HibernateTransactionManager) ctx.getBean("transactionManager");  
	        DefaultTransactionDefinition def = new DefaultTransactionDefinition();  
	        def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); // 事物隔离级别,开启新事务,这样会比较安全些。  
	        TransactionStatus status = transactionManager.getTransaction(def); // 获得事务状态 
这段获取事务一定要放到for循环中,否则再处理第二条数据就会报:do not commit or rollback more than once per transaction,因为放在for循环外面,此时还是一个事务,这点需要注意!!!!!!!

还需要有一下配置:1.applicationContext.xml中配置

      <bean id="springContextHolder" class="com.taikang.util.SpringContextHolder"></bean>  
需要写个类:
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

public class SpringContextHolder implements ApplicationContextAware {

	private static ApplicationContext applicationContext;  
    
    @Override  
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {  
        SpringContextHolder.applicationContext = applicationContext;  
    }  
   
    public static ApplicationContext getApplicationContext() {  
        return applicationContext;  
    }  
    public static Object getBean(String beanName) {  
        return applicationContext.getBean(beanName);  
    }  
   
    public static <T>T getBean(String beanName , Class<T>clazz) {  
        return applicationContext.getBean(beanName , clazz);  
    }  
  

}

对于单个数据的事务和这个处理差不多,重要的是for循环中事务的处理会有一些坑!!!!!!!


<think>我们正在讨论Spring中未使用@Transactional注解时的事务行为,特别是关于SqlSession的复用问题。根据用户提供的引用问题,我们需要明确在默认自动提交模式下,SqlSession是如何被管理的。 关键点: 1. **引用[1]**: 指出如果Mapper调用后不关闭SqlSession,会导致数据库连接占用,后续请求可能超时失败。这意味着SqlSession需要被正确关闭以避免资源泄漏。 2. **引用[2]**: 说明SqlSessionTemplate有两种情况: - 当由事务同步管理器返回时,关闭由Spring管理。 - 如果是从工厂直接获取,则需要手动调用close()。 3. **引用[3]**: 提到SqlSessionFactory的作用是创建SqlSessionMapper对象。 4. **引用[4]**: 展示了在Spring整合MyBatis时,我们通常注入的是SqlSessionTemplate,并且它是线程安全的。 5. **引用[5]**: 显示了一个调试日志,表明非事务性的SqlSession被关闭(Closing non transactional SqlSession)。 问题核心:在Spring默认自动提交(无事务)的情况下,每个Mapper操作使用的SqlSession是否会被复用? 分析: 在Spring与MyBatis整合时,我们通常使用`SqlSessionTemplate`代替MyBatis原生的`SqlSession`。`SqlSessionTemplate`是线程安全的,它管理了SqlSession的生命周期。根据引用[2]引用[5]: - 当没有事务时(即自动提交模式),每个Mapper方法调用会创建一个新的SqlSession(实际上可能是从连接池中获取一个连接,然后创建一个SqlSession绑定到这个连接),并在方法调用结束后关闭这个SqlSession(释放连接回连接池)。这就是引用[5]中提到的“Closing non transactional SqlSession”。 - 当有事务时(使用@Transactional),Spring会将同一个事务范围内的多个操作绑定到同一个SqlSession(即同一个数据库连接),并在事务结束时(提交或回滚)关闭SqlSession。 因此,在默认自动提交(无事务)的情况下: - 每个Mapper方法调用(即每个数据库操作)都会使用一个独立的SqlSession(对应一个独立的数据库连接)。 - 操作成后,这个SqlSession会被立即关闭(即连接被释放回连接池)。 所以,SqlSession不会被多个Mapper操作复用(在没有事务的情况下)。每个操作都是独立的,使用自己的SqlSession,执行后立即关闭。 但是,这有一个重要的点:连接池的复用。虽然每次操作都会获取关闭SqlSession(以及底层的数据库连接),但连接池会管理物理连接。当我们从连接池获取连接,使用后关闭(实际上是归还给连接池),下次获取时可能拿到同一个物理连接(但逻辑上是一个新的SqlSession)。所以,物理连接会被复用,但每次操作使用的SqlSession对象是不同的。 总结回答: 在Spring默认自动提交(无事务)模式下: 1. 每个Mapper方法调用都会创建一个新的SqlSession(对应一个新的数据库连接,实际可能是从连接池中取出的)。 2. 操作成后,该SqlSession会被立即关闭(即连接归还给连接池)。 3. 因此,同一个Mapper的多次调用不会复用同一个SqlSession实例,每次都是独立的。 4. 但是,底层的数据库连接(物理连接)会被连接池复用,这是连接池的功能。 注意:这要区分SqlSession对象底层的数据库连接。连接池复用的是物理连接,而SqlSession对象每次都是新的(在无事务情况下)。 引用说明: - 引用[2]引用[5]直接说明了非事务性SqlSession的关闭行为。 - 引用[1]提醒我们不关闭SqlSession会导致连接泄漏,但Spring通过SqlSessionTemplate已经帮我们处理了关闭。 所以,回答用户的问题:在默认自动提交情况下,SqlSession不会被多个Mapper复用(每次操作都是独立的SqlSession),但底层的数据库连接会被连接池复用。</think>### Spring默认自动提交模式下SqlSession的复用机制分析 在Spring与MyBatis整合的场景中,**默认自动提交模式下SqlSession不会被多个Mapper复用**,每个Mapper操作都会使用独立的SqlSession。以下是具体分析: #### 1. **SqlSession的生命周期管理** | 场景 | SqlSession创建时机 | 关闭时机 | 复用性 | |---------------------|--------------------------------|----------------------------|-------------| | **无事务自动提交** | 每次Mapper方法调用时创建新实例 | 方法执行后立即关闭[^1][^5] | 不复用 | | **声明式事务** | 事务开始时创建 | 事务结束时关闭 | 事务内复用 | ```mermaid graph TD A[Mapper方法调用] --> B{是否有事务?} B -- 无 --> C[创建新SqlSession] C --> D[执行SQL] D --> E[自动提交] E --> F[立即关闭SqlSession][^5] B -- 有 --> G[复用现有SqlSession] ``` #### 2. **关键机制解析** - **自动关闭机制**: - 非事务环境下,Spring会为每个Mapper操作创建独立的SqlSession - 操作成后立即关闭并释放连接(日志示例:`Closing non transactional SqlSession`[^5]) - 避免连接泄漏:若不关闭会导致连接池耗尽[^1] - **SqlSessionTemplate实现**: ```java public class SqlSessionTemplate implements SqlSession { public <T> T selectOne(String statement) { SqlSession sqlSession = getSqlSession(); try { return sqlSession.selectOne(statement); } finally { // 无事务时立即关闭 if (!isSqlSessionTransactional(sqlSession)) { closeSqlSession(sqlSession); // [^2][^5] } } } } ``` #### 3. **性能影响与最佳实践** - **连接池行为**: - 物理连接会被连接池复用,但逻辑SqlSession始终新建 - 高频调用时建议: ```java // 避免在循环中频繁调用Mapper @Service public class BatchService { public void batchUpdate(List<Entity> list) { for(Entity item : list) { // 每次循环创建新SqlSession(低效) mapper.update(item); } // 改用批量操作 mapper.batchUpdate(list); // 单次SqlSession } } ``` - **配置优化**: ```xml <!-- 在mybatis-config.xml中启用批处理 --> <settings> <setting name="defaultExecutorType" value="BATCH"/> </settings> ``` #### 4. **线程安全保证** - **SqlSessionTemplate特性**: - 通过ThreadLocal实现线程隔离[^4] - 每个线程拥有独立的SqlSession实例 - 自动提交模式下无并发冲突风险 > **结论**:Spring默认自动提交模式中,每个Mapper操作使用独立的SqlSession实例执行后立即关闭,不存在跨Mapper复用情况[^1][^2][^5]。这种设计确保了操作隔离性,但需注意高频调用时的性能开销。 --- ### 相关问题 1. MyBatis中SqlSessionFactorySqlSessionTemplate的主要区别是什么? 2. 如何优化无事务场景下的批量数据库操作性能? 3. Spring事务管理如何影响MyBatis的SqlSession生命周期? 4. SqlSession线程隔离机制在分布式环境下是否仍然有效? 5. 连接池配置参数如何影响SqlSession的创建关闭行为?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值