Spring5学习随笔-事务属性详解(@Transactional)

发布时间 2023-11-21 12:10:44作者: 扬眉剑出鞘

学习视频:【孙哥说Spring5:从设计模式到基本应用到应用级底层分析,一次深入浅出的Spring全探索。学不会Spring?只因你未遇见孙哥】

第三章、Spring的事务处理

1.什么是事务?

事务是保证业务操作完整性的一种数据库机制
事务的4特点:ACID

  1. A 原子性
  2. C 一致性
  3. I 隔离性
  4. D 持久性

2.如何控制事务

JDBC:

Connection.setAutoCommit(false)

Connection.commit();

Connection.rollback();

Mybatis:

Mybatis自动开启事务

SqlSession(底层还是Connection).commit();

sqlSession(底层还是Connection).rollback();

结论:控制事务的底层 都是Connection对象完成的

3.Spring控制事务的开发

Spring是通过AOP的方式进行事务开发

  1. 原始对象

    public class XXXUserServiceImpl{
    	1.原始对象 ---> 原始方法  --->核心功能(业务处理+DAO调用)
    	2.DAO作为Service的成员变量,依赖注入的方式进行赋值
    }
    
  2. 额外功能

    下面的额外功能封装在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
    
  3. 切入点

    @**Transactional**
    事务的额外功能能加入给那些业务方法
    
    1. 类上:类中所有的方法都会加入事务
    2. 方法上:这个方法加入事务
    
  4. 组装切面

    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.什么是事务属性

属性:描述物体特征的一系列值

性别 身高 体重…

所谓的事务属性指的是描述事务特征的一系列值

  1. 隔离属性
  2. 传播属性
  3. 只读属性
  4. 超时属性
  5. 异常属性

2.如何添加事务属性

@Transactional(isolation(隔离属性)=,propagation(传播属性)=,readOnly(只读属性)=,timeout(超时属性)=,rollbackFor=,noRollbackFor=);

3.事务属性详解

1.隔离属性(ISOLATION)

  • 隔离属性的概念

    概念:他描述了事务解决并发问题的特征

    1. 什么是并发

      指的是多个事务(用户)在同一时间,访问操作了相同的数据

      重点 同一时间:微小的时间内 0.0000几秒 微小前、后

    2. 并发会产生那些问题

      1. 脏读
      2. 不可重复读
      3. 幻影读
    3. 并发问题如何解决

      通过隔离属性解决,隔离属性设置不同的值,解决并发处理过程中的问题。

  • 事务并发产生的问题

    • 脏读

      指的是一个事务/用户读取了另外一个事务/用户中没有提交的数据。会在本事务中产生不一致的问题

      解决方案:@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

    1. MySQL repeatbale_read
    2. 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)

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

  1. 为什么事务要进行等待呢?

    因为在当前事务访问数据时,有可能访问的数据是被别的事务进行加锁的处理,那么此时本事务就必须进行等待

  2. 等待时间:秒

  3. 如何应用 @Transactional(timeout=2)

  4. 超时属性的默认值:-1

    最终由对应的数据库来指定默认值,一般使用默认值

5.异常属性

Spring事务处理过程中

默认 对于RuntimeException及其子类 采用的是回滚的策略

默认 对于Exception及其子类 采用的是提交的策略

想要改变默认的策略,可以设置两个属性

  1. rollbackFor(回滚) ={java.lang.Exception,xxx,……};
  2. noRollbackFor(不回滚)=
@Transactional(rollbackFor = {Exception.class},noRollbackFor = {RuntimeException.class})

建议:实战中使用RuntimeException及其子类 使用事务异常属性的默认值,实际开发很少改变策略

4.事务属性常见配置总结

  1. 隔离属性 默认值
  2. 传播属性 Required(默认值)增删改 Supports 查询操作
  3. 只读属性 readOnly false 增删改 查询用true
  4. 超时属性 默认值:-1
  5. 异常属性 默认值

后续实战当中的建议:增删改操作 @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>