@Transactional注解与synchronized使用带来的一些问题
这几天在练习写一个小工程,遇到了一些问题,代码大致如下:
@RequestMapping(value = "/downCodeFile",method = RequestMethod.POST)
@Transactional
public String downCode(HttpServletRequest request, HttpServletResponse response, RedirectAttributes model){
synchronized(this){
// 获取 是否已经使用
used = getUsed(xsdCode);
switch (used) {
case "已使用":
throw new OrderPeriodException(OrderExceptionEnum.USED);
case "不在记录区":
throw new OrderPeriodException(OrderExceptionEnum.NOmySQL);
}
//设置为已使用
setUsed(xsdCode, name, subject);
}
codeServer.getCodeFile(name,xsdCode,subject,response);
}
这段代码的 大致 逻辑就是 下载一个东西,然后判断下载码是否可用,不可用抛出异常,可用的话就设提交数据库为已使用。
然后在我测试的过程中,产生了让我想不通的问题。就是同一个下载码同一时间提交两次,竟然会出现会被下载两次的情况,但是如果两次提交间隔1s左右,就不会发生这种问题。刚开始觉得是 mybatis的一级缓存的问题,但是后来发现不是,因为两次提交都打印了sql语句,如果是用到了mybatis的缓存,根本不会输出sql语句。况且我也没开mybatis的二级缓存,一级缓存的话作用域也就是同一个SqlSession才有效,mybatis在springmvc是线程安全的,所以这种问题明显不是一级缓存的原因!!看了一个博主的博客,他写的是 因为一级缓存的原因,但是我实在不能理解这种解释。
但这又是什么问题呢?里面的sql操作我也加锁了啊?为什么还会出现这种“双花问题”?为什么会被下载两次??为什么?锁不管用了吗???
后来在搜索学习的过程中,发现了是@Transactional
的问题。
特别重要!!!
在第一次提交的过程中,还没有提交事务的时候,这个时候第二次提交进入了!,虽然已经执行了
//设置为已使用
setUsed(xsdCode, name, subject);
这段代码,但是这个时候因为第一次提交的那个过程的事务还没有提交,所以第二次查询数据库还是
没有使用!所以造成了双花问题,所以 解决办法就是加锁的范围包括到整个事务,即就是把synchronized 加到 方法上,不过这样会使得程序的性能降低,没办法,安全性和效率 ,我还是选择安全。
@Transactional注解的使用
- 默认捕捉unchecked exception异常,不会捕捉 checked exception。那么什么是 unchecked exception ,什么是checked exception?
- 两个的区别就是 一个 是 检查类型异常,一个是非检查类型异常。
- 检查型异常 简单来说就是 编译器给你提示 要你
try catch
或者throws
的语句,而非检查型的异常就是 NullPointerException、IOException 这种类型的异常。 - 第二点就是
- checked exception继承于Exception类
unchecked exception 继承于RuntimeException类
- checked exception继承于Exception类
所以 要想@Transactional
注解生效的话,就要保证 你的自定义异常是继承于RuntimeException
,还要保证该方法时public。还有一种方法就是 以下这种,指定捕获异常。
@Transactional(rollbackFor={OrderPeriodException.class,IOException.class})
还需要注意的地方:
-
在使用
@Transactional
的时候,抛出的异常要想事务管理,一定不要 try catch,因为这样@Transactional
就会失效,spring认为你这里自己处理了要抛出的异常。-
当然你可以 手动让事务在catch语句中回滚
第一步:spring boot入口函数添加@EnableTransactionManagement
注解-
第二步:
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();//关键 return false;
-
-
@Transactional事务失效的常见场景:
public Class A{
EmployeeRepository employeeRepository
@RequestMapping("/test")
public void addEnployee2Controller(emp){
this.addEmployee(emp);
}
@Transactional
public void addEmployee(emp){
employeeRepository.save(emp)
// 模拟异常
int i = 1 / 0;
}
}
运行上述代码,并不会回滚,事务起不到作用。为什么呢?因为 我们开启事务后,spring会为我们动态生成一个代理对象,调用方法的时候,调用的就是 原始对象的事务方法,而不是代理对象的事务方法 ,所以事务并不会起到效果。这时候,可以把事务操作封装到另外一个类里面,在当前类定义一个事务类的属性。调用该属性的方法即可。
(图片来源自:点击查看原版)
由于以前没有特别学习过 异常的使用,用了才知道 RuntimeException类型的异常抛出以后不需要try...catch
或throws
也可以完美运行。而Exception 就必须try...catch
或throws