Java事务-ACID
1.本地
@Transactional
spring 所有的事务管理策略类都继承自 org.springframework.transaction.PlatformTransactionManager 接口
编程式事务:begin,commit手动提交事务;声明式事务:加上**@Transactional**注解
1. 添加位置
- 接口实现类或接口实现方法上,而不是接口类中。
- 访问权限:public 的方法才起作用。@Transactional 注解应该只被应用到 public 方法上,这是由 Spring AOP 的本质决定的。系统设计:将标签放置在需要进行事务管理的方法上,而不是放在所有接口实现类上:只读的接口就不需要事务管理,由于配置了@Transactional就需要AOP拦截及事务的处理,可能影响系统性能。
1.接口中A、B两个方法,A无@Transactional标签,B有,上层通过A间接调用B,此时事务不生效。
2.接口中异常(运行时异常)被捕获而没有被抛出。
默认配置下,spring 只有在抛出的异常为运行时 unchecked 异常时才回滚该事务,
也就是抛出的异常为RuntimeException 的子类(Errors也会导致事务回滚),
而抛出 checked 异常则不会导致事务回滚 。可通过 @Transactional rollbackFor进行配置。
3.多线程下事务管理因为线程不属于 spring 托管,故线程不能够默认使用 spring 的事务,
也不能获取spring 注入的 bean 。
在被 spring 声明式事务管理的方法内开启多线程,多线程内的方法不被事务控制。
一个使用了@Transactional 的方法,如果方法内包含多线程的使用,方法内部出现异常,
不会回滚线程中调用方法的事务。
2.注解
@Transactional 实质是使用了 JDBC 的事务来进行事务控制的
@Transactional 基于 Spring 的动态代理的机制
@Transactional 实现原理:
1) 事务开始时,通过AOP机制,生成一个代理connection对象,
并将其放入 DataSource 实例的某个与 DataSourceTransactionManager 相关的某处容器中。
在接下来的整个事务中,客户代码都应该使用该 connection 连接数据库,
执行所有数据库命令。
[不使用该 connection 连接数据库执行的数据库命令,在本事务回滚的时候得不到回滚]
(物理连接 connection 逻辑上新建一个会话session;
DataSource 与 TransactionManager 配置相同的数据源)
2) 事务结束时,回滚在第1步骤中得到的代理 connection 对象上执行的数据库命令,
然后关闭该代理 connection 对象。
(事务结束后,回滚操作不会对已执行完毕的SQL操作命令起作用)
属性:transactionManager指定事务管理器
isolation事务的隔离级别,默认是isolation.DEFAULT
propagation(Spring事务的传播行为类型)
timeout
事务的超时时间,单位为秒。
readOnly
该属性用于设置当前事务是否为只读事务,设置为true表示只读,false则表示可读写,默认值为false。如果一个事务只涉及到只读,可以设置为true。
rollbackFor
用于指定能够触发事务回滚的异常类型,可以指定多个异常类型。 默认是在RuntimeException和Error上回滚。
noRollbackFor
抛出指定的异常类型,不回滚事务,也可以指定多个异常类型。
3.脏读、不可重复读、幻读
(1)脏读(Dirty Read)
脏读是指在一个事务处理过程里读取了另一个未提交的事务中的数据。
(2)不可重复读(Non-repeatable read)
在一个事务的两次查询中数据不一致,可能是两次查询过程中另一个事务更新了数据。
(3)虚读(幻读)
是指在一个事务内读取到了别的事务插入的数据,导致前后读取不一致。,例如事务T1批量对一个表中某一列列值为1的数据修改为2的变更,但是在这时,事务T2对这张表插入了一条列值为1的数据,并完成提交。此时,如果事务T1查看刚刚完成操作的数据,发现还有一条列值为1的数据没有进行修改,而这条数据其实是T2刚刚提交插入的,这就是幻读。
MySQL四类事务隔离级别
① Serializable (串行化):可避免脏读、不可重复读、幻读的发生
② Repeatable read (可重复读):可避免脏读、不可重复读的发生
③ Read committed (读已提交):可避免脏读的发生
④ Read uncommitted (读未提交):最低级别,任何情况都无法保证
InnoDB 存储引擎的默认支持的隔离级别是 REPEATABLE-READ(可重读)。
通过SELECT @@tx_isolation; 命令来查看;使用的是Next-Key Lock 锁算法,
因此可以避免幻读的产生
4.失效原因
1.底层数据库引擎不支持事务
2.在非public修饰的方法使用
3.异常被catch掉了
4.方法中调用同类方法:
一个类中的A方法(未标注声明式事务)在内部调用了B方法(标注了声明式事务),这样会导致B方法中的事务失效
public class Test{
public void A(){
//插入一条数据
//调用B方法
B();
}
@Transactional
public void B(){
//插入数据
}
}
// Spring在扫描Bean的时候会自动为标注了@Transactional注解的类生成一个代理类(proxy),当有注解的方法被调用的时候,实际上是代理类调用的,代理类在调用之前会开启事务,执行事务的操作,但是同类中的方法互相调用,相当于this.B(),此时的B方法并非是代理类调用,而是直接通过原有的Bean直接调用,所以注解会失效
解决方法:用代理类调用方法
@Transactional
public void a() {
//this.b();
TestService service = (TestService)AopContext.currentProxy();
service.b();
}
@Transactional
public void b(){}
// 引入依赖spring-boot-starter-aop并在启动类开启代理类注解@EnableAspectJAutoProxy(exposeProxy = true),该注解会使用AspectJ方式取代jdk动态代理,exposeProxy属性是对外暴露代理对象
5.rollbackFor(noRollbackFor)属性设置错误
6.Spring事务的传播行为类型配置错误
总结:
2.分布式
CAP定理、Raft保证CP、Base理论最终一致性缓存、消息中的数据都是保证和mysql数据的最终一致性
阿里-seata(两阶段提交)
3.总结
并发量小情况:使用2PC、3PC、TCC模式,如现在阿里提供的seata组件
并发量大情况:使用可靠消息+最大努力通知型,如使用RabbitMQ或者RocketMQ等