1.什么是事务属性
属性:描述物体特征的一系列值
事务属性;描述事务特征的一系列值
1. 隔离属性
2. 传播属性
3. 只读属性
4. 超时属性
5. 异常属性
2.如何添加事务属性
@Transactional(isloation=,propagation=,readOnly=,timeout=,rollbackFor,norollbackFor=,)
3.事务属性详解
1.隔离属性(isloation)
概念:描述了事务解决并发问题的特征。
1. 什么是并发
多个事务(用户)在同一时间,访问操作了相同的数据
同一时间:0.00几秒的微小的差异
2. 并发会产生的问题
1.脏读
2.不可重复读
3.幻影读
3. 并发问题的解决
通过隔离属性解决,隔离属性当中设置不同的值解决并发处理过程中的问题。
脏读
一个事务(用户)读取了另一个事务中没有提交的数据,会在本事务中产生数据不一致的问题。
如下图:
suns媳妇首先在suns的账户取了300,执行更新操作后还剩700,与此同时suns以0.00几秒的差异访问suns账户,
此时账户已被取了300还剩700,
suns又取了200,在suns角度执行更新操作还剩500并且提交,然后suns媳妇良心发现把300又还了回去(执行回滚操作),
此时在suns媳妇角度账户还剩1000;
suns是读取了suns媳妇没有进行提交操作的700,进行取200的操作,所以suns认为还有500。读取到了脏数据500.
解决方案:@Transactional(isolation = Isolation.READ_COMMITTED) 读已提交的数据
READ_COMMITTED隔离级别并没有显式使用行级锁来保证读取操作的一致性。它的主要特点是一个事务只能读取到已经提交的数据,而不会读取到未提交的数据。这是通过数据库的一致性读取机制来实现的。
2. 不可重复读
一个事务中,多次读取相同的数据,但是读取结果不一样,会在本事务中产生数据不一致的问题。
注意:
- 1.不是脏读 后来读的是已经提交的数据;2.在一个事务中查询
不可重复读如下图:
- suns媳妇开启事务第一次查询id=1的数据为1000块,做了一系列操作xxx,与此同时suns以0.00几秒的差距对id=1的数据进行了更新操作,取了200并且进行提交操作,还剩800。与此同时suns媳妇再次查询却得到了800的数据,不知道此时该以1000为基准还是800为基准。影响后续操作。
-
解决方案:@Transactional(isolation = Isolation.REPEATABLE_READ)
本质:给先执行事务的数据加上一把行锁
区别:
- 脏读是一个事务读取了另一个事务尚未提交的数据,读取到的数据可能是无效的或可能被回滚的。
- 不可重复读是同一个事务内多次读取同一行数据时,发现数据被其他事务修改(已提交)导致结果不一致。
3.幻影读
一个事务中,多次对整表进行查询统计,但是结果不一样,会在本事务中产生数据不一致的问题。
幻读(Phantom Read)是数据库并发操作中的一种问题,它指的是在同一个事务中多次执行相同的查询,但得到的结果集却不同。这种情况下,新插入的行或已删除的行会在同一个事务内重新出现,就像是出现了幻觉一样,因此称为幻读。
如下图:
tx1先进行统计操作为5000(4行),同时tx2用户插入一个数据,tx1再次查询统计操作为6000(5行)。----插入的第五行像个幻影所以叫幻影读。
解决方案:@Transactional(isolation = Isolation.SERIALIZABLE)
本质:表锁(tx1先访问了这张表,就会把这张表锁上,tx2就不能插入数据。)
总结
-
并发安全性:SERIALIZABLE > REPEATABLE_READ > READ_COMMITTED
-
运行效率;SERIALIZABLE < REPEATABLE_READ < READ_COMMITTED
-
SERIALIZABLE(串行化):
SERIALIZABLE是最高的隔离级别,它确保事务是完全隔离的,可以防止任何并发问题。在该隔离级别下,事务会对读取的数据进行共享锁(Shared Lock),确保其他事务无法修改数据,直到当前事务提交或回滚。这种严格的隔离级别保证了数据的一致性,但并发性较低,因为并发访问同一资源时需要等待锁的释放。 -
REPEATABLE_READ(可重复读):
REPEATABLE_READ隔离级别保证了事务在同一个事务内多次读取同一行数据时的一致性。在该隔离级别下,读取的数据会被加上共享锁,并且其他事务无法对这些数据进行修改。这意味着其他事务无法插入、更新或删除这些数据,从而保证了一致性。相对于SERIALIZABLE隔离级别,REPEATABLE_READ允许更高的并发性,因为事务只在需要读取的数据上加锁。 -
READ_COMMITTED(读已提交):
READ_COMMITTED是较低的隔离级别,它确保一个事务只能读取到已经提交的数据。在该隔离级别下,读取的数据不会加上共享锁,允许其他事务对这些数据进行修改。这意味着读取操作不会阻塞其他事务的更新操作,提高了并发性能。然而,由于其他事务可能在读取期间进行修改,所以在同一个事务内的多次读取可能会得到不同的结果(不可重复读的问题)。
2.数据库对于隔离属性的支持
隔离属性的值 | MySQL | Oracle |
---|---|---|
Isolation.READ_COMMITTED | √ | √ |
Isolation.REPEATABLE_READ | √ | × |
Isolation.SERIALIZABLE | √ | √ |
Oracle不支持Isolation.REPEATABLE_READ 如何解决不可重复度,采用的是多版本比对的方式 解决不可重复读问题
~默认隔离属性
ISOLATION_DEFAULT:会调用不同数据库所设置的默认隔离属性
mysql:REPEATABLE_READ
Oracle:READ_COMMITTED
查看数据库默认隔离属性
隔离属性在实战中的建议
推荐使用Spring指定的ISOLATION_DEFAULT
mysql:REPEATABLE_READ
Oracle:READ_COMMITTED
在未来的实战中,并发访问情况很低,
如果遇到并发问题,通过乐观锁解决。(隔离锁性能低)
Hibernate(jpa) Version
Mybatis 通过拦截器自定义开发。
2.传播属性(PROPAGATION)
传播属性的概念:
他描述了事务解决嵌套问题的特征
什么叫事务的嵌套:
他指的是一个大的事务中,包含了若干个小的事务。service调用service情况下,有可能会出现事务嵌套。
事务嵌套会出现什么问题:
如上图:A事务开启,B事务开启,B事务提交,C事务开启,C事务出现问题回滚,A还没提交,在最后也跟着回滚,但是此时B事务已经提交无法回滚。
大事务中融入了很多小的事务,他们彼此影响,最终就会导致外部大的事务,丧失了事务的原子性。
传播属性的值及其用法
分析REQURED属性的用法: 假如上图一个类中有方法TXA、TXB、TXC都加入了注解:
@Transactional(propagation = Propagation.REQURED)
- 首先到TXA会判定方法TXA外部不存在事务,就会开启新的事务,
- 然后到TXB判断方法TXB外部存在事务TXA,就会融合到TXA事务中,TXB的事务不复存在
- 同理TXC也融合到TXA事务中,这样整个类只有TXA一个事务,事务嵌套不存在了,满足事务原子性的特点。
默认的传播属性:
Propagation.REQURED是默认值
推荐传播属性的使用方式:
增删改:直接使用默认值REQURED (因为增删改需要事务,使用REQURED 后不管是独立使用,还是在别人内部使用都保证了有事务的存在。)
查询 :显示指定传播属性的值为SUPPORTS (因为查询操作不需要事务,独立应用时就不开启事务了,在内部应用查询操作时,融入外部事务)
3.只读属性(readOnly)
针对于只进行查询操作的业务方法,可以加入只读属性,提高运行效率。 eg:登录
4.超时属性(timeout)
指定了事务等待的最长时间
1. 为什么事务进行等待?
因为当前事务访问数据时,有可能访问的数据被别的事务进行加锁的处理(t1访问id=1的数据,加锁后t2也来访问),那么此时本事务就必须进行等待。
2. 等待时间:秒(单位)
3. 如何应用@Transactional(timeout = 2) ---->超过2秒后会抛出异常
4. 超时属性的默认值: -1 ---->最终由对应的数据库来决定。
5.异常属性
Spring事务处理过程中
默认 对于RunTimeException及其子类,采用的是回滚的策略。
默认 对于Exception及其子类,采用的是提交的策略。
rollbackFor = {java.lang.Exception.class,xxx,xxx} 指定异常类回滚事务
noRollbackFor = {java.lang.RuntimeException.class,xxx,xxx} 指定异常类提交事务
建议:实战中使用RuntimeException及其子类,使用事务异常属性的默认值
4.事务属性常见配置总结
1. 隔离属性 默认值 mysql:REPEATABLE_READ(行锁,解决不可重复度)
2. 传播属性 Required(默认值)增删改 Supports查询操作
3. 只读属性 readOnly false 增删改 true 查询
4. 超时属性 默认值 -1
5. 异常属性 默认值
增删改操作 @Transactional
查询操作 @Transactional(propagation = Propagation.SUPPORTS,readOnly = true)
5.基于标签的事务配置方式(事务开发的第二种形式)
基于注解@Transactional的事务配置回顾。
如下图:
第一步:创建原始对象 userService
第二步:额外功能(DataSourceTransactionManager帮我们封装了事务处理的额外功能)
第三步:通过@Transactional加入切入点。也设置了对应的事务属性
第四步:整合;整合第二三步。
基于标签的事务配置方式与基于注解的事务配置区别在第三四步,第一二步是一样的。
基于标签的事务配置方式:
前两步和基于注解的事务配置一样:配置对象 --> 事务处理的额外功能
第三步:给不同的方法设置对应的事务属性 (切入点在第四步)
<tx:advice id="txAdvice" transaction-manager="dataSourceTransactionManager">
<!--事务属性--><tx:attributes>
<tx:method name="register" isolation="DEFAULT"propagation="SUPPORTS"></tx:method>
<tx:method name="login"></tx:method>
<!-- 在name属性中指定 方法名
等效于:
@Transactinal
public void register(){} -->
</tx:attributes>
</tx:advice>
第四步:切入点+整合
<aop:config>
<aop:pointcut id="pc" expression="execution(*com.dy.service.UserServiceImpl.register(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pc"></aop:advisor>
</aop:config>
简化:
基于标签的事务配置在实战中的应用方式
简化1:
在第三步中给不同的方法设置事务属性中,如果方法多则会造成代码冗余。如何解决?
<tx:method name="register" isolation="DEFAULT"propagation="SUPPORTS"></tx:method>
<tx:method name="login"></tx:method>
简化为:
<tx:method name="register"></tx:method>这句代码表示给方法名为register的方法配置对应的事务。
<tx:method name="modify*"></tx:method>这句代码表示给方法命名为modify开头的方法配置对应的事务。
这个事务符合增删改的事务
<tx:method name="*"propagation="SUPPORTS",read-Only = "true"></tx:method>这句代码放在name="modify*" 和 name="register"下面表示给方法名除了开头为modify的方法和方法名为register的方法以外的所有方法加上对应事务。这个符合查询方法的事务
<!--编程的时候:service中负责进行增删改查操作的方法,都可以以modify开头 查询操作命名无所谓-->
简化2:
在第四步切入点+整合中 指定切入点太多的话会代码冗余
应用过程中(以包作为切入点),service放置到service包中
<aop:config>
<aop:pointcut id="pc" expression="execution(*com.dy.service..*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pc"></aop:advisor>
</aop:config>
纯注解版事务编程
对应上图顺序
1. 原始对象
@Serice
public class UserServiceImpl implements UserService{
@Autowired
private UserDAO userDAO;
}
2. 额外功能
@Configuration
public class TransactionAutonConfiguration{
@Autowired
private DataSource dataSource;//已在别的类中完成DataSource的代码
@Bean
public DataSourceTransactionManager dataSourceTransactionManager(){
DataSourceTransactionManager dstm = new DataSourceTransactionManager ();
dstm.setDataSource(dataSource);
return dstm;
}
}
3. 事务属性
@Transactional
@Serice
public class UserServiceImpl implements UserService{
@Autowired
private UserDAO userDAO;
}
4. 基于Scheme的事务配置
@EnableTransactionManager -- 配置bean中