spring 有五个事务隔离级别:ISOLATION_DEFAULT、ISOLATION_READ_UNCOMMITTED、ISOLATION_READ_COMMITTED、ISOLATION_REPEATABLE_READ、ISOLATION_SERIALIZABLE
第一种是 Spring 默认使用 DB设置的事务隔离级别,后面四种事务隔离级别跟 Mysql 的事务隔离级别一致,下面就类比着 Mysql 的事务隔离级别,进行分析!!!
session B 开启一个事务,修改了 id = 1 的 name 数据,但是 session B 事务未提交;此时 另一个 session A 事务读取到 session B 修改但没提交的 name 数据,并返回了,然后 session B 事务回滚了,那么此刻 session A 读取到的数据是不存在的,那么这种现象是脏读。(读未提交–该事务隔离级别会出现)
session A 事务开启,读取到 id = 1 的 name 数据为武汉,session A 事务并未结束, session B 事务将 id = 1 的 name 数据修改为 温州,session A 事务读取 id =1 的 name 数据为温州;session B 事务将 id = 1 的 name 数据修改为杭州,sesssion A 事务读取 id = 1 的 name 数据为杭州;session B 事务每次修改后的数据,session A 事务都能读取到最新的数据,那么这种现象就是不可重复读。(读未提交,读已提交–两种事务隔离级别都可能会出现)
session A 事务开启,读取 id > 0 的 name 数据为武汉,session 事务 A 未结束;session B 事务开启,插入温州市数据;session A 事务再次读取 id > 0 的 name 数据,读取到武汉和温州时,这种现象就是幻读 (读未提交,读已提交,可重复读–三种隔离级别都可能出现)
session A 事务开启,更新 id = 1 的数据为温州;session B 的事务开启,读取到未提交事务 A 修改的数据;
session A 事务开启,更新 id =1 的 name 数据,session B 事务开启,读取 id = 1 的 name 数据为 武汉;session A事务提交,session B 事务再次读取 id = 1 的name 数据为温州;也就是说 事务 B 只能读取到 事务 A 修改且提交的数据
session A 事务开启,更新 id = 1 的 name 名称为温州,此时 session B 事务开启,读取 id = 1 的数据为 武汉
session A 事务读取 id = 1 的 name 名称为温州;session A 事务提交;session B 事务提交;session B 事务再次读取 id = 1 的 name 名称为 温州;也就是说在可重复读隔离级别下,session B 事务读取 session A 事务修改后的值,需要 session A 事务修改数据且提交,session B 当前的事务也提交。
提问:为什么 session A事务修改数据,加了写锁,为什么别的事务还可以操作?因为 MVCC 多版本控制,有快照可以供其他事务读
在 Spring-tx 的 jar 包中, TransactionDefinition 接口定义了七种事务传播行为:
当前方法必须在一个具有事务的上下文中运行,如果调用端有事务在运行,那么被调用端将在该事务中运行,否则将重新开启一个事务( 如果被调用端发生异常,则调用端和被调用端事务都将回滚 )
当前方法不必需要一个具有事务的上下文,但是上下文具有事务时,也可以在这个事务中运行
当前方法必须在一个事务中运行,不然会抛出异常
当前方法必须运行在它自己的事务中,如果一个新的事务将启动,而且如果有一个当前事务在运行的话,则当前事务( 方法 )在运行期被挂起,等待新事务提交或回滚才恢复执行
当前方法不支持在事务中运行,总是以非事务的方式运行,如果有一个事务正在执行,当前方法将在运行期挂起,直到这个事务提交或回滚,才恢复执行
当前方法不支持在事务中运行,如果存在当前事务,将会抛出异常
如果当前方法存在一个事务正在执行,那么该方法应该运行在一个嵌套事务中,被嵌套的事务(子事务)可以独立于被封装的事务(父事务)之外提交或回滚。如果封装事务存在,并且外层事务抛出异常,事务回滚,那么内层事务必须回滚,反之,内层事务并不影响外层事务。如果封装事务不存在,则跟 PROPAGATION_REQUIRED 的传播机制一致
Java 的访问权限,主要有四种:private、default、protected 、public 权限从左往右,依次变大,如果事务方法,使用错误的访问权限,事务就会出现问题,spring-tx 的 jar 包 中 AbstractFallbackTransactionAttributeSource#computeTransactionAttribute() 方法,事务方法必须是 public 关键字修饰的访问权限,否则会返回 null,事务就会失效,所以事务方法访问权限是 private ,default ,protected 关键字修饰时,Spring不会提供事务功能
如果某个方法不想被子类重写,就需要用 final 关键字修饰,Spring 的事务是基于 AOP 实现的,如果使用 final 关键字,该方法将不会被重写,则无法通过动态代理,生成代理类,添加事务;static 关键字也无法使用动态代理,生成代理类,添加事务!
同一个类中的方法,直接内部调用,会导致事务失效,因为 spring 的 事务是通过 aop 实现的,同一个类中的方法,直接调用,相当于通过 this 关键字直接调用方法,没有通过代理类,添加事务,故事务失效!可以通过 AopContext#currentProxy() 获取代理对象,使用事务!!!
@Servcie
public class ServiceA {
public void save(User user) {
queryData1();
queryData2();
((ServiceA)AopContext.currentProxy()).doSave(user);
}
@Transactional(rollbackFor=Exception.class)
public void doSave(User user) {
addData1();
updateData2();
}
}
使用 Spring 的事务,需要Bean 对象被 Spring 管理。没有使用@Controller @Service @Reponsitory @Component @Bean 等注解,对象将不会被 Spring 管理,则事务不生效
Spring 事务需要正常回滚,必须抛出 Spring 能正确处理的异常,如果没有异常抛出,则事务不会回滚
@Slf4j
@Service
public class UserService {
@Transactional
public void add(UserModel userModel) throws Exception {
try {
saveData(userModel);
updateData(userModel);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
}
如果代码捕获了异常,并手动抛出了异常:Exception ,对于普通的 Exception (非运行时异常),事务不会回滚
@Slf4j
@Service
public class UserService {
@Transactional
public void add(UserModel userModel) throws Exception {
try {
saveData(userModel);
updateData(userModel);
} catch (Exception e) {
log.error(e.getMessage(), e);
throw new Exception(e);
}
}
}
@Transactional 注解中的 rollbackFor 属性使用默认值,那么当程序抛出 Exception ,事务不会回滚,所以,一般需要设置 rollbackFor 的属性值为 Exception 或 Throwable
@Slf4j
@Service
public class UserService {
@Transactional(rollbackFor = BusinessException.class)
public void add(UserModel userModel) throws Exception {
saveData(userModel);
updateData(userModel);
}
}