5 - 声明式事务

发布时间:2024年01月15日

传统事务流程:

Connection connection = JdbcUtils.getConnection();
try {
    //1. 先设置事务不要自动提交
    connection.setAutoCommit(false);
    //2. 进行各种 crud
    //多个表的修改,添加 ,删除
    select from 商品表 => 获取价格
    //修改用户余额
    update ...
    //修改库存量
    update
    //3. 提交
    connection.commit();
} catch (Exception e) {
    //4. 回滚
    conection.rollback();
}

使用 Spring 的声明式事务处理, 可以将上面三个子步骤分别写成一个方法,然后统一管理

1. 使用实例

1.1?创建 src\tx_ioc.xml 文件

<!-- 引入外部属性文件 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 配置数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <property name="user" value="${jdbc.userName}"></property>
    <property name="password" value="${jdbc.password}"></property>
    <property name="driverClass" value="${jdbc.driverClass}"></property>
    <property name="jdbcUrl" value="${jdbc.url}"></property>
</bean>
<!-- 配置 JdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <!-- 将上面的数据源分配给 jdbcTemplate -->
    <property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置事务管理器 -->
<bean id="dataSourceTransactionManager"
    class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>
<!-- 开启基于注解的声明式事务功能 -->
<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>
<!-- 加入自动扫描包 dao -->
<context:component-scan base-package="com.hspedu.spring.tx.dao"/>

1.2 修改对象

修改 GoodsService.java,加入声明式事务注解

@Transactional
public void buyGoodsByTx(int user_id, int goods_id, int num) {
    //查询到商品价格
    Float goods_price = goodsDao.queryPriceById(goods_id);
    //购买商品,减去余额
    goodsDao.updateBalance(user_id, goods_price * num);
    //模拟一个异常, 会发生数据库数据不一致现象
    // int i = 10 / 0;
    //更新库存
    goodsDao.updateAmount(goods_id, num);
}

1.3?声明式事务机制

  • 使用@Transactional 可以进行声明式事务控制;即将标识的方法中的,对数据库的操作作为一个事务管理
  • @Transactional 底层使用的仍然是AOP机制
  • 底层是使用动态代理对象来调用buyGoodsByTx

执行流程:

  1. 在执行buyGoodsByTx() 方法 先调用 事务管理器的 doBegin(),主要是将自动提交关掉??
  2. 然后调用 buyGoodsByTx()
  3. 如果执行没有发生异常,则调用 事务管理器的 doCommit()
  4. 如果发生异常 调用事务管理器的 doRollback()

2.?事务的传播机制

当有多个事务处理并存时,如何控制?

2.1 事务的传播机制种类

  • REQUIRED:如果现在有事务在运行,当前的方法就是在这个事务内运行
  • REQUIRED_NEW:当前方法必须新启动一个事务,在自己的事务内运行
  • SUPPORTS:如果现在有事务在运行,当前方法就在这个事务内运行,否则它可以不运行在事务中
  • NOT_SUPPORTE:当前方法不应该运行在事务内,如果有事务,该方法将挂起
  • MANDATORY:当前方法必须运行在事务内,如果没有运行在事务,则抛出异常
  • NEVER:当前方法不可以运行在事务内,如果运行在事务,则抛出异常
  • NESTED:如果现在有事务在运行,当前的方法就应该在这个事务的嵌套事务内运行,否则就启动一个新的事务,并运行在自己的事务里

重点分析?REQUIRED 和 REQUIRED_NEW

1)REQUIED(默认事务传播机制)

2)REQUIRES_NEW?

?3)事务的传播机制的设置方法

?4)例子

如果设置为 REQUIRES_NEW:

buyGoods2 如果错误,不会影响到 buyGoods(),即它们的事务是独立的

如果设置为 REQUIRED:

buyGoods2 和 buyGoods 是一个整体,只要有方法的事务错误,那么两个方法都不会执行成功.!


3.??事务的隔离级别

这个概念参考 MySQL

3.1 说明

默认的隔离级别, 就是 mysql 数据库默认的隔离级别 一般为 REPEATABLE_READ

查看数据库默认的隔离级别:

SELECT @@global.tx_isolation

3.2?事务隔离级别的设置及测试

1)默认

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void buyGoodsByTxISOLATION(int user_id, int goods_id, int num) {
    //查询到商品价格
    Float goods_price = goodsDao.queryPriceById(goods_id);
    System.out.println("第一次读取的价格 =" + goods_price);
    //测试一下隔离级别,在同一个事务中,查询一下价格
    goods_price = goodsDao.queryPriceById(goods_id);
    System.out.println("第二次读取的价格 =" + goods_price);
}

过程:在读完第一次数据后,通过 mysql 进行修改该数据

结果:两次读取到的价格是一样的,不会受到 MySQL?修改影响

2)READ_COMMITTED 隔离级别情况

语法:

?结果:两次读取到的价格是不一样的,数据是会受到 MySQL?修改影响


4.?事务的超时回滚

目的:如果一个事务执行的时间超过某个时间限制,就让该事务回滚

语法:

超出时间会抛出异常,事务回滚,原来的操作撤销?

注:

  • timeout = 2 表示 buyGoodsByTxTimeout 如果执行时间超过了2秒, 该事务就进行回滚
  • 如果没有设置 timeout, 默认是 -1,表示使用事务的默认超时时间,或者不支持

文章来源:https://blog.csdn.net/lin_xiangxiang/article/details/135611243
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。