Spring事务Thransctionl总结:

文章目录

    • [1、@Thransctionl 使用方式:](#1、@Thransctionl 使用方式:)
    • [2、@Thransctionl 使用场景:](#2、@Thransctionl 使用场景:)

1、@Thransctionl 使用方式:

  • 当我们的方法上面加了@Thransctionl 注解的时候,但是在我们的方法里面直接抛出 throw new Exception("") 事,事务注解是不起作用的,控制不了事务的;

  • 因为在@Thransctionl 注解中有一个 "rollbackFor"的属性:

    java 复制代码
    Class <? extends Throwable>[]rollbackFor( ) default{}

    而rollbackFor是针对什么样的异常回滚呢?

    在官方的文档中指出只会对运行异常时异常(RuntimeException)和错误(Error)进行回滚;

    当我们抛出Exception,受检异常是不能回滚的;

  • 所以当我们在业务代码中要抛出异常时,不能抛出这一类的受检异常,而是运行异常,或者呢在@Thransctionl 注解中声明一下"rollbackFor" 什么样的异常要回

    java 复制代码
    @Transactional(rollbackFor = Exception.class)
  • 或者我们抛出一个自定义异常,该自定义异常它又继承RuntimeException 运行时异常

    java 复制代码
    throw new CustomException("数据有误,需要回滚!")

    这样就不用在方法上面声明这个异常需要回滚了 ;

2、@Thransctionl 使用场景:

  • @Transactional注解 可以作用于接口、接口方法、类以及类方法上。当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。

  • 虽然@Transactional 注解可以作用于接口、接口方法、类以及类方法上,但是 Spring 建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效。另外, @Transactional注解应该只被应用到 public 方法上,这是由Spring AOP的本质决定的。如果你在 protected、private 或者默认可见性的方法上使用 @Transactional 注解,这将被忽略,也不会抛出任何异常。

  • 需要注意的是,在同一个类中调用被 @Transactional 注解修饰的方法时,事务处理可能会失效,因为 Spring 会在运行时为该类创建一个代理对象,而在同一个类中调用方法时,是不会经过代理对象的。如果需要在同一个类中调用被 @Transactional 注解修饰的方法,可以将该方法抽取到另一个类中,并通过依赖注入的方式调用。也可以先获取到本类的代理对象再调用内部方法。

1.2 函数之间相互调用

关于有@Transactional的函数之间调用,会产生什么情况。这里咱们通过几个例子来说明。

1.2.1 同一个类中函数相互调用

同一个类AClass中,有两个函数aFunction、aInnerFunction。aFunction调用aInnerFunction。而且aFunction函数会被外部调用。

情况1:

aFunction添加了@Transactional注解,aInnerFunction函数没有添加。aInnerFunction抛异常。

java 复制代码
public class AClass {
 
    @Transactional(rollbackFor = Exception.class)
    public void aFunction() {
        //todo: 数据库操作A(增,删,该)
        aInnerFunction(); // 调用内部没有添加@Transactional注解的函数
    }
 
    private void aInnerFunction() {
        //todo: 操作数据B(做了增,删,改 操作)
        throw new RuntimeException("函数执行有异常!");
    }
 
}
结果:两个函数操作的数据都会回滚。
情况2:

两个函数都添加了@Transactional注解。aInnerFunction抛异常。

java 复制代码
public class AClass {
 
    @Transactional(rollbackFor = Exception.class)
    public void aFunction() {
        //todo: 数据库操作A(增,删,该)
        aInnerFunction(); // 调用内部没有添加@Transactional注解的函数
    }
 
    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
    private void aInnerFunction() {
        //todo: 操作数据B(做了增,删,改 操作)
        throw new RuntimeException("函数执行有异常!");
    }
 
}
结果:同第一种情况一样,两个函数对数据库操作都会回滚。因为同一个类中函数相互调用的时候,内部函数添加@Transactional注解无效。@Transactional注解只有外部调用才有效。
情况3:

aFunction不添加注解,aInnerFunction添加注解。aInnerFunction抛异常。

java 复制代码
public class AClass {
 
    public void aFunction() {
        //todo: 数据库操作A(增,删,该)
        aInnerFunction(); // 调用内部没有添加@Transactional注解的函数
    }
 
    @Transactional(rollbackFor = Exception.class)
    protected void aInnerFunction() {
        //todo: 操作数据B(做了增,删,改 操作)
        throw new RuntimeException("函数执行有异常!");
    }
 
}
java 复制代码
结果:两个函数对数据库的操作都不会回滚。因为内部函数@Transactional注解添加和没添加一样。
情况4:
aFunction添加了@Transactional注解,aInnerFunction函数没有添加。aInnerFunction抛异常,不过在aFunction里面把异常抓出来了。
java 复制代码
public class AClass {
 
    @Transactional(rollbackFor = Exception.class)
    public void aFunction() {
        //todo: 数据库操作A(增,删,该)
        try {
            aInnerFunction(); // 调用内部没有添加@Transactional注解的函数
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
 
    private void aInnerFunction() {
        //todo: 操作数据B(做了增,删,改 操作)
        throw new RuntimeException("函数执行有异常!");
    }
 
}
java 复制代码
结果:两个函数里面的数据库操作都成功。事务回滚的动作发生在当有@Transactional注解函数有对应异常抛出时才会回滚。(当然了要看你添加的@Transactional注解有没有效)。
1.2.1. 不同类中函数相互调用

两个类AClass、BClass。AClass类有aFunction、BClass类有bFunction。AClass类aFunction调用BClass类bFunction。最终会在外部调用AClass类的aFunction。

情况1:

aFunction添加注解,bFunction不添加注解。bFunction抛异常。

java 复制代码
@Service()
public class AClass {
 
    private BClass bClass;
 
    @Autowired
    public void setbClass(BClass bClass) {
        this.bClass = bClass;
    }
 
    @Transactional(rollbackFor = Exception.class)
    public void aFunction() {
        //todo: 数据库操作A(增,删,该)
        bClass.bFunction();
    }
 
}

@Service()
public class BClass {
 
    public void bFunction() {
        //todo: 数据库操作A(增,删,该)
        throw new RuntimeException("函数执行有异常!");
    }
}

结果:两个函数对数据库的操作都回滚了。

情况2:

aFunction、bFunction两个函数都添加注解,bFunction抛异常。

java 复制代码
@Service()
public class AClass {
 
    private BClass bClass;
 
    @Autowired
    public void setbClass(BClass bClass) {
        this.bClass = bClass;
    }
 
    @Transactional(rollbackFor = Exception.class)
    public void aFunction() {
        //todo: 数据库操作A(增,删,该)
        bClass.bFunction();
    }
 
}
 
@Service()
public class BClass {
 
    @Transactional(rollbackFor = Exception.class)
    public void bFunction() {
        //todo: 数据库操作A(增,删,该)
        throw new RuntimeException("函数执行有异常!");
    }
}

结果:两个函数对数据库的操作都回滚了。两个函数里面用的还是同一个事务。这种情况下,你可以认为事务rollback了两次。两个函数都有异常。

情况3:

aFunction、bFunction两个函数都添加注解,bFunction抛异常。aFunction抓出异常。

java 复制代码
@Service()
public class AClass {
 
    private BClass bClass;
 
    @Autowired
    public void setbClass(BClass bClass) {
        this.bClass = bClass;
    }
 
    @Transactional(rollbackFor = Exception.class)
    public void aFunction() {
        //todo: 数据库操作A(增,删,该)
        try {
            bClass.bFunction();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
 
}
 
@Service()
public class BClass {
 
    @Transactional(rollbackFor = Exception.class)
    public void bFunction() {
        //todo: 数据库操作A(增,删,该)
        throw new RuntimeException("函数执行有异常!");
    }
}

结果:两个函数数据库操作都没成功。而且还抛异常了。org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only。看打印出来的解释也很好理解把。咱们也可以这么理解,两个函数用的是同一个事务。bFunction函数抛了异常,调了事务的rollback函数。事务被标记了只能rollback了。程序继续执行,aFunction函数里面把异常给抓出来了,这个时候aFunction函数没有抛出异常,既然你没有异常那事务就需要提交,会调事务的commit函数。而之前已经标记了事务只能rollback-only(以为是同一个事务)。直接就抛异常了,不让调了。

情况4:

aFunction、bFunction两个函数都添加注解,bFunction抛异常。aFunction抓出异常。这里要注意bFunction函数@Transactional注解我们是有变化的,加了一个参数propagation = Propagation.REQUIRES_NEW,控制事务的传播行为。表明是一个新的事务。其实咱们情况3就是来解决情况2的问题的。

java 复制代码
@Service()
public class AClass {
 
    private BClass bClass;
 
    @Autowired
    public void setbClass(BClass bClass) {
        this.bClass = bClass;
    }
 
    @Transactional(rollbackFor = Exception.class)
    public void aFunction() {
        //todo: 数据库操作A(增,删,该)
        try {
            bClass.bFunction();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
 
}
 
@Service()
public class BClass {
 
    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
    public void bFunction() {
        //todo: 数据库操作A(增,删,该)
        throw new RuntimeException("函数执行有异常!");
    }
}

结果:bFunction函数里面的操作回滚了,aFunction里面的操作成功了。有了前面情况2的理解。这种情况也很好解释。两个函数不是同一个事务了。

1.3.4 同类内方法调用如何使事务生效

上面示例中,在同一个类中调用被 @Transactional 注解修饰的方法时,事务处理可能会失效,因为 Spring 会在运行时为该类创建一个代理对象,而在同一个类中调用方法时,是不会经过代理对象的。如果需要在同一个类中调用被 @Transactional 注解修饰的方法,可以将该方法抽取到另一个类中,并通过依赖注入的方式调用。也可以先获取到本类的代理对象再调用内部方法。

方法1:

使用 AopContext 类获取代理对象

需要注意的是,使用 AopContext 类获取代理对象可能会带来安全风险,因此默认情况下是禁用的。如果需要启用该功能,可以在配置文件中设置 expose-proxy 属性为 true。例如,在 Spring Boot 中可以通过以下方式设置:spring.aop.proxy-target-class: true

java 复制代码
@Service
public class UserService {

   @Autowired
   private UserDao userDao;

   @Transactional(propagation = Propagation.REQUIRED)
   public void updateUser(User user) {
       // 获取当前对象的代理对象
       UserService userService = (UserService) AopContext.currentProxy();

       // 调用内部方法,启用事务处理
       userService.updateUserDetail(user.getUserId(), user.getUserDetail());
   }

   @Transactional(propagation = Propagation.REQUIRES_NEW)
   public void updateUserDetail(Long userId, UserDetail userDetail) {
       userDao.updateUserDetail(userId, userDetail);
   }
}
方法2:

使用 beanFactory.getBean 类获取代理对象

java 复制代码
@Service
public class UserService {

   @Autowired
   private BeanFactory beanFactory;

   @Autowired
   private UserDao userDao;

   @Transactional(propagation = Propagation.REQUIRED)
   public void updateUser(User user) {
       // 获取当前对象的代理对象
       UserService userService = beanFactory.getBean(UserService.class);

       // 调用内部方法,启用事务处理
       userService.updateUserDetail(user.getUserId(), user.getUserDetail());
   }

   @Transactional(propagation = Propagation.REQUIRES_NEW)
   public void updateUserDetail(Long userId, UserDetail userDetail) {
       userDao.updateUserDetail(userId, userDetail);
   }
}

在上面的代码中,updateUser() 方法通过 beanFactory.getBean(UserService.class) 方法获取当前对象的代理对象,然后调用内部方法 updateUserDetail(),从而启用事务处理。需要注意的是,在使用 getBean() 方法获取当前对象的代理对象时,要求该类必须是一个 Spring 管理的 Bean,否则会抛出异常。

总结:

要知道@Transactional注解里面每个属性的含义。@Transactional注解属性就是来控制事务属性的。通过这些属性来生成事务。

要明确我们添加的@Transactional注解会不会起作用。@Transactional注解在外部调用的函数上才有效果,内部调用的函数添加无效,要切记。这是由AOP的特性决定的。

要明确事务的作用范围,有@Transactional的函数调用有@Transactional的函数的时候,进入第二个函数的时候是新的事务,还是沿用之前的事务。稍不注意就会抛UnexpectedRollbackException异常。

相关推荐
魔道不误砍柴功12 分钟前
Java 中如何巧妙应用 Function 让方法复用性更强
java·开发语言·python
NiNg_1_23412 分钟前
SpringBoot整合SpringSecurity实现密码加密解密、登录认证退出功能
java·spring boot·后端
闲晨15 分钟前
C++ 继承:代码传承的魔法棒,开启奇幻编程之旅
java·c语言·开发语言·c++·经验分享
Chrikk2 小时前
Go-性能调优实战案例
开发语言·后端·golang
幼儿园老大*2 小时前
Go的环境搭建以及GoLand安装教程
开发语言·经验分享·后端·golang·go
canyuemanyue2 小时前
go语言连续监控事件并回调处理
开发语言·后端·golang
杜杜的man2 小时前
【go从零单排】go语言中的指针
开发语言·后端·golang
测开小菜鸟2 小时前
使用python向钉钉群聊发送消息
java·python·钉钉
P.H. Infinity3 小时前
【RabbitMQ】04-发送者可靠性
java·rabbitmq·java-rabbitmq
生命几十年3万天3 小时前
java的threadlocal为何内存泄漏
java