Spring5学习随笔-事务属性详解(@Transactional)
学习视频:【孙哥说Spring5:从设计模式到基本应用到应用级底层分析,一次深入浅出的Spring全探索。学不会Spring?只因你未遇见孙哥】
第三章、Spring的事务处理
1.什么是事务?
事务是保证业务操作完整性的一种数据库机制
事务的4特点:ACID
- A 原子性
- C 一致性
- I 隔离性
- D 持久性
2.如何控制事务
JDBC:
Connection.setAutoCommit(false)
Connection.commit();
Connection.rollback();
Mybatis:
Mybatis自动开启事务
SqlSession(底层还是Connection).commit();
sqlSession(底层还是Connection).rollback();
结论:控制事务的底层 都是Connection对象完成的
3.Spring控制事务的开发
Spring是通过AOP的方式进行事务开发
-
原始对象
public class XXXUserServiceImpl{ 1.原始对象 ---> 原始方法 --->核心功能(业务处理+DAO调用) 2.DAO作为Service的成员变量,依赖注入的方式进行赋值 }
-
额外功能
下面的额外功能封装在org.springframework.jdbc.datasource.**DataSourceTransactionManager 应用的过程需要注入DataSource** 1.MethodInterceptor public Object invoke(MethodInvocation invocation){ try{ Connection.setAutoCommit(false); Object ret = invocation.proceed(); Connection.commit(); return ret; }catch(Exception e){ Connection.rollback(); } } 2. @Aspect @Around
-
切入点
@**Transactional** 事务的额外功能能加入给那些业务方法 1. 类上:类中所有的方法都会加入事务 2. 方法上:这个方法加入事务
-
组装切面
- 切入点
- 额外功能
<tx:annotation-driven transaction-manager=””/>
4.Spring控制事务的编码
- 搭建开发环境(jar)
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.1.14.RELEASE</version>
</dependency>
- 编码
<!-- 1. 原始对象-->
<bean id="userService" class="com.baizhi.service.UserServiceImpl">
<property name="userDao" ref="userDao"/>
</bean>
<!-- 2. 额外功能-->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
3.切入点
@Transactional
public class UserServiceImpl implements UserService {
private UserDao userDao;
<!--
4.组装
选择结尾是/tx的driven
-->
<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>
-
细节
<tx:annotation-driven transaction-manager="dataSourceTransactionManager" proxy-target-class="true"/>
进行动态代理底层实现的切换 proxy-target-class
默认false:JDK true: Cglib
第四章、Spring中的事务属性(Transaction Attribute)
1.什么是事务属性
属性:描述物体特征的一系列值
性别 身高 体重…
所谓的事务属性指的是描述事务特征的一系列值
- 隔离属性
- 传播属性
- 只读属性
- 超时属性
- 异常属性
2.如何添加事务属性
@Transactional(isolation(隔离属性)=,propagation(传播属性)=,readOnly(只读属性)=,timeout(超时属性)=,rollbackFor=,noRollbackFor=);
3.事务属性详解
1.隔离属性(ISOLATION)
-
隔离属性的概念
概念:他描述了事务解决并发问题的特征
-
什么是并发
指的是多个事务(用户)在同一时间,访问操作了相同的数据
重点 同一时间:微小的时间内 0.0000几秒 微小前、后
-
并发会产生那些问题
- 脏读
- 不可重复读
- 幻影读
-
并发问题如何解决
通过隔离属性解决,隔离属性设置不同的值,解决并发处理过程中的问题。
-
-
事务并发产生的问题
-
脏读
指的是一个事务/用户读取了另外一个事务/用户中没有提交的数据。会在本事务中产生不一致的问题
解决方案:@Transactional(isolation=Isolation.READ.COMMITTED)
本质:只读取已提交的数据
-
不可重复读
一个事务中,多次读取相同的数据,但是读取结果不一样,会在本事务中产生数据不一致的问题
注意:1.不是脏读 2.在一个事务中
解决方案:@Transactional(isolation=Isolation.REPEATABLE_READ)
本质:对应数据库底层的行锁(第一个人在读取数据时,其他人都得等第一个人处理完)
-
幻影读
一个事务中,多次对整表进行查询统计,但是结果不一样,会在本事务中产生数据不一致的问题。
解决方案:@Transactional(isolation=Isolation.SERIALIZABLE)
本质对应数据库底层的表锁
-
总结
并发安全:SERIALIZABLE>REPEATABLE_READ>READ.COMMITTED
运行效率:READ.COMMITTED>REPEATABLE_READ>SERIALIZABLE
-
-
数据库对于隔离属性的支持
隔离属性的值 MySQL Oracle Isolation.READ.COMMITTED ✔️ ✔️ Isolation.REPEATABLE_READ ✔️ ❌ Isolation.SERIALIZABLE ✔️ ✔️ Oracle不支持REPETABLE_READ值 采用的是多版本比对的方式 解决不可重复读的问题
-
默认隔离属性
ISOLATION_DEFAULT:会调用不同数据库所设置的默认隔离属性
例如:如果使用MYSQL 默认隔离属性:REPEATABLE_READ,Oracle:READ_COMMITTED
- 查看数据库默认隔离属性
-
MySQL
8.0版本以前:select @@tx_isolation 8.0版本之后:select @@transaction_isolatio
-
- 查看数据库默认隔离属性
-
隔离属性在实战中的建议
推荐使用Spring指定的ISOLATION——DEFALUT
- MySQL repeatbale_read
- Oracle READ_COMMITED
未来中的实战中,并发访问情况 很低(前提就是海量用户)
如果真遇到并发问题,可以使用乐观锁
Hibernate(JPA) Version
MyBatis 通过拦截器自定义开发
2.传播属性(PROPAGATION)
-
传播属性的概念
概念:他描述了事务解决嵌套问题的特征
什么叫做事物的嵌套:他指的是一个大的事务中,包含若干个小的事务
问题:大事务中融入了很多小的事务,他们彼此影响,最终导致外部大的事务,丧失了事务的原子性
-
传播属性的值及其用法
不管属性是什么,其中心思想是保证同一时间只会有一个事务的存在
传播属性的值 外部不存在事务 外部存在事务 用法 备注 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 极其不常用
融合指的是:放弃自己的事务,以外部的事务为准
-
默认的传播属性
REQUIRED是传播属性的默认值
-
推荐传播属性的使用方式
增删改 方法:直接使用默认值REQUIRED
查询 操作:显示指定传播属性的值SUPPORTS
3.只读属性(readOnly)
针对于只进行查询操作的业务方法,可以加入只读属性,提供运行效率
默认值:false
4.超时属性(timeout)
指定了事务等待的最长时间
-
为什么事务要进行等待呢?
因为在当前事务访问数据时,有可能访问的数据是被别的事务进行加锁的处理,那么此时本事务就必须进行等待
-
等待时间:秒
-
如何应用 @Transactional(timeout=2)
-
超时属性的默认值:-1
最终由对应的数据库来指定默认值,一般使用默认值
5.异常属性
Spring事务处理过程中
默认 对于RuntimeException及其子类 采用的是回滚的策略
默认 对于Exception及其子类 采用的是提交的策略
想要改变默认的策略,可以设置两个属性
- rollbackFor(回滚) ={java.lang.Exception,xxx,……};
- noRollbackFor(不回滚)=
@Transactional(rollbackFor = {Exception.class},noRollbackFor = {RuntimeException.class})
建议:实战中使用RuntimeException及其子类 使用事务异常属性的默认值,实际开发很少改变策略
4.事务属性常见配置总结
- 隔离属性 默认值
- 传播属性 Required(默认值)增删改 Supports 查询操作
- 只读属性 readOnly false 增删改 查询用true
- 超时属性 默认值:-1
- 异常属性 默认值
后续实战当中的建议:增删改操作 @Transactional
而对于查询操作 @Transactional(propagation=Propagation.SUPPORTS,readOnly=true)
5.基于标签的事务配置方式(事务开发的第二种形式)
- 基于注解 @Transaction的事务配置回顾
<!-- 1. 原始对象-->
<bean id="userService" class="com.baizhi.service.UserServiceImpl">
<property name="userDao" ref="userDao"/>
</bean>
<!-- 2. 额外功能-->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
3.切入点
@Transactional
public class UserServiceImpl implements UserService {
private UserDao userDao;
<!--
4.组装
选择结尾是/tx的driven
-->
<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>
- 基于标签的事务配置
<!-- 1. 原始对象-->
<bean id="userService" class="com.baizhi.service.UserServiceImpl">
<property name="userDao" ref="userDao"/>
</bean>
<!-- 2. 额外功能-->
<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" isolation="DEFAULT" propagation=""/>
<tx:method name="login"...../>
</tx:attributes>
</tx:advice>
<!-- 第四步 指定切入点-->
<aop:config>
<aop:pointcut id="pc" expression="execution(* com.baizhi.service.UserServiceImpl.register(..))"/>
<aop:advisor advice-ref="" pointcut-ref="">
</aop:config>
-
基于标签的事务配置在实战中的应用方式
<!-- 1. 原始对象--> <bean id="userService" class="com.baizhi.service.UserServiceImpl"> <property name="userDao" ref="userDao"/> </bean> <!-- 2. 额外功能--> <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> **编程的时候 service 负责进行增删改操作的方法 都以modify开头 查询操作 命名无所谓 用*号代替 可以将特殊事务的事务放在前面** <tx:advice id="txAdvice" transacation-manager="dataSourceTransactionManager"> <tx:attributes> <tx:method name="register" /> <tx:method name="modify*"/> <tx:method name="*" propagation="SUPPORTS" read-only="true"/> </tx:attributes> </tx:advice> <!-- 第四步 指定切入点--> 使用包切入点,应用的过程中 service放置到service包中 <aop:config> <aop:pointcut id="pc" expression="execution(* com.baizhi.service..*.*(..))"/> <aop:advisor advice-ref="" pointcut-ref=""> </aop:config>
作者:扬眉剑出鞘
出处: https://www.cnblogs.com/eyewink/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。