文章目录
-
- [1、@Thransctionl 使用方式:](#1、@Thransctionl 使用方式:)
- [2、@Thransctionl 使用场景:](#2、@Thransctionl 使用场景:)
1、@Thransctionl 使用方式:
-
当我们的方法上面加了@Thransctionl 注解的时候,但是在我们的方法里面直接抛出 throw new Exception("") 事,事务注解是不起作用的,控制不了事务的;
-
因为在@Thransctionl 注解中有一个 "rollbackFor"的属性:
javaClass <? extends Throwable>[]rollbackFor( ) default{}
而rollbackFor是针对什么样的异常回滚呢?
在官方的文档中指出只会对运行异常时异常(RuntimeException)和错误(Error)进行回滚;
当我们抛出Exception,受检异常是不能回滚的;
-
所以当我们在业务代码中要抛出异常时,不能抛出这一类的受检异常,而是运行异常,或者呢在@Thransctionl 注解中声明一下"rollbackFor" 什么样的异常要回
java@Transactional(rollbackFor = Exception.class)
-
或者我们抛出一个自定义异常,该自定义异常它又继承RuntimeException 运行时异常
javathrow 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异常。