前言:首先,事务这个概念是数据库层面的,数据库事务和spring事务本质上其实都是同一个概念,spring的事务是对数据库的事务的封装,最后本质的实现还是在数据库,假如数据库不支持事务的话,spring的事务是没有作用的。数据库的事务说简单就只有开启,回滚和关闭,spring对数据库事务的包装,原理就是拿一个数据连接,根据spring的事务配置,操作这个数据连接对数据库进行事务开启,回滚或关闭操作。但是spring除了实现这些,还配合spring的传播行为对事务进行了更广泛的管理。其实这里还有个重要的点,那就是事务中涉及的隔离级别,以及spring如何对数据库的隔离级别进行封装。事务与隔离级别放在一起理解会更好些。
在使用Spring框架时,可以有两种使用事务的方式,一种是编程式的,一种是申明式的。
【1】- @Transactional注解就是申明式的。
比如我们可以通过在某个方法上增加@Transactional注解,就可以开启事务,这个方法中所有的sql都会在一个事务中执行,统一成功或失败。
在一个方法上加了@Transactional注解后,Spring会基于这个类生成一个代理对象,会将这个代理对象作为bean,当在使用这个代理对象的方法时,如果这个方法上存在@Transactional注解,那么代理逻辑会先把事务的自动提交设置为false,然后再去执行原本的业务逻辑方法,如果执行业务逻辑方法没有出现异常,那么代理逻辑中就会将事务进行提交,如果执行业务逻辑方法出现了异常,那么则会将事务进行回滚。
PS:如果关于动态代理不懂的可以去复习一遍动态代理的源码就清楚了
public static Object newProxyInstance(ClassLoader loader, //用哪个类加载器去加载代理对象
Class<?>[] interfaces, //动态代理类需要实现的接口
InvocationHandler h) //动态代理方法在执行时,会调用h里面的invoke方法去执行
throws IllegalArgumentException
当然,针对哪些异常回滚事务是可以配置的,可以利用@Transactional注解中的rollbackFor属性进行配置,默认情况下会对RuntimeException和Error进行回滚。
PS:这里可以结合编程使用:
方式一:
@Transactional(rollbackFor = Exception.class)
public R function() {
try {
// 业务代码
......
} catch (Exception e) {
// 手动回滚事务
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
return R.fail();
}
return R.suc();
}
方式二:
@Transactional(rollbackFor = Exception.class)
public R function() {
try {
// 业务代码
......
// 设置事务回滚点
Object savePoint = TransactionAspectSupport.currentTransactionStatus().createSavepoint();
// 业务代码
......
} catch (Exception e) {
// 手动回滚到savePoint事务点
TransactionAspectSupport.currentTransactionStatus().rollbackToSavepoint(savePoint);
return R.fail();
}
return R.suc();
}
当然,自己设定回滚的条件也可以,即如果满足什么条件,就手动回滚事务。
【2】- 编程式可以手动创建、回滚和提交事务。
例如:
1.注入DataSourceTransactionManager
@Autowired
private DataSourceTransactionManager dataSourceTransactionManager;
2.手动开启事务
DefaultTransactionDefinition defaultTransactionDefinition = new DefaultTransactionDefinition();
defaultTransactionDefinition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
TransactionStatus transaction = dataSourceTransactionManager.getTransaction(defaultTransactionDefinition);
3.try catch业务代码
try{
// 业务代码
......
// 手动提交事务
dataSourceTransactionManager.commit(transaction);
} catch {
// 手动回滚事务
dataSourceTransactionManager.rollback(transaction);
}
PS:如果A方法调用了B方法,想在A方法中开启事务,B方法中提交事务,那么可以将TransactionStatus transaction
作为参数传递给B方法。
spring事务隔离级别就是数据库的隔离级别:外加一个默认级别
相关知识:
【数据库】数据库中事务的隔离级别(读未提交、读已提交、重复读、可串行化)
【数据库】幻读与不可重复读
数据库的配置隔离级别是Read Commited,而Spring配置的隔离级别是Repeatable Read,请问这时隔离 级别是以哪一个为准?
以Spring配置的为准,如果spring设置的隔离级别数据库不支持,效果取决于数据库。
多个事务方法相互调用时,事务如何在这些方法间传播?
方法A是一个事务的方法,方法A执行过程中调用了方法B,那么方法B有无事务以及方法B对事务的要求不同都会对方法A的事务具体执行造成影响,同时方法A的事务对方法B的事务执行也有影响,这种影响具体是什么就由两个方法所定义的事务传播类型所决定。
REQUIRED(Spring默认的事务传播类型):如果(B)当前没有事务,则自己新建一个事务,如果当前存在事务,则加入(A)这个事务。
其它作为了解吧:
SUPPORTS:当前存在事务,则加入当前事务,如果当前没有事务,就以非事务方法执行。
MANDATORY:当前存在事务,则加入当前事务,如果当前事务不存在,则抛出异常。
REQUIRES_NEW:创建一个新事务,如果存在当前事务,则挂起该事务。
NOT_SUPPORTED:以非事务方式执行,如果当前存在事务,则挂起当前事务
NEVER:不使用事务,如果当前事务存在,则抛出异常
NESTED:如果当前事务存在,则在嵌套事务中执行,否则REQUIRED的操作一样(开启一个事务)
spring事务的原理是AOP,进行了切面增强,那么失效的根本原因是这个AOP不起作用了!常见情况有如下几种:
打个比方:UserService
和 UserServiceImpl
,UserServiceImpl
去实现了 UserService
接口中的某个方法,如果在这个方法上加上 @Transactional 注解则会基于这个类(UserServiceImpl
)生成一个代理对象,而生成得到代理对象的源码中(涉及动态代理):
public static Object newProxyInstance(ClassLoader loader, //用哪个类加载器去加载代理对象
Class<?>[] interfaces, //动态代理类需要实现的接口
InvocationHandler h) //动态代理方法在执行时,会调用h里面的invoke方法去执行
throws IllegalArgumentException
//生成得到代理类
public Object getProxy(){
//注意这里的getInterfaces()
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
UserServiceImpl.getClass().getInterfaces(), 继承InvocationHandler接口的类对象);
}
通过 getInterfaces()
拿到 UserService
生成动态代理类。
由此可得知,当发生类内部调用时,如果是用 this.updateUserAge()
方式,即使 updateUserName()
实现了 UserService
并加了 @Transactional 注解,事务也是不会生效的,因为这里的 this 指的是 UserServiceImpl
,而事务是拿到 UserServiceImpl
生成的代理对象(UserService
),用他来做的事务处理,而 UserServiceImpl
不是代理对象。
public interface UserService {
boolean updateUser();
boolean updateUserName();
}
@Service
public class UserServiceImpl implements UserService {
@Override
public boolean updateUser() {
......
//这里的this指的是UserServiceImpl,这种调用方式下面的两个事务均不会生效
this.updateUserName();
this.updateUserAge();
......
}
@Override
@Transactional
public boolean updateUserName() {
......
}
@Transactional
public boolean updateUserAge() {
......
}
}
解决方法很简单,让那个this变成代理类即可!
//通过api获取到代理对象
UserService proxy = (UserService) AopContext.currentProxy();
proxy.updateUserName();
需要导包和在启动类上加注解才能这样显式地获取
ps:如果对于这点不理解的可以看看事务的原理,或者结合这个视频进行理解https://www.bilibili.com/video/BV1cr4y1671t?p=54,如果还是不理解就可以看看动态代理,关于动态代理不懂的可以去复习一遍动态代理的源码就清楚了
参考:https://www.jb51.net/article/256279.htm
如果一个方法使用@Transactional
开启了事务,方法的业务中修改了A表,接着方法又调用异步服务修改A表,则会发生死锁问题。
原因:事务先给A表上了表锁,紧接着执行的异步方法是新开的线程,无法加入到主线程的事务中,所以需要释放了A表的锁才能够修改A表,而因为整个方法未执行完,事务未提交,所以就释放不了锁,最后造成死锁问题。
解决方法:去掉@Transactional
,使用编程方法,手动添加、回滚和提交事务(在开启异步服务前就将事务提交)。
这个问题涉及到事务的原理,可以回看前言或前面的内容。
如果业务中修改了A表,然后调用其它异步服务,其它异步服务修改了B表,若这个异步服务出现异常,A表仍然会被修改,不会受其它异步服务影响。若不是异步服务,是同步的话,则受事务影响,即其它服务出现异常,AB表都不会被修改。(以上说的服务都是属于同一个微服务中,若是涉及到远程调用其它微服务,则属于分布式事务,以上讨论的是本地事务。)
待更新
redis中事务的概念就是一次执行多条命令;并带有以下三个保证
一个redis事务会经过三个步骤:开始事务 - 》命令入队 - 》执行事务
注意: