关于事务的一些补充
事务的两大知识点:隔离特性(对多个线程来说)和传播特性(对多个线程来说)
事务的注意事项:
- 不要在接口上声明@Transactional ,而要在具体类的方法上使用 @Transactional 注解,否则注解可能无效。
- 不要将@Transactional放置在类级的声明中,会使得所有方法都有事务。
- 同一个类中调用@Transactional的方法, @Transactional无效。如果非要调用,见例1
- 使用了@Transactional的方法,只能是public,@Transactional注解被外部其他类调用才有效,故只能是public。道理和上面的有关联。其他不会报错,但事务无效。
一、隔离特性
在事务的四个属性中,隔离性又可以细分,事务的隔离级别分为四级:未提交读(read uncommitted)、已提交读(read committed)、可重复读(repeatable read)、串行化(serializable)。(在spring boot中可以在@Transactional中的value来设置)
未提交读
A事务已执行,但未提交;B事务查询到A事务的更新后数据;A事务回滚;—出现脏数据
已提交读
A事务执行更新;B事务查询;A事务又执行更新;B事务再次查询时,前后两次数据不一致;—不可重复读
可重复读
A事务无论执行多少次,只要不提交,B事务查询值都不变;B事务仅查询B事务开始时那一瞬间的数据快照;
串行化
不允许读写并发操作,写执行时,读必须等待,允许执行读读操作;
Mysql默认的事务隔离级别是可重复读(有的人说项目中要改成读已提交
),其实我之前做过那么多项目一直用的是mysql的默认隔离级别。
但是MYSQl帮我们在可重复度的情况下帮我们把幻读的情况解决了,所以,MYSQL在可重复读的情况下是不会出现幻读的。
二、传播特性
事务之间的调用大至分下面几种情况
有事务会一直传播给无事务的,并且会一直传播下去,无论在本类或者异类调用中,如A有事务,A调用B(无事务),B调用C(无事务),B和C同样会具有A的事务。
无事务是不会向有事务传播的。
事务与事务之间的调用,仅限于异类中,本类中的调用需要重新注入本类调用,参照例一,异类之间的调用又分为7个级别(了解)(在spring boot中可以在@Transactional中的value来设置propagation的值来设置)
1、PROPAGATION_REQUIRED ,默认的spring事务传播级别,使用该级别的特点是,如果上下文中已经存在事务,那么就加入到事务中执行,如果当前上下文中不存在事务,则新建事务执行。所以这个级别通常能满足处理大多数的业务场景。;
2、PROPAGATION_SUPPORTS:支持当前事务,若当前存在事务,则支持当前事务,若不存在,则以无事务的状态执行;
3、PROPAGATION_MANDATORY:支持当前事务,若当前存在事务,则支持当前事务,若不存在,则抛出异常;
4、PROPAGATION_REQUIRES_NEW:新开事务执行,若当前存在事务,则挂起当前事务;
5:PROPAGATION_NOT_SUPPORTED:以无事务状态执行,若存在事务则挂起;
6:PROPAGATION_NEVER:以非事务状态执行,若存在事务则抛异常;
7、PROPAGATION_NESTED:当前存在事务,则嵌套一个新事务,当前不存在事务,则新开事务。
例一:
@Autowired
IService iService = null;
@Service(“service”)
public class ServiceImpl implements IService{
@Override
@Transactional(propagation=Propagation.REQUIRED)
public void a() {
this.b(); //①
b();//②
iService.b();③
}
@Override
@Transactional(propagation=Propagation.REQUIRES_NEW)
public void b() { }
}
可以看到a()和b()都是有事务的,但是在a中用①和②的调用方式是本类的调用方式,@Transactional(propagation = Propagation.REQUIRES_NEW) 不起作用,也就是说这时候 b 方法与 a 方法的事务定义是一样的,只有③方式才会使b的事务生效。