Java事务失效

探讨Java事务在Excel批量导入过程中的失效问题,分析嵌套事务、异常处理与事务传播的复杂关系,提出有效的解决方案。

Java事务失效


问题复现,用伪代码复现问题!

事务配置文件

<tx:advice id="txAdvice" transaction-manager="transactionManager">   
    <tx:attributes>
        <!-- TODO 事务隔离级别可以尝试 NESTED -->      
        <tx:method name="save*" propagation="REQUIRED" rollback-for="Exception"/>      
        <tx:method name="create*" propagation="REQUIRED" rollback-for="Exception"/>
        <tx:method name="add*" propagation="REQUIRED" rollback-for="Exception"/>
        <tx:method name="update*" propagation="REQUIRED" rollback-for="Exception"/>
        <tx:method name="*" read-only="true" rollback-for="Exception"/>
      </tx:attributes>
 </tx:advice>

controller层代码

/** * excel批量导入信息 */
@RequestMapping(path="/pre/pretemplate/importExcel/{templateType}")
public Map<String, Object> importCrm(@PathVariable String templateType, MultipartFile[] file, HttpServletRequest req) throws Exception{   
    User user = getSessionUser(req);
    Map<String, Object> data = null;
		//0 老师 1 学生
		if("0".equals(templateType)) {
			data = service.updateExcel(file[0], req,templateType);
			if ((boolean) data.get("success")) {    
				service.writeSystemLog("批量导入老师数据成功");
			} else {
				service.writeSystemLog("批量导入老师数据失败");
			}
		}
    
    	if("1".equals(templateType)) {
			data = service.updateExcel(file[0], req,templateType);
			if ((boolean) data.get("success")) {    
				service.writeSystemLog("批量导入学生数据成功");
			} else {
				service.writeSystemLog("批量导入学生数据失败");
			}
		}
    return data;
}

service层代码

public Map<String, Object> updateExcel(MultipartFile file, HttpServletRequest req, String templateType) throws Exception {
    .........//权限与数据的处理
     data = importService.importLevelExcel(file, templateType);
    if (!(boolean) data.get("success")) {
			return data;
		}
		// 将数据插入数据库
		data.put("msg", "信息导入成功!");
		return data;
}

 public Map<String, Object> importLevelExcel(MultipartFile file, String templateType) throws Exception {
     HSSFWorkbook hwb;
        Map<String, Object> map = new LinkedHashMap<>();
        hwb = new HSSFWorkbook(file.getInputStream());
        HSSFSheet sheet = hwb.getSheetAt(0);
     try{
     		........
    		String target = getCellValue(row.getCell(2));
            String method = getCellValue(row.getCell(3));
         //检验是否包含特殊字符
         checkForString(method);
     }catch (Exception e) {
				map.put("success", false);
				map.put("msg", "数据类型异常,请检查!");
				return map;
			}
 }

业务需求:需要在用Excel模板导入信息时进行判断,Windows系统下新建文件夹不支持的符号不能导入!

因为导入模板信息较多,所以我写了一个公共方法,在导入时进行调用进行校验

public void checkForString(String str) throws Exception {    
    String regEx = "[\\/:*?\"<>|]";    
    Pattern p = Pattern.compile(regEx);    
    Matcher m = p.matcher(str);    
    bolean b = m.find();    
    if (b) {        
        throw new BusinessException("数据中不能包含:“\\\\/:*?\"<>|”中任何字符!");    
    }
}

本以为这样就结束了,没想到经过测试后发现,消息提示什么的都没问题,但是即便是在模板信息中含有特殊字符的情况下,还是可以成功保存几条数据,也就是说事务有问题,顺着思路去配置文件一看,只有service里面的第一级方法有事务,所以在配置文件中添加了导入方法的事务。

<tx:method name="importLevelExcel" propagation="REQUIRES_NEW" rollback-for="Exception"/>

本想着加完就可以愉快的结束,没想到问题还是没有解决,于是我上百度上遨游了一圈,发现当前加事务的方法中异常被try-catch捕获了,导致异常处理器不能捕捉到异常,事务出现问题。

但是方法中不仅要返回弹框提示信息,还有返回日志信息,保存用户操作日志,这该怎么办?

经过思考我决定将异常放在service最顶级方法处理,根据异常类进行信息回写,service代码改成

public Map<String, Object> updateExcel(MultipartFile file, HttpServletRequest req, String templateType) throws Exception {
    .........//权限与数据的处理
        try{
     data = importService.importLevelExcel(file, templateType);
    } catch (BusinessException bus) {
            data.put("success", false);
            data.put("msg", "导入模板中不能包含:“\\/:*?\"<>|”中任何字符!");
            return data;
        } catch (Exception e) {
            data.put("success", false);
            data.put("msg", "数据类型异常,请检查!");
            return data;
        }
    return data;
}

 public Map<String, Object> importLevelExcel(MultipartFile file, String templateType) throws Exception {
     HSSFWorkbook hwb;
        Map<String, Object> map = new LinkedHashMap<>();
        hwb = new HSSFWorkbook(file.getInputStream());
        HSSFSheet sheet = hwb.getSheetAt(0);
     
     		........
    		String target = getCellValue(row.getCell(2));
            String method = getCellValue(row.getCell(3));
         //检验是否包含特殊字符
         checkForString(method);
    
 }

到此为止,我觉得我已经大功告成,但是经过测试,还有问题,我就有点纳闷了!

经过查询才知道,原来同类中子方法的事务是无效的,什么嵌套还有传播通通无效,但是我想想自己的父级方法不是有事务吗?为什么还是失效,最后才明白,原来是我在父级方法中try-catch了,导致整体都没有事务了

解决办法有两种:

1.将子方法放在其他service下

2.调用子方法前,重新获取bean对象

于是我在service中重新获取bean对象,就OK了

总结:

1.spring事务管理中,如果方法参与了事务,就不能在方法中进行try-catch,否则事务控制器无法捕获事务,事务失效

2.同类方法中,如果父类参与了事务管理,那么即便子类重新添加了事务,哪怕是不同传播等级的事务,还是无效,spring默认集成父类的事务。如果想子类单独处理事务,有两种解决方案

A:将子方法放在其他service下

B:调用子方法前,重新获取bean对象

如若有误,欢迎指正!

### Java 事务失效条件 #### 异常被捕获而不抛出 当异常在方法内部被捕获而没有重新抛出时,Spring 的 AOP 将无法检测到该异常的发生。因此,即使配置了 `@Transactional` 注解,也不会触发回滚操作[^1]。 #### 使用异步处理方式 如果业务逻辑是在异步线程中执行,则默认情况下 Spring事务管理不会生效于这些子线程中的操作。这是因为每个新启动的线程会获得一个新的上下文环境,从而脱离原有主线程所建立的事物控制范围之外。 #### 方法被 final 或者 private 关键字修饰 对于被标记为 `final`, `private` 的成员函数来说,它们不能够通过代理模式来进行增强处理;同样地,在同一个类里面调用其他带有 `@Transactional` 标记的方法也会因为同样的原因失去作用。 #### 非 public 访问权限的方法上应用 @Transactional 只有公开访问级别的方法才能正常激活由 `@Transactional` 所定义的行为规则。这意味着受保护(`protected`)或包级私有(package-private)的方法上的此注解会被忽略掉。 #### 不恰当设置传播行为 (Propagation Behavior) 错误设定事务传播特性可能会导致预期外的结果。比如选择了 `NOT_SUPPORTED` 这样的选项意味着当前的操作不应该在一个活动事物内运行,这显然违背了一般性的需求[^5]。 #### 数据库引擎本身不支持事务功能 某些特定类型的数据库存储引擎可能不具备完整的ACID特性集,像 MySQL 中 MyISAM 表就不具备这一能力。所以在这样的环境下即便应用程序层面做了充分准备也无法真正意义上开启并利用起事务机制来保障一致性。 #### 缺少合适的事务管理组件集成 为了使 Spring 能够有效地管理和协调整个生命周期内的各项资源分配情况以及状态转换过程,必须确保已经正确引入相应的依赖项并与目标框架良好对接。例如 Hibernate/JPA 场景下就需要额外配置对应的 SessionFactory 实例等要素[^3]。 ```xml <bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean"> <!-- ... --> </bean> ``` #### 错误匹配的异常类型影响自动回滚策略 默认情形之下,任何未经检查(unchecked)的 RuntimeException 类型都将促使系统尝试撤销之前所做的更改动作以恢复初始状况。然而若是开发者自行指定了仅针对某几种类别的 Exception 发生才予以响应的话,那么其余不在列表里的问题就不会引起类似的连锁反应,进而造成部分失败却未能及时修正的局面出现。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值