原文博客:https://blog.csdn.net/jiadajing267/article/details/81056057

事务的特性

事务的特性:acid

  • atomicity:原子性,保证事务的一些列动作要么全部完成,要么全部回滚
  • consistency:一致性,隔离执行事务时(即在没有其他事务并执行的情况下)保持数据库的一致性
  • isolation:隔离性,多个事务之间不应该相互影响
  • durability:持久性,事务结果被写到存储器中

Spring 的事务管理

Spring 支持编程式事务和声明式事务管理

  • 编程式事务管理:将事务管理代码嵌到业务中来控制事务的提交和回滚,缺点是必须在每个事务操作业务逻辑中包含额外的事务管理代码
  • 声明式事务管理:将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理,即将事务管理作为横切关注点,通过 aop 方法模块化;Spring 中通过 Spring AOP 框架支持声明式事务管理

Spring 的事务管理器:Spring的核心事务管理抽象,管理封装了一组独立于技术的方法,无论使用哪种事务管理策略(编程式或声明式),事务管理器都是必须的

  1. org.springframework.transaction.PlatformTransactionManager
  2. <!-- jdbc事务 -->
  3. <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  4. <property name="dataSource" ref="dataSource" />
  5. </bean>
  6. <!-- hibernate事务 -->
  7. <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
  8. <property name="sessionFactory" ref="sessionFactory" />
  9. </bean>
  10. <!-- jpa事务 -->
  11. <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
  12. <property name="sessionFactory" ref="sessionFactory" />
  13. </bean>
  14. <!-- java原生api事务:通常用于跨越多个事务管理源(多数据源) -->
  15. <bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
  16. <property name="transactionManagerName" value="java:/TransactionManager" />
  17. </bean>

Spring 事务属性

Spring 事务属性:

  • 传播行为
  • 隔离规则
  • 回滚规则
  • 事务超时
  • 是否只读?

传播行为

事务的传播行为:事务被另一个事务方法调用时,必须指定事务应该如何传播。例如方法可能继续在现有事务中运行,也可能开启一个新的事务,并在自己的事务运行。spring中的事务传播行为可由传播属性 Propagation 指定

REQUIRED 持当前事务,如果当前没有事务,就新建一个事务,这是最常见的选择
REQUIRED_NEW 当前方法必须启动新事务,并在自己的事务内运行,如果有事务正在运行,则将它挂起
SUPPORTS 如果有事务在运行,当前的方法就在这个事务内运行,否则可以不运行在事务中
NOT_SUPPORTED
表示该方法不应该运行在事务中。如果存在当前事务,在该方法运行期间,当前事务将被挂起。如果使用 JTATransactionManager 的话,则需要访问 TransactionManager
MANDATORY 当前的方法必须运行在事务内部,如果没有正在运行的事务,就会抛出异常
NEVER 当前方法不应该运行在事务中,如果有运行的事务,就抛出异常
NESTED 如果有事务在运行,当前的方法就应该在这个事务的嵌套事务内运行。嵌套的事务可以独立于当前事务进行单独地提交或回滚。如果当前事务不存在,那么其行为与 PROPAGATION_REQUIRED 一样

事务的隔离级别

事务隔离级别:并发事务会导致发生以下三种类型的问题

脏读 发生在一个事务读取了另一个事务改写尚未提交的数据时,改写的数据被回滚了,那么第一个事务获取的数据无效
不可重复读 当同一个事务执行两次及以上相同的查询时,每次都得到不同的数据。一般因为另一并发事务在两次查询期间进行了更新
幻读 第一个事务读取了一些数据,此时第二个事务在该表中插入了一些新数据,这是第一个事务再读取相同的数据就会多几行

不可重读读和幻读的区别:

  • 不可重复读侧重点在相同数据被修改
  • 幻读是删除或新增

Spring 中事务的隔离级别可以通过隔离属性指定:

DEFAULT 使用底层数据库的默认隔离级别,大部分数据库,默认隔离级别都是READ_COMMITED
READ_COMMITED 只允许事务读取已经被其他事务提交的更改,可以避免脏读,但不可重复读和幻读问题仍然可能出现
READ_UNCOMMITED 允许事务读取未被其他事务提交的更改。脏读,不可重复读,幻读都可能会出现
REPEATABLE_READ 确保事务可以多次从一个字段中读取相同的值。在这个事务持续期间,禁止其他事务对这个字段进行更新,可以避免脏读和不可重复读,但是幻读的问题依然存在
SERIALIZABLE 确保事务可以从一个表中读取相同的行,在这个事务持续期间,禁止其他事务对该表执行插入,更新,删除。所有的并发问题都能避免,但是性能比较低。

注意:事务的隔离级别需要底层数据库引擎的支持,而不是应用程序或框架的支持

  • Oracle 支持 2 种事务隔离级别:READ_COMMITED、SERIALIZABLE
  • MySQL 支持 4 种事务隔离级别

回滚规则

回滚规则:默认情况下只有未检查异常(RuntimeException 和 Error 类型的异常)会导致事务回滚,事务回滚规则可以属性管理

  • rollbackFor:遇到时必须进行回滚
  • noRollbackFor:一组异常类,遇到时必须不能回滚
    1. rollbackFor={IOException.class , SQLException.class},
    2. noRollbackFor=ArithmeticException.class

在 service 层中进行事务处理时,如果相关方法对异常进行了捕获或拦截,会导致 spring aop 无法捕获到异常,因此会导致事务无法回滚

  • 因此在进行事务处理时,一般不要使用 try … catch … 捕获异常
  • 如果进行了异常处理,则可以在 catch 语句中进行手动回滚事务或者继续抛出 runtimeException ```java // 手动回滚 try { userDao.save(user); } carch (Exception e){ // 对当前事务进行回滚 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    }

// 或者继续抛出 runtimeException 异常,在 Controller 层进行同一异常处理 try { userDao.save(user); } carch (Exception e){ throw new RuntimeException(“xxx”);
}

  1. **
  2. <a name="DgCbS"></a>
  3. #### 只读属性
  4. 只读属性:如果事务只读数据但不修改可以通过配置只读事务属性,帮助数据库引擎优化事务
  5. - 只读事务属性:表示这个事务只读读取数据,但是不更新数据
  6. - @Transactional(readOnly = true)
  7. <a name="HWGrH"></a>
  8. #### 超时事务属性
  9. 超时事务属性:
  10. - 事务可以在行和表上获得锁,因此长事务会占用资源,并对整体性能产生影响
  11. - 可以配置超时事务属性,事务在强制回滚之前可以保持多久,可以避免长期运行的事务占用资源
  12. <a name="IgC6h"></a>
  13. ### Spring事务管理配置
  14. 1、使用 spring-context.xml 进行配置:
  15. ```xml
  16. <!-- 1. 配置事务管理器 -->
  17. <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  18. <property name="dataSource" ref="dataSource"></property>
  19. </bean>
  20. <!-- 2. 配置事务属性 -->
  21. <!--<tx:advice>元素声明事务通知-->
  22. <tx:advice id="txAdvice" transaction-manager="transactionManager">
  23. <tx:attributes>
  24. <!-- 根据方法名指定事务的属性 -->
  25. <tx:method name="*"/>
  26. <!--propagation配置事务传播行为-->
  27. <tx:method name="purchase" propagation="REQUIRES_NEW"/>
  28. <!--isolation配置事务的隔离级别-->
  29. <tx:method name="update*" isolation="SERIALIZABLE"/>
  30. <!--rollback-for配置事务遇到异常必须回滚,no-rollback-for配置事务遇到异常必须不能回滚-->
  31. <tx:method name="add*" rollback-for="java.io.IOException" no-rollback-for="com.dmsd.spring.tx.BookStockException"/>
  32. <!--read-only配置事务只读属性-->
  33. <tx:method name="find*" read-only="true"/>
  34. <!--timeout配置事务的超时属性-->
  35. <tx:method name="get*" timeout="3"/>
  36. </tx:attributes>
  37. </tx:advice>
  38. <!-- 3. 配置事务切入点, 以及把事务切入点和事务属性关联起来 -->
  39. <aop:config>
  40. <aop:pointcut expression="execution(* com.atguigu.spring.tx.xml.service.*.*(..))"
  41. id="txPointCut"/>
  42. <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
  43. </aop:config>

2、使用 @Transactional 注解声明式地管理事务,同时使用 spring-context.xml 配置重要属性

  • Spring中注解的方式 @Transactional 标注事务方法
  • 为了将方法定义为支持事务处理,可以在方法上添加@Transactional注解
  • 根据Spring AOP基于代理机制,只能标注公有方法
  • 如果在类上标注@Transactional注解,那么这个类中所有公有方法都会被定义为支持事务
    1. <!-- 配置事务管理器 -->
    2. <bean id="transactionManager"
    3. class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    4. <property name="dataSource" ref="dataSource"></property>
    5. </bean>
    6. <!-- 启用事务注解 -->
    7. <tx:annotation-driven transaction-manager="transactionManager"/>
    在java类的方法上添加: ```java //添加事务注解 //1.使用 propagation 指定事务的传播行为, 即当前的事务方法被另外一个事务方法调用时如何使用事务 // REQUIRED:默认设置,即使用调用方法的事务 // REQUIRES_NEW: 事务自己的事务, 调用的事务方法的事务被挂起. //2.使用 isolation 指定事务的隔离级别, 最常用的取值为 READ_COMMITTED //3.默认情况下 Spring 的声明式事务对所有的运行时异常进行回滚. 也可以通过对应的属性进行设置. 通常情况下去默认值即可. //4.使用 readOnly 指定事务是否为只读. 表示这个事务只读取数据但不更新数据, 这样可以帮助数据库引擎优化事务. 若真的事一个只读取数据库值的方法, 应设置 readOnly=true //5.使用 timeout 指定强制回滚之前事务可以占用的时间. @Transactional(propagation=Propagation.REQUIRES_NEW,
    1. isolation=Isolation.READ_COMMITTED,
    2. noRollbackFor={UserAccountException.class},
    3. rollbackFor = IOException.class,
    4. readOnly=false,
    5. timeout=3)
    @Override public void purchase(String username, String isbn) {}
  1. 3、使用 Java Config 进行配置:
  2. ```java
  3. @Configuration
  4. @PropertySource(value = {"classpath:db.properties"})
  5. @ComponentScan({"org.example"})
  6. @Import(DataSourceConfig.class)
  7. @EnableTransactionManagement // 开启事务注解功能,等同于<tx:annotation-driven>
  8. public class DemoConfig {
  9. @Autowired
  10. private DataSource dataSource;
  11. @Bean
  12. public JdbcTemplate jdbcTemplate() {
  13. JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
  14. return jdbcTemplate;
  15. }
  16. // 配置事务管理器
  17. @Bean
  18. public PlatformTransactionManager transactionManager() {
  19. return new DataSourceTransactionManager(dataSource);
  20. }
  21. }

事务失效

因为spring事务是基于aop的代理机制,当方法中调用 this 本身的方法时候即使在 this 的方法标明事务注解,但是事务注解会失效。如下

  1. @Transactional
  2. @Override
  3. public void purchase(String username, String isbn) {
  4. this.update(username, isbn);
  5. }
  6. @Transactional
  7. public void update(String username, String isbn) {
  8. //1. 获取书的单价
  9. int price = bookShopDao.findBookPriceByIsbn(isbn);
  10. //2. 更新数的库存
  11. bookShopDao.updateBookStock(isbn);
  12. //3. 更新用户余额
  13. bookShopDao.updateUserAccount(username, price);
  14. }

原因:因为调用 this 本身方法不走代理机制,这个时候可以通过配置解决这个问题
**
解决上述原因造成的事务失效:
1、在配置中添加如下内容

  1. <!--开启aspectj代理,并暴露aop代理到ThreadLocal-->
  2. <aop:aspectj-autoproxy expose-proxy="true"/>

2、在上述调用的地方改成如下调用

  1. @Transactional
  2. @Override
  3. public void purchase(String username, String isbn) {
  4. ((BookShopServiceImpl)AopContext.currentProxy()).update(username, isbn);
  5. }