文章目录
参考
https://blog.csdn.net/hepei120/article/details/78058468
https://blog.csdn.net/yangquanwa/article/details/88578357
一. @Transactional失效
@Transactional失效的场景有很多种,感兴趣的研究下,文章很多,本文着重说明类内部调用Spring事务注解@Transactional失效的场景。
现象1
在一个事务中更新之后再查询能查询到最新的数据,毋庸置疑。
代码
@Autowired
private TestMapper testMapper;
@Transactional
public void testTransactional() {
System.out.println("1.====:" + testMapper.selectById(1).toString());
updateTestById("司马缸5");
System.out.println("2.====:" + testMapper.selectById(1).toString());
}
public void updateTestById(String name) {
TestEntity entity = new TestEntity();
entity.setId(1);
entity.setName(name);
testMapper.updateById(entity);
}
执行testTransactional()
输出
现象2
在一个事务中更新,在查询方法中添加事务传播行为Propagation.REQUIRES_NEW,意味着不管事务存不存在都新启用一个事务运行。
代码
@Autowired
private TestMapper testMapper;
@Transactional
public void testTransactional() {
System.out.println("1.====:" + testMapper.selectById(1).toString());
updateTestById("司马缸5");
System.out.println("2.====:" + get().toString());
}
public void updateTestById(String name) {
TestEntity entity = new TestEntity();
entity.setId(1);
entity.setName(name);
testMapper.updateById(entity);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public TestEntity get() {
return testMapper.selectById(1);
}
执行testTransactional()
输出
分析
事务隔离级别
事务隔离级别由弱到强分别是:READ_UNCOMMITTED(未提交读)、READ_COMMITTED(提交读)、REPEATABLE_READ(重复读)和SERIALIZABLE(串行读)。
数据问题
-
脏读:
脏读指的是一个事务允许读取其他正在运行的事务还没有提交的数据,这种情况的发生主要因为没有加锁。 -
不可重复读:
是指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。(即不能读到相同的数据内容)
例如,一个编辑人员两次读取同一文档,但在两次读取之间,作者重写了该文档。当编辑人员第二次读取文档时,文档已更改。原始读取不可重复。如果只有在作者全部完成编写后编辑人员才可以读取文档,则可以避免该问题。
要达到允许可重复读,必须让当前事务保持一个读共享锁。 -
幻读:
幻读指的是事务不是串行发生时发生的一种现象,是事务A读取了事务B已提交的新增数据。例如第一个事务对一个表的所有数据进行修改,同时第二个事务向表中插入一条新数据。那么操作第一个事务的用户就发现表中还有没有修改的数据行,就像发生了幻觉一样。解决幻读的方法是增加范围锁或者表锁。
不同的隔离级别会出现的问题
隔离界别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
READ_UNCOMMITTED | 允许 | 允许 | 允许 |
READ_COMMITTED | 不允 | 允许 | 允许 |
REPEATABLE_READ | 不允 | 不允 | 允许 |
SERIALIZABLE | 不允 | 不允 | 不允许 |
MySQL的默认事务隔离级别是REPEATABLE_READ,ORACLE、SQL Server、DB2和PostgreSQL的默认事务隔离级别是READ_COMMITED
按照MySQL的默认事务隔离级别REPEATABLE_READ按理来说不应该出现脏读,也就是不应该读取到其他事务没有提交的数据,但是现在读取到了,WHY???
继续看现象3
现象3
在一个事务中更新,在查询方法中添加事务传播行为Propagation.REQUIRES_NEW,意味着不管事务存不存在都新启用一个事务运行。
代码
@Service
public class TestServiceImpl extends ServiceImpl<TestMapper, TestEntity> implements ITestService {
@Autowired
private TestMapper testMapper;
@Autowired
private TestServiceImpl2 testServiceImpl2;
@Transactional
public void testTransactional() {
System.out.println("1.====:" + testMapper.selectById(1).toString());
updateTestById("司马缸5");
System.out.println("2.====:" + testServiceImpl2.get().toString());
}
public void updateTestById(String name) {
TestEntity entity = new TestEntity();
entity.setId(1);
entity.setName(name);
testMapper.updateById(entity);
}
}
@Service
public class TestServiceImpl2 extends ServiceImpl<TestMapper, TestEntity> implements ITestService {
@Autowired
private TestMapper testMapper;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public TestEntity get() {
return testMapper.selectById(1);
}
}
执行testTransactional()
输出
分析
看出现象2和现象3的代码有什么不同了吗?调用get()不同,2是this.get(),3是testServiceImpl2.get(),3把get()方法提取出来了。
都是查询数据库为什么2中@Transactional(propagation = Propagation.REQUIRES_NEW)失效了呢?
原因
Spring事务是基于AOP实现的,就是把@Transactional标识的类进行代理,在创建对象的时候创建代理类,在代理类中执行方法前后添加事务处理,相当于@Around。当我们执行需要事务的方法时是调用的代理类执行。但是在方法内部调用本类其他事务方法是通过this.get()的方式,得到的是目标类不是代理类,所以get()方法的Propagation.REQUIRES_NEW
是失效的,并没有创建一个新的事务,还是在一个事务中执行。
结论
事务是基于代理类的