Spring Transaction 声明式事务管理

Love The Way You Lie 2023-02-20 03:11 180阅读 0赞

Spring Transaction 声明式事务管理

        1. Spring 事务特性
        1. Spring 事务API
        1. Spring 事务传播行为
        1. Spring 事务分类
        • 4.1 编程式事务
        • 4.2 声明式事务
        1. Spring 事务操作转账
        • 5.1 无事务操作
        • 5.2 XML 配置事务
        • 5.3 配置事务通知
        • 5.4 注解 配置事务

LOGO

参考资料:https://lfvepclr.gitbooks.io/spring-framework-5-doc-cn/content/

1. Spring 事务特性

事务 : 是逻辑上一组操作,要么全都成功,要么全都失败。
事务特性 : ACID

  • 原子性 : 事务不可分割
  • 一致性 : 事务执行的前后,数据完整性保持一致
  • 隔离性 : 一个事务执行的时候,不应该受到其他事务的打扰
  • 持久性 : 一旦结束,数据就永久的保存到数据库

如果不考虑隔离性:
脏读 : 一个事务读到另一个事务未提交数据
不可重复读 : 一个事务读到另一个事务已经提交数据(update)导致一个事务多次查询结果不一致
虚读 : 一个事务读到另一个事务已经提交数据(insert)导致一个事务多次查询结果不一致

事务的隔离级别:

  • 未提交读 : 以上情况都有可能发生。
  • 已提交读 : 避免脏读,但不可重复读,虚读是有可能发生。
  • 可重复读 : 避免脏读,不可重复读,但是虚读有可能发生。
  • 串行的 : 避免以上所有情况。

mySQL 数据库存储引擎:innoDB(支持事务)、MyISAM(不支持事务)

2. Spring 事务API

  • PlatformTransactionManager : 平台事务管理器

    • getTransaction(TransactionDefinition definition)
    • rollback(TransactionStatus status)
    • commit(TransactionStatus status)
  • TransactionDefinition : 事务定义

    • ISOLation_XXX:事务隔离级别(isolation)

      • ISOLATION_DEFAULT : 默认级别,Mysql —> repeatable_read | Oracle —>> read_commited
      • ISOLATION_READ_UNCOMMITTED
      • ISOLATION_READ_COMMITTED
      • ISOLATION_REPEATABLE_READ
      • ISOLATION_SERIALIZABLE
    • PROPAGATION_XXX:事务的传播行为
  • TransactionStatus : 事务状态
    是否有保存点
    是否是一个新的事务
    事务是否已经提交

关系 :

PlatformTransactionManager 通过 TransactionDefinition 设置事务相关信息管理事务,管理事务过程中,产生一些事务状态,状态由 TransactionStatus 记录。

API详解:

PlatformTransactionManager : 接口
Spring为不同的持久化框架提供了不同 PlatformTransactionManager 接口实现。

使用Spring JDBC或iBatis 进行持久化数据时使用(★):

  • org.springframework.jdbc.datasource.DataSourceTransactionManager

使用Hibernate进行持久化数据时使用:

  • org.springframework.orm.hibernate.HibernateTransactionManager

使用JPA进行持久化时使用:

  • org.springframework.orm.jpa.JpaTransactionManager

当持久化机制是Jdo时使用:

  • org.springframework.jdo.JdoTransactionManager

使用一个JTA实现来管理事务,在一个事务跨越多个资源时必须使用:

  • org.springframework.transaction.jta.JtaTransactionManager

3. Spring 事务传播行为

不是JDBC事务管理,用来解决实际开发的问题。

传播行为:解决业务层之间调用的事务的关系 (ServiceA.m() 方法与 ServiceB.m() 方法)

  • PROPAGATION_REQUIRED : 支持当前事务,如果不存在 就新建一个

    A,B——如果A有事务,B使用A的事务,如果A没有事务,B就开启一个新的事务(A,B是在一个事务中)

  • PROPAGATION_SUPPORTS : 支持当前事务,如果不存在,就不使用事务

    A,B——如果A有事务,B使用A的事务,如果A没有事务,B就不使用事务

  • PROPAGATION_MANDATORY : 支持当前事务,如果不存在,抛出异常

    A,B——如果A有事务,B使用A的事务,如果A没有事务,抛出异常

  • PROPAGATION_REQUIRES_NEW : 如果有事务存在,挂起当前事务,创建一个新的事务

    A,B——如果A有事务,B将A的事务挂起,重新创建一个新的事务(A,B不在一个事务中,事务互不影响)

  • PROPAGATION_NOT_SUPPORTED : 以非事务方式运行,如果有事务存在,挂起当前事务

    A,B——非事务的方式运行,A有事务,就会挂起当前的事务

  • PROPAGATION_NEVER : 以非事务方式运行,如果有事务存在,抛出异常
  • PROPAGATION_NESTED : 如果当前事务存在,则嵌套事务执行。基于 SavePoint 技术

    A,B——A有事务,A执行之后,将A事务执行之后的内容保存到SavePoint;B事务有异常的话,用户需要自己设置事务提交还是回滚。

  • 常用:
    PROPAGATION_REQUIRED
    PROPAGATION_REQUIRES_NEW
    PROPAGATION_NESTED

4. Spring 事务分类

  • Spring 事务管理
    分层开发:事务处在Service层。
  • Spring 事务管理分成两类

    • 编程式事务管理 : 手动编写代码完成事务管理 (xml配置 + transactionTemplate.execute())
    • 声明式事务管理 : 不需要手动编写代码和配置 (★ @Transactional 注解)

4.1 编程式事务

  • TransactionTemplate

在 doIntransaction 里处理逻辑。如果出异常了,就执行 isRollbackOnly 方法进行回滚。

  1. @Autowired
  2. TransactionTemplate transactionTemplate;
  3. transactionTemplate.execute((TransactionStatus transactionStatus) -> {
  4. try {
  5. //...
  6. } catch (Exception e) {
  7. transactionStatus.isRollbackOnly();
  8. throw e;
  9. }
  10. return null;
  11. });
  • TransactionManager

手动 commit,异常就 rollback

  1. TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
  2. try {
  3. userRepository.save(user);
  4. transactionManager.commit(status);
  5. } catch (Exception e) {
  6. transactionManager.rollback(status);
  7. e.printStackTrace();
  8. }

4.2 声明式事务

Spring 事务的本质是数据库对事务的支持。

支持方式:

  • @EnableTransactionManagement 注解方式,开启对事务注解的解析
  • <tx:annotation-driven /> XML配置方式,开启 spring 对注解事务的支持

5. Spring 事务操作转账

5.1 无事务操作

  1. 创建表

    CREATE TABLE account (

    1. `id` int(11) NOT NULL AUTO_INCREMENT,
    2. `name` varchar(20) NOT NULL,
    3. `money` double DEFAULT NULL,
    4. PRIMARY KEY (`id`)

    ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

    INSERT INTO account VALUES (‘1’, ‘aaa’, ‘1000’);
    INSERT INTO account VALUES (‘2’, ‘bbb’, ‘1000’);

  2. 创建项目,导入依赖



    org.springframework
    spring-context
    5.0.2.RELEASE



    org.springframework
    spring-tx
    5.0.2.RELEASE



    org.aspectj
    aspectjweaver
    1.8.13



    org.springframework
    spring-jdbc
    5.0.2.RELEASE



    com.alibaba
    druid
    1.0.9



    mysql
    mysql-connector-java
    5.1.32



    junit
    junit
    4.12

  3. 创建接口和类 Dao & Service

    // 1.AccountDao
    public interface AccountDao {

    1. // 加钱
    2. void increaseMoney(Integer id, Double money);
    3. // 减钱
    4. void decreaseMoney(Integer id, Double money);

    }

    // 2.AccountDaoImpl
    // 继承 JdbcDaoSupport 可以直接调用父类方法(含了1个 jdbcTemplate)
    public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {

    1. @Override
    2. public void increaseMoney(Integer id, Double money) {
    3. getJdbcTemplate().update("update account set money = money+? where id = ? ", money,id);
    4. }
    5. @Override
    6. public void decreaseMoney(Integer id, Double money) {
    7. getJdbcTemplate().update("update account set money = money-? where id = ? ", money,id);
    8. }

    }

    // 3.AccountService
    public interface AccountService {

    1. //转账方法
    2. void transfer(Integer from,Integer to,Double money);

    }

    // 4.AccountServiceImpl
    public class AccountServiceImpl implements AccountService {

    1. private AccountDao accountDao;
    2. @Override
    3. public void transfer(Integer from, Integer to, Double money) {
    4. // 减钱
    5. accountDao.decreaseMoney(from, money);
    6. // int i = 1 / 0;// 如果发生异常数据(钱)会丢失,数据不一致
    7. // 加钱
    8. accountDao.increaseMoney(to, money);
    9. }
    10. public void setAccountDao(AccountDao accountDao) {
    11. this.accountDao = accountDao;
    12. }

    }

  4. 创建 applicationContext.xml 引入事务 tx 约束

    <?xml version=”1.0” encoding=”UTF-8”?>

    1. <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    2. xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context"
    3. xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
    4. xsi:schemaLocation="http://www.springframework.org/schema/beans
    5. http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
    6. http://www.springframework.org/schema/context
    7. http://www.springframework.org/schema/context/spring-context-4.2.xsd
    8. http://www.springframework.org/schema/aop
    9. http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
    10. http://www.springframework.org/schema/tx
    11. http://www.springframework.org/schema/tx/spring-tx-4.2.xsd ">
    12. <bean name="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    13. <property name="url" value="jdbc:mysql://localhost:3306/数据库名"></property>
    14. <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
    15. <property name="username" value="root"></property>
    16. <property name="password" value="root"></property>
    17. </bean>
    18. <bean name="accountDao" class="com.AccountDaoImpl">
    19. <property name="dataSource" ref="dataSource"></property>
    20. </bean>
    21. <bean name="accountService" class="com.AccountServiceImpl">
    22. <property name="accountDao" ref="accountDao"></property>
    23. </bean>
    24. </beans>

5.测试

  1. public class Demo {
  2. @Test
  3. public void test(){
  4. ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
  5. AccountService accountService = (AccountService)context.getBean("accountService");
  6. accountService.transfer(1, 2, 100d);
  7. }
  8. }

5.2 XML 配置事务

无事务操作时,如果在转账方法中出现异常后,数据前后会产生不一致,此时,我们需要用 Spring 的事务管理来解决这一问题。

手动编码的方式完成事务管理,缺点 : 代码量增加,代码有侵入性

  1. // 修改AccountServiceImpl
  2. public class AccountServiceImpl implements AccountService {
  3. private AccountDao accountDao;
  4. // TransactionTemplate 事务模板类
  5. private TransactionTemplate transactionTemplate;
  6. @Override
  7. public void transfer(Integer from, Integer to, Double money) {
  8. // 事务模板调用 execute 来自动开启、关闭、以及异常时自动回滚操作
  9. transactionTemplate.execute(new TransactionCallbackWithoutResult() {
  10. @Override
  11. protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
  12. // 减钱
  13. accountDao.decreaseMoney(from, money);
  14. int i = 1 / 0; // 如果发生异常数据(钱)不会丢失,数据保持一致
  15. // 加钱
  16. accountDao.increaseMoney(to, money);
  17. }
  18. });
  19. }
  20. public void setAccountDao(AccountDao accountDao) {
  21. this.accountDao = accountDao;
  22. }
  23. public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
  24. this.transactionTemplate = transactionTemplate;
  25. }
  26. }
  27. <!-- applicationContext.xml -->
  28. <bean name="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
  29. <property name="url" value="jdbc:mysql://localhost:3306/mydb" />
  30. <property name="driverClassName" value="com.mysql.jdbc.Driver" />
  31. <property name="username" value="root"></property>
  32. <property name="password" value="root"></property>
  33. </bean>
  34. <!-- TransactionManager 事务核心管理器,封装了所有事务操作,依赖于连接池 -->
  35. <bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  36. <property name="dataSource" ref="dataSource" />
  37. </bean>
  38. <!-- TransactionTemplate 事务模板对象,依赖于事务核心管理器 -->
  39. <bean name="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
  40. <property name="transactionManager" ref="transactionManager" />
  41. </bean>
  42. <bean name="accountDao" class="com.AccountDaoImpl">
  43. <property name="dataSource" ref="dataSource" />
  44. </bean>
  45. <bean name="accountService" class="com.AccountServiceImpl">
  46. <property name="accountDao" ref="accountDao" />
  47. <!-- accountService 注入事务模板对象 -->
  48. <property name="transactionTemplate" ref="transactionTemplate" />
  49. </bean>

此时调用测试类测试,在转账出现异常时,Spring 事务会自动进行回滚,让数据保持一致。

5.3 配置事务通知

修改 applicationContext.xml,配置事务通知是需注意:

企业中配置CRUD方法一般使用 方法名+通配符* 的形式配置通知,此时类中的方法名要和配置的方法名一致。

使用 <tx:advice> <tx:attributes> <tx:method ... /> 配置指定方法的事务通知。

  • 以方法为单位,指定方法应用什么事务属性:

    • isolation:用于指定事务的隔离级别。默认值是 DEFAULT,表示使用数据库的默认隔离级别。
    • propagation:用于指定事务的传播行为。默认值是 REQUIRED,表示一定会有事务,增删改的选择。查询方法可以选择 SUPPORTS。
    • read-only:用于指定事务是否只读。只有查询方法才能设置为true。默认值是false,表示读写。
    • timeout:用于指定事务的超时时间,默认值是-1,表示永不超时。如果指定了数值,以秒为单位。
    • rollback-for:用于指定一个异常,决定是否回滚,当产生该异常时,事务回滚,产生其他异常时,事务不回滚。没有默认值。表示任何异常都回滚。
    • no-rollback-for:用于指定一个异常,决定是否不回滚,当产生该异常时,事务不回滚,产生其他异常时事务回滚。没有默认值。表示任何异常都回滚。

    //localhost:3306/mydb">





    1. <tx:attributes>
    2. <tx:method name="save*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false" />
    3. <tx:method name="persist*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false" />
    4. <tx:method name="update*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false" />
    5. <tx:method name="modify*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false" />
    6. <tx:method name="delete*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false" />
    7. <tx:method name="remove*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false" />
    8. <tx:method name="get*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="true" />
    9. <tx:method name="find*" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="true" />
    10. <tx:method name="transfer" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false" />
    11. </tx:attributes>

    1. <!-- 配置切点表达式 -->
    2. <aop:pointcut expression="execution(* com.qf.service.*ServiceImpl.*(..))" id="txPc" />
    3. <!-- 配置切面 : 通知+切点 advice-ref:通知的名称 pointcut-ref:切点的名称 -->
    4. <aop:advisor advice-ref="txAdvice" pointcut-ref="txPc" />





5.4 注解 配置事务

注解配置(aop)的方式完成事务管理,修改 applicationContext.xml :

<tx:annotation-driven /> 开启 spring 对注解事务的支持。

  1. <bean name="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
  2. <property name="url" value="jdbc:mysql://localhost:3306/mydb" />
  3. <property name="driverClassName" value="com.mysql.jdbc.Driver" />
  4. <property name="username" value="root" />
  5. <property name="password" value="root" />
  6. </bean>
  7. <!-- 事务核心管理器,封装了所有事务操作. 依赖于连接池 -->
  8. <bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  9. <property name="dataSource" ref="dataSource" />
  10. </bean>
  11. <!-- 开启 spring 对注解事务的支持-->
  12. <tx:annotation-driven />
  13. <bean name="accountDao" class="com.AccountDaoImpl">
  14. <property name="dataSource" ref="dataSource" />
  15. </bean>
  16. <bean name="accountService" class="com.AccountServiceImpl">
  17. <property name="accountDao" ref="accountDao" />
  18. </bean>

@Transactional 注解














































属性 类型 描述
value String 事务管理器
propagation Propagation 传播级别
isolation Isolation 隔离级别
readOnly boolean 读/写与只读事务
timeout int 事务超时(秒)
rollbackFor Class 触发事务回滚的类,默认只对未检查异常有效
noRollbackFor Class 设置不需要进行回滚的异常类数组
  • @Transactional 特性

    • 类上添加 @Transactional,在每个方法单开一个事务,管理方式相同。
    • @Transactional 注解只在 public 方法上起作用。
    • 默认只对未检查异常回滚
    • 只读事务只在事务启动时应用,否则即使配置也会被忽略。

修改 AccountServiceImpl :

  1. // 注解添加声明式事务
  2. @Transactional(isolation=Isolation.REPEATABLE_READ,propagation=Propagation.REQUIRED,readOnly=true)
  3. public class AccountServiceImpl implements AccountService {
  4. private AccountDao accountDao;
  5. @Override
  6. //如果该方法与类名上的配置不同,可以单独在这个方法上配置注解,单独生效自己的
  7. @Transactional(isolation= Isolation.REPEATABLE_READ,propagation= Propagation.REQUIRED,readOnly=false)
  8. public void transfer(Integer from, Integer to, Double money) {
  9. // 减钱
  10. accountDao.decreaseMoney(from, money);
  11. int i = 1 / 0;// 如果发生异常数据(钱)不会丢失,数据保持一致
  12. // 加钱
  13. accountDao.increaseMoney(to, money);
  14. }
  15. public void setAccountDao(AccountDao accountDao) {
  16. this.accountDao = accountDao;
  17. }
  18. }

发表评论

表情:
评论列表 (有 0 条评论,180人围观)

还没有评论,来说两句吧...

相关阅读