目录
1.什么是事务属性
属性:描述物体特征的一些列值
事务属性:主要从5个维度来描述事务。
1.隔离属性
2.传播属性
3.只读属性
4.超时属性
5.异常属性
2.如何添加事务属性
@Transactional(isolation = , propagation = , readOnly = , timeout = , rollbackFor = , noRollbackFor = )
3.事务属性详解
3.1.隔离属性(isolation)
1.隔离属性的概念:它描述了事务解决并发问题的特征
- 什么是并发:多个事务、用户,
在同一时间段(0.00001s,很小的时间段)
,访问操作了相同的数据 - 并发会产生哪些问题:
1.脏读
2.不可重复读
3.幻影读 - 并发问题如何解决?
通过隔离属性解决,隔离属性中设置不同的值,解决并发处理过程中的问题。
2.事务并发产生的问题:
-
2.1脏读:一个事物读取了另一个事务中还没有提交的数据 。这些数据在提交前可能会做进一步的修改或者回滚,所以当前事务中的读到的这个数据是脏数据。
-
解决办法:读已提交的数据。
@Transactional(isolation = Isolation.READ_COMMITTED)
-
2.2不可重复度:在一个事务中,多次读取相同的记录但是每次读取的结果不一样。多次读取的间隔中,这些数据可能会被其他用户修改,并提交。这样在一个事务中每次读取的结果不一样,就产生了不可重复度的现象。会另当前用户产生困惑:到底该用哪次结果作为基准来进行后续操作。
-
注意:不是脏读,因为虽然读取的数据不一样,但是都是已提交的正确数据;而且是在一个事务中,如果不在一个事务中,或者间隔时间很长,那么每次读取的结果不一样就是应该的。
扫描二维码关注公众号,回复: 13306619 查看本文章 -
解决:
@Transactional(isolation = Isolation.REPEATABLE_READ)
。本质是数据库底层为读取的这行数据
加上一把行锁,只有在当前用户对这个数据操作完之后,其他用户才能操作这行数据。 -
2.3幻影读:一个事务中,多次对整表进行查询统计,但是发现结果不一样,会在本事务中产生数据不一致的问题。
-
解决方案:
@Transactional(isolation = Isolation.SERIALIZABLE)
,本质上是数据库底层为整表加上一个表锁。 -
2.4隔离属性小结:
并发安全:解决脏读的READ_COMMITTED,只能保证每次读取的都是已提交的数据。无法保证同一事务中多次读取同一个数据的一致性,所以并发安全性最差。安全性最高的加上了表锁的
SERIALIZABLE
,其次是加上了行锁的REPEATABLE_READ
,最差的是READ_COMMITTED
运行效率正好相反:READ_COMMITTED>REPEATABLE_READ>SERIALIZABLE
-
2.5数据库对隔离属性的支持
Oracle不支持REPEATABLE_READ值如何解决不可重复读:采用的是多版本比对的方式 解决不可重复读的问题 -
2.6默认的隔离属性
Spring默认的隔离属性是:ISOLATION_DEFAULT。它会调用不同数据库所设置的默认隔离属性。
MySQL的默认隔离属性 : REPEATABLE_READ
Oracle的默认隔离属性: READ_COMMITTED
-
2.7隔离属性在实战中的建议:
推荐使用Spring指定的默认值ISOLATION_DEFAULT。
未来实战中,并发访问情况很低。所以为了一个小概率的事情,而加各种各样的锁影响效率,这是没有意义的。如果真的遇到了并发问题,优先推荐的方案:乐观锁,它是应用锁,不是物理锁,不会过多的影响我们的效率。
Hibernate(JPA):Version;MyBatis:通过拦截器自定义开发。
3.2事务的传播属性
1.传播属性的概念:它描述了解决事务嵌套问题的特征。
什么是事务的嵌套:指的是一个大的事务中,包含若干小的事务。比如Service层的相互调用。
导致的问题:破坏了最外层事务的原子性。
2.传播属性的值及其用法:@Transactional(propagation = )
中心思想:保证同一时间,只有一个事务。
-
2.1REQUIRED:
@Transactional(propagation = Propagation.REQUIRED)
运用了REQUIRED后,如果当前业务方法的外层不包含外部事务,那么会开启新的事务;如果当前的业务方法的外层存在了一个事务,业务方法就会融合到外部事务中,不开启新的事务。
-
一般为增删改方法加上REQUIRED这个传播属性
-
2.2SUPPORTS:业务方法外部不存在事务,不开启事务;外部事务存在事务,融合到外部的事务。就是没有事务呗,这样都不开启呗。
-
所以一般用在查询的方法:查询不需要事务。
-
2.3默认的传播属性:
一般而言:REQUIRED和SUPPORTS能满足我们99%的业务情况了。Spring提供的默认传播属性是REQUIRED
-
2.4传播属性推荐的使用方式:
增删改方法:直接使用默认值REQUIRED
查询方法:指定传播属性为SUPPORTS -
2.5REQUIRES_NEW:
思考这样一个情况,如过一个save操作失败了发生回滚,那么这次save操作要不要被日志记录下来?
显然,即使是一次失败的操作也要被记录下来。所以save操作被事务控制,save方法中要调用日志记录方法log,且log方法也被事务控制。如果log方法的传播属性是REQUIRED,那么log事务就不开启,而是融入到save事务中。那么当外层的控制save事务回滚的话,log方法也会回滚。没法记录下这次的save操作。
所以log方法的事务要独立出来,而且又要保证同一时间只能有一个事务,所以先挂起外部的save事务,开启log的事务,等待log事务结束后,在运行save的事务!
REQUIRES_NEW
:如果没有外部事物,开启本身的事物;如果有外部事物,先挂起外部事务,运行本身的事务,待本身的事务结束后,再运行外部的事务。经常用在日志的记录中。 -
2.6后面的3个事务传播属性极其不常用。
3.3事务的只读属性(readOnly)
-
针对于只进行查询操作的业务方法,可以加入只读属性,提高运行效率。
-
为了保证并发安全,会加上各种各样的锁;将并行执行的方式,变成串行的执行方式,影响效率;当我们确定这个方法是只进行查询操作的话,我们为其加上只读属性,那么就不会为其加上各种各样的锁了,提高运行效率。
- 默认值是false:不是只读;所以如果确定一个方法只有查询操作,要显示的指定只读属性为true。
3.4.事务的超时时间属性
-
指定了事务等待的最长时间。
-
为什么事务会进行等待:当前事务访问数据时,有可能访问的数据被别的事务进行了加锁处理,那么此时本事务就必须进行等待。
-
如何设置:@Transactional(timeout=2)。单位:秒,超过时间后,就会抛出异常。
-
超时属性的默认值:-1。意为由对应的数据库来指定
3.5.事务的异常属性
1.Spring事务处理过程中:
- 默认对于RuntimeException及其子类,采用的是回滚的策略
- 默认对于Exception及其子类,采用的是提交的策略(检查异常会抛出来,但是更新操作会正常进行事务提交)。
2.怎么改变这种策略:
- 想要让遇到检查时异常Exception及其子类,也进行回滚:
@Transactional(rollbackFor = java.lang.Exception.class)
- 让遇到运行时异常时,也能提交(不会滚):
@Transactional(rnoRollbackFor = {java.lang.RuntimeException.class})
@Transactional(rollbackFor = {java.lang.Exception.class},noRollbackFor = {java.lang.RuntimeException.class})
- 实战中一般使用Spring的默认事务异常属性。
4.事务属性常见配置总结
1. 隔离属性 默认值
2. 传播属性 Required(默认值) 增删改;Supports 查询操作
3. 只读属性 readOnly false-->增删改;true-->查询操作
4. 超时属性 默认值 -1
5. 异常属性 默认值
增删改操作 @Transactional
查询操作 @Transactional(propagation=Propagation.SUPPORTS,readOnly=true)
5.基于标签的事务配置方式(事务开发的第二种形式)
5.1基于@Transactional注解的事务开发回顾
<bean id="userService" class="com.baizhiedu.service.UserServiceImpl">
<property name="userDAO" ref="userDAO"/>
</bean>
<!--DataSourceTransactionManager-->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
@Transactional(isolation=,propagation=,...)
public class UserServiceImpl implements UserService {
private UserDAO userDAO;
<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>
5.2基于标签的事务配
<bean id="userService" class="com.baizhiedu.service.UserServiceImpl">
<property name="userDAO" ref="userDAO"/>
</bean>
<!--DataSourceTransactionManager-->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--事务属性 -->
<tx:advice id="txAdvice" transacation-manager="dataSourceTransactionManager">
<tx:attributes>
<tx:method name="register" isoloation="",propagation=""></tx:method>
<tx:method name="login" .....></tx:method>
<!--
等效于
@Transactional(isolation=,propagation=,)
public void register(){
}
-->
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="pc" expression="execution(* com.baizhiedu.service.UserServiceImpl.register(..))"></aop:pointcut>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pc"></aop:advisor>
</aop:config>
5.3每个方法都要进行配置:太冗余
编程时候 service中负责进行增删改操作的方法 都以modify开头
查询操作 命名无所谓
<tx:advice id="txAdvice" transacation-manager="dataSourceTransactionManager">
<tx:attributes>
<tx:method name="register"></tx:method>
<tx:method name="modify*"></tx:method>
<tx:method name="*" propagation="SUPPORTS" read-only="true"></tx:method>
</tx:attributes>
</tx:advice>
应用的过程中,service放置到service包中
<aop:config>
<aop:pointcut id="pc" expression="execution(* com.baizhiedu.service..*.*(..))"></aop:pointcut>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pc"></aop:advisor>
</aop:config>