Spring事务的实现方式和原理
在使用 Spring 框架的时候,事务的实现方式有两种,一种是编程式事务,程序员自己通过代码来控制事务的处理逻辑,还有一种是声明式事务,可以通过 @Transactional 注解来实现。
其实事务的操作本来应该是由数据库来进行控制的,但是为了方便程序员进行业务逻辑的操作,Spring 对事务功能进行了扩展实现,一般我们很少会用到编程式事务,更多的是通过添加 @Transactional 注解来进行实现,由 Spring 框架来帮助进行控制。
在一个方法上加了 @Transactional 注解后,Spring 会基于这个类生成一个代理对象,会将这个代理对象作为 bean,当使用这个代理对象的方法时,那么代理逻辑会先把事务的自动提交设置为 false,然后再去执行具体的业务逻辑,如果执行逻辑没有出现异常,那么代理逻辑就会将事务进行提交,如果执行逻辑出现了异常,那么则会将事务进行回滚。默认情况下会对 RuntimeException
和 Error
进行回滚。可以利用 @Transactional 注解中的 rollbackFor
属性进行配置异常信息。
Spring事务失效的场景
1) 非 public 修饰的方法
When using proxies, you should apply the @Transactional annotation only to methods with public visibility. If you do annotate protected, private or package-visible methods with the @Transactional annotation, no error is raised, but the annotated method does not exhibit the configured transactional settings. Consider the use of AspectJ (see below) if you need to annotate non-public methods.
以上段落来自 Spring 官方文档,大概的意思就是 @Transactional 只能用于 public 的方法上,否则事务不会生效,如果要用在非 public 方法上,可以开启 AspectJ
代理模式。
2) 自身调用
@Service
public class OrderServiceImpl implements OrderService {
public void update(Order order) {
updateOrder(order);
}
@Transactional
public void updateOrder(Order order) {
// update order
}
}
事务的管理是通过代理执行的方式生效的,如果是方法内部调用,没有经过 Spring 的代理类,就调用不到了
3)被 final、static 关键字修饰的类或方法
cglib 动态代理是通过生成目标类子类的方式生成代理类的,如果被final、static修饰后,无法继承父类与父类的方法。
4)没有被 Spring 管理
// @Service
public class OrderServiceImpl implements OrderService {
@Transactional
public void updateOrder(Order order) {
// update order
}
}
如果此时把 @Service 注解注释掉,这个类就不会被加载成一个 Bean,那这个类就不会被 Spring 管理了,事务自然就失效了。
5)数据库不支持事务
比如 Mysql 的 Myisam
存储引擎是不支持事务的,只有 innodb
存储引擎才支持。
6)异常被捕获
@Service
public class OrderServiceImpl implements OrderService {
@Transactional
public void updateOrder(Order order) {
try {
// update order
} catch {
}
}
}
当异常被捕获后,而且没有再抛出,那么事务是不会回滚的。
7)异常类型错误
@Service
public class OrderServiceImpl implements OrderService
@Transactional
public void updateOrder(Order order) {
try {
// update order
} catch {
throw new Exception("更新错误");
}
}
}
这样事务也是不生效的,因为默认回滚的是:RuntimeException
和 Error
,如果想触发其他异常的回滚,需要在注解上配置一下,如通过指定@Transactional(rollbackFor = Exception.class)
的方式进行全异常捕获。
8)多线程调用
Spring 的事务是通过数据库连接来实现,而数据库连接是放在 ThreadLocal
里面。同一个事务,只能用同一个数据库连接。而多线程场景下,拿到的数据库连接是不一样的。
9)错误的传播行为
使用的传播特性不支持事务
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public boolean save(User user) {
boolean isSuccess = userService.save(user);
try {
int i = 1 / 0;
} catch (Exception e) {
throw new RuntimeException();
}
return isSuccess;
}