@Transactional 注解与synchronized的使用的一些实战问题

@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才有效,mybatisspringmvc是线程安全的,所以这种问题明显不是一级缓存的原因!!看了一个博主的博客,他写的是 因为一级缓存的原因,但是我实在不能理解这种解释。

但这又是什么问题呢?里面的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

所以 要想@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...catchthrows也可以完美运行。而Exception 就必须try...catchthrows

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值