事务

事务

保证业务操作完整性的数据库技术。
一个业务操作中的多个步骤,要么同时成功,要么同时失败,而且不能产生相互的影响。
事务是数据库保证的,java 中的事务操作仅仅是对数据库中 api 的调用。

控制事务

不同的持久化技术事务控制的方式是不一样的
JDBC:
	开启事务 Connection.setCommit(true);
	提交事务 Connection.commit();
	回滚事务 Connection.rollback();
Mybatis: Mybatis 中 SqlSession 底层封装的也是 Connection	
	自动开启事务
	提交事务 SqlSession.commit();
	回滚事务 SqlSession.rollback();

结论: 无论是JDBC,Mybatis,还是其他持久成技术,控制事务的底层都是 Connection 对象来完成的
		

Spring 控制事务的开发

事务是业务层中的额外功能,既然是额外功能,最好是通过 AOP 的方式进行开发,而 Spring 就是通过 AOP 的方式进行事务开发的。
AOP 四步
1、原始对象
2、额外功能
3、切入点
4、组装切面

1、原始对象

public class ServiceImpl{
	原始对象->原始方法->核心功能(业务操作+ Dao 调用)
	Dao 调用以成员变量的形式注入进来
}

2、额外功能

org.springframework.jdbc.datasource.DatasourceTransactionManager: Spring 框架封装的控制事务代码,实际上就是如下 MethodIntercept中的事务操作
DataSourceTransactionManager对象对于事务的操作是通过连接对象实现的,所以需要将连接对象注入给它。现阶段开发为了提高连接对象的使用效率,通常通过引入连接池进行
连接获取。所以等效于DataSourceTransactionManager需要连接池,此时我们只需要为它注入连接池,它会根据连接池获取连接进而进行事务控制。
1、MethodIntercept
原始方法运行之前,开启事务,运行之后提交事务,出现异常回滚事务
public void invoke(MethodInvocation invocation){
        try{
        Connection.setAutoCommit(true);
        Object res = invocation.process(); // 原始方法运行
        Connection.commit();
        }catch(Exception e){
        Connection.rollback();
        }
        return res;        
}
2、@Aspect
   @Around

3、切入点

@Transactional,可指定事务这个额外功能要加给哪些方法。可加在类上,意味着该类中所有方法都加入事务。可加在方法上,意味着本方法加入事务。

4、组装切面

1、切入点
2、额外功能

Spring 框架中通过一个标签来体现额外功能,通过该标签指定TransactionManager,进而可执行相应的事务操作
<tx:annotation-driven transacationManager=""/>

Spring 框架中通过注解扫描的方式,扫描@Transactional注解,进而找到对应切入点,也就是需要事务操作的方法

细节

<tx:annotation-driven transacationManager="datasourceTransactionManager",proxy-target-class="true"/>
proxy-target-class: 进行动态代理底层实现的切换 
默认是 false,通过JDK动态代理(动态代理的目标必须要有接口实现)
true,通过Cglib动态代理(继承目标类,创建它的子类,在子类中重写父类同名的方法,进而实现功能的修改)

Spring 的事务属性

1、什么是事务属性

1、什么是属性
描述物体特征的一些列值
2、事务属性:描述事务特征的一些列值
五个维度:
隔离属性
传播属性
只读属性
超时属性
异常属性

2、如何添加事务属性

@Transactional(isolation=,propagation=,readOnly=,timeOut=,rollbackFor=,noRollbackFor=)
隔离属性:isolation
传播属性:propagation
只读属性:readOnly
超时属性:timeOut
异常属性:rollbackFor/noRollbackFor

3、事物属性详解

隔离属性:描述了事务解决并发问题的特征
1、什么是并发
多个事务(用户)在同一时间访问并操作了相同的数据
2、并发出现了什么问题
脏读
不可重复读
幻读
3、并发问题如何解决
通过隔离属性解决,隔离属性中设置不同的值,解决并发处理过程中的问题

账户表:

id name password balance
1 a 123 1000
2 b 123 2000
3 c 123 1000
4 d 123 1000

t1,t2 两个用户同一时间(并不是严格意义的同一时间)访问了 id 为1的数据,产生了并发

事务并发产生的问题
  • 脏读
一个事务读取了另外一个事务没有提交的数据,最终会在本事务中产生数据不一致的问题
eg:
用户A,用户B同时访问了id为1的数据,用户A将balance值减去300,在用户A没有提交事务的前提下,用户B他看到的balance值仍然是1000,
他将Balance的值减去400.然后用户B提交事务了。此时用户A不想执行这个操作了,他选择了回滚,然后他提交了事务,在用户A的想法中,现在balance仍然为1000
在用户B的想法中balance的值为600。当有一天两人交谈时会发现数据不一致,出现了脏读。

解决方案:
@Transacational(isolation = Isolation.READ_COMMITTED) 读已提交
一个事务读取另一个事务提交后的数据
  • 不可重复读
一个事务中多次读取相同的数据,但是读取的数据结果不一样,这样就会在本事务中产生数据不一致的问题
eg:
用户A,用户B同时访问了 id 为1的数据,用户A以0.00001秒的差距先一步进行访问,并开启了事务,他做了一次查询,查询到balance的值为1000,
然后在本次事务(用户A)中,用户A去做其他业务操作了,并没有提交事务。此时用户B开始进行访问Id为1的数据,他更新的balance值,将其变成了800,
然后用户B提交了事务。当用户A做完业务操作后,他又一次对 id 为1的数据进行了一次查询,此时balance值为800,出现多次访问中数据不一致的问题。

解决方案:
@Transacational(isolation = Isolation.REPEATABLE_READ) 可重复读
实际上是为该条数据加了行锁,当用户A先一步访问数据时,它会为这条数据加一把行锁,只要用户A事务没有结束,锁没有释放,那用户B永远都要等着
  • 幻读
一个事务中多次对整表数据进行查询(如聚合balance值),但是多次查询数据不一致
eg:
用户A,用户B同时访问账户表数据,用户A先一步进行了访问,并开启了事务,他查询了账户表中的总金额数(sum(balance)),然后就去做其他业务操作了,
本事务并没有提交,此时用户B开发访问账户表数据,他新增了一条数据,id为5,该数据balance值为2000,然后提交事务了。此时用户A又一次访问账户表的总balance值
发现值发生了变化,表中多了一条id为5的数据,就像影子一样。数据产生不一致。

解决方案:
@Transacational(isolation = Isolation.SERIALIZABLE) 可序列化的
实际上是为账户表加了表锁,当用户A访问账户表时,会为该表加一把表锁,只有用户A事务提交,并释放表锁,用户B才能访问账户表

总结

并发安全上来说,一定是序列化方式安全性最高(SERIALIZABLE),之后依次是可重复读(REPEATABLE_READ),读已提交(READ_COMMITTED)
效率上来说,顺序相反,READ_COMMITTED未加任何锁

数据库对于隔离属性的知识


隔离属性的值 mysql oracle
READ_COMMITTED 支持 支持
REPEATABLE_READ 支持 不支持
SERIALIZABLE 支持 支持
oracle 不支持可重复读的隔离属性,它是通过多版本对比的方式来解决这个问题。

Spring 默认的隔离属性

ISOLATION_DEFAULT,代表它会调用不同数据库所设置的隔离性属性
Mysql: REPEATABLE_READ
Oracle: READ_COMMITTED

隔离属性在实战中的建议

推荐使用Spring指定的默认值 ISOLATION_DEFAULT
如果连接不同数据库,则使用的是数据库默认隔离级别

如果并发量高,也不推荐使用隔离属性解决,因为隔离属性中加锁是物理锁,对系统性能影响较大,一般会使用乐观锁的方式解决并发高的场景

传播属性(propagation)

事务的嵌套

TA事务中包含TB数据,TB事务中包含TC事务,等。
事务嵌套会出现的问题:
大事务TA中融入了多个小事务TB、TC,小事务TC出现异常回滚,TB事务正常提交,此时会出现,TA,TC回滚,而TB并没有回滚的现象。影响了事务的原子性

事务嵌套如何解决:
使用传播属性来解决

传播属性的值

传播属性的值 外部不存在事务 外部存在事务 用法 备注
REQUIRED 开启事务 融合到外部事务中 @Transactional(propagation=Propagation.REQUIRED) 增删改方法
SUPPORTS 不开启事务 融合到外部事务中 @Transactional(propagation=Propagation.SUPPORTS) 查询方法
REQUIRES_NEW 开启事务 挂起外部事务,创建新的事物 @Transactional(propagation=Propagation.REQUIRES_NEW) 日志记录的方法中
NOT_SUPPORTED 不开启事务 挂起外部事务 @Transactional(propagation=Propagation.NOT_SUPPORTED) 不常用
NEVER 不开启事务 抛出异常 @Transactional(propagation=Propagation.NEVER) 不常用
MANDATORY 抛出异常 融合到外部事务中 @Transactional(propagation=Propagation.MANDATORY) 不常用
融合到外部事务意味着放弃本事务,使用外部事务

默认的传播属性

增删改: 直接使用 Spring 默认的传播属性 REQUIRED 
查询: 显示的指定传播属性为 SUPPORTS

只读属性

针对于只查询的业务方法,可以加入只读属性,可提高运行效率(当事务为只读属性时,不会为其加各种各样的锁,也就意味着性能会得到释放)
@Transactional(propagation=Propagation.SUPPORTS,readOnly=true)
默认值:false

超时属性(timeout)

指定了事务等待的最长时间

为什么事务要进行等待呢
因为在当前事务访问数据时,有可能访问的数据被别的事务加锁,那么本事务就需要等待了,等待的时间以秒为单位
超过 timeout 就会抛出异常

@Transactional(timeout=2)
默认值: -1 最终由对应的数据库来指定

异常属性

spring 事务处理过程中
默认 对于RuntimeException及其子类采用的是回滚的策略
默认 对于Exception及其子类采用的是提交的策略

rollbackFor = {} 回滚   
noRollbackFor = {} 提交

@Transactional(rollbackFor=java.lang.Exception.class) 当遇到Exception及其子类的异常时,进行回滚
@Transactional(noRollbackFor=java.lang.RuntimeException.class) 当遇到RuntimeException及其子类的异常时,进行提交

事务属性常见配置总结

隔离属性 使用默认
传播属性 增删改: 默认  查询: SUPPOETS
只读属性 增删改: 默认 false 查询: true
超时属性 使用默认
异常属性 使用默认

增删改 : @Transactionl
查询: @Transactional(propagation=Propagation.SUPPORTS,readOnly=true)
posted @ 2023-09-14 22:51  jamers  阅读(18)  评论(0编辑  收藏  举报