• Spring 声明式事务


    Spring 声明式事务

    spring中对事务的控制有声明式事务编程式事务

    声明式事务是使用 AOP 进行织入, 而编程式事务是使用代码来实现

    关于编程式事务, 这里不进行介绍, 可查看官网:http://mybatis.org/spring/zh/transactions.html#programmatic

    我主要说声明式事务.

    当然, 如果你是用的springboot项目, 你不需要任何配置, 直接使用注解即可: 基于@Transactional注解的实现

    图解:

    回顾事务

    • 事务在项目开发过程非常重要,涉及到数据的一致性的问题,不容马虎!
    • 事务管理是企业级应用程序开发中必备技术,用来确保数据的完整性和一致性。

    是否还记得在JDBC中使用事务: 点击跳转到JDBC使用事务

    事务四个ACID特性

    • Atomicity: 原子性 事务中所有操作是不可再分割的原子单位。事务中所有操作要么全部执行成功,要么全部执行失败。
    • Consistency: 一致性 事务执行后,数据库状态与其它业务规则保持一致。如转账业务,无论事务执行成功与否,参与转账的两个账户金额之和在事务前后应该是保持不变的。
    • Isolation: 隔离性,隔离性是指在并发操作中,不同事务之间应该隔离开来,使每个并发中的事务不会相互干扰。也就是说,在事中务查看数据更新时,数据所处的状态要么是另一事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看到中间状态的数据。
    • Durability: 持久性,一旦事务提交成功,事务中所有的数据操作都必须被持久化到数据库中,即使提交事务后,数据库马上崩溃,在数据库重启时,也必须能保证通过某种机制恢复数据。

    下面我们来正式开始使用事务

    基于XML配置文件实现

    1. 前提准备

    spring已经整合了mybatis, 并导入了向关依赖, 如jdbc, mybatis, juntl, spring等

    2. service示例(接口略)

    我们假设第一条对数据库的插入操作成功, 第二条对数据库的更改失败

    @Service
    public class UserServiceImpl implements UserService {
    	
        // 注入dao层
        @Resource
        private UserDao userDao;
    
        // 业务方法
        @Override
        public void change() {
            // 使用dao层插入数据
            User user = new User(null, "root", "122", "54627946@qq.com");
            userDao.insertUsers(user);
            
            // 更改一条数据, 这里已经把sql语句故意写错了, 目的是为了执行更改的时候排除异常
            User user2 = new User(3, "sys", "1232", "54665885@qq.com");
            userDao.updateUser(user2);
        }
    }
    

    3. 配置文件

    3.1 jdbc事务

    <bean id="transactionManager"
          	class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    

    3.2 配置事务的通知

    <!--配置事务的通知-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <!--
            <tx:method name="insertUser" propagation="REQUIRED"/>
            <tx:method name="updateUser" propagation="REQUIRED"/>
            -->
            <!--这里使用*号是配置service中的所有方法, 就可以省略上面每个方法都要声明了-->
            <!--propagation属性为事务的传播特性, 默认就是REQUIRED, 可省略-->
            <tx:method name="*" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>
    

    关于<tx:method>标签的详细内容[点击跳转]

    3.3 AOP织入事务

    <!--AOP织入事务-->
    <aop:config>
        <aop:pointcut id="txPointcut" expression="execution(* com.aaron.service.*.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
    </aop:config>
    

    4. 测试

    @Test
    public void test3() {
        userService.change();
    }
    

    测试结果, 抛出异常, 数据库中没有插入数据

    假设我们跳过第三步, 直接进行测试, 虽然抛出了异常, 但是数据还是插入了

    tx:method标签属性

    关于<tx:method>标签的属性, 简介如下

    属性 类型 默认值 说明
    propagation Propagation枚举 REQUIRED 事务传播属性
    isolation isolation枚举 DEFAULT(所用数据库默认级别) 事务隔离级别
    readOnly boolean boolean boolean
    timeout int -1 超时(秒), 超时后抛出异常
    rollbackFor Class[] {} 需要回滚的异常类
    即遇到哪些异常回滚
    包括异常的子类
    rollbackForClassName String[] {} 需要回滚的异常类名
    noRollbackFor Class[] {} 不需要回滚的异常类
    即遇到哪些异常不回滚<br?包括异常子类
    noRollbackForClassName String[] {} 不需要回滚的异常类名
    • readOnly
      事务属性中的readOnly标志表示对应的事务应该被最优化为只读事务。如果值为true就会告诉Spring我这个方法里面没有insert或者update,你只需要提供只读的数据库Connection就行了,这种执行效率会比read-write的Connection高,所以这是一个最优化提示。在一些情况下,一些事务策略能够起到显著的最优化效果,例如在使用Object/Relational映射工具(如:Hibernate或TopLink)时避免dirty checking(试图“刷新”)。

    • timeout
      在属性中还有定义“timeout”值的选项,指定事务超时为几秒。一般不会使用这个属性。在JTA中,这将被简单地传递到J2EE服务器的事务协调程序,并据此得到相应的解释。

    • Isolation Level(事务隔离等级)的5个枚举值
      为什么事务要有Isolation Level这个属性?先回顾下数据库事务的知识:

      1. 第一类丢失更新(lost update):在完全未隔离事务的情况下,两个事物更新同一条数据资源,某一事物异常终止,回滚造成第一个完成的更新也同时丢失。
      2. 第二类丢失更新(second lost updates):是不可重复读的特殊情况,如果两个事务都读取同一行,然后两个都进行写操作,并提交,第一个事务所做的改变就会丢失。
      3. 脏读(dirty read):如果第二个事务查询到第一个事务还未提交的更新数据,形成脏读。因为第一个事务你还不知道是否提交,所以数据不一定是正确的。
      4. 虚读(phantom read):一个事务执行两次查询,第二次结果集包含第一次中没有或者某些行已被删除,造成两次结果不一致,只是另一个事务在这两次查询中间插入或者删除了数据造成的。
      5. 不可重复读(unrepeated read):一个事务两次读取同一行数据,结果得到不同状态结果,如中间正好另一个事务更新了该数据,两次结果相异,不可信任。

      当遇到以上这些情况时我们可以设置isolation下面这些枚举值:
      DEFAULT:采用数据库默认隔离级别
      SERIALIZABLE:最严格的级别,事务串行执行,资源消耗最大;
      REPEATABLE_READ:保证了一个事务不会修改已经由另一个事务读取但未提交(回滚)的数据。避免了“脏读取”和“不可重复读取”的情况,但是带来了更多的性能损失。
      READ_COMMITTED:大多数主流数据库的默认事务等级,保证了一个事务不会读到另一个并行事务已修改但未提交的数据,避免了“脏读取”。该级别适用于大多数系统。
      READ_UNCOMMITTED:保证了读取过程中不会读取到非法数据。隔离级别在于处理多事务的并发问题。

    • propagation属性, 事务传播特性

      • 取值:(REQUIRED)propagation_requierd:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,这是最常见的选择, 也是spring中默认的选择

      • 取值:(SUPPORTS)propagation_supports:支持当前事务,如果没有当前事务,就以非事务方法执行。

      • 取值:(MANDATORY)propagation_mandatory:使用当前事务,如果没有当前事务,就抛出异常。

      • 取值:(REQUIRES_NEW)propagation_required_new:新建事务,如果当前存在事务,把当前事务挂起。

      • 取值:(NOT_SUPPORTED)propagation_not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

      • 取值:(NEVER)propagation_never:以非事务方式执行操作,如果当前事务存在则抛出异常。

      • 取值:(NESTED)propagation_nested:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与propagation_required类似的操作

    基于@Transactional注解的实现

    注解的实现非常的简单, 我们不需要任何的配置, 只需要开启驱动, 然后在在service类上加个@Transactional注解即可

    例如:

    @Transactional(readOnly = false,
                   rollbackFor =Throwable.class,
                   timeout =-1,
                   isolation = Isolation.READ_COMMITTED)
    // service实现类...
    

    开启驱动

    配置文件 springboot可不写

    <tx:annotation-driven />
    <bean id="transactionManager"
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>
    

    给service类加注解

    @Service
    @Transactional
    public class UserServiceImpl implements UserService {
    	
        // 注入dao层
        @Resource
        private UserDao userDao;
    
        // 业务方法
        @Override
        public void change() {
            // 使用dao层插入数据
            User user = new User(null, "root", "122", "54627946@qq.com");
            userDao.insertUsers(user);
            
            // 更改一条数据, 这里已经把sql语句故意写错了, 目的是为了执行更改的时候排除异常
            User user2 = new User(3, "sys", "1232", "54665885@qq.com");
            userDao.updateUser(user2);
        }
    }
    

    进行测试后以后抛出事务已经回滚, 就说明成功了, 即数据抛异常后没有插入

    注解属性

    属性也可参考上面的 tx:method标签属性

    属性名 说明
    name 当在配置文件中有多个 TransactionManager , 可以用该属性指定选择哪个事务管理器。
    propagation 事务的传播行为,默认值为 REQUIRED。
    isolation 事务的隔离度,默认值采用 DEFAULT。
    timeout 事务的超时时间,默认值为-1(永不超时)。
    如果超过该时间限制但事务还没有完成,则自动回滚事务。
    read-only 指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。
    rollback-for 用于指定能够触发事务回滚的异常类型,如果有多个异常类型需要指定,各类型之间可以通过逗号分隔。
    no-rollback- for 抛出 no-rollback-for 指定的异常类型,不回滚事务。
  • 相关阅读:
    小程序自定义日历组件
    uni-app 区分环境
    uniapp开发微信小程序获取用户手机号
    flex布局中子元素宽度失效的问题
    使用 VSCode 创建 SpringBoot RESTful 增删改查接口项目并发布
    [译] 如何使用 WebGL 技术进行风力地图可视化
    Cesium Primitive API 实践:绘制一个三角形
    ArcPy 输出路径神坑:不能有短横线
    【问题记录】本地没有更新远程仓库的jar包
    k8s 的pod按照时间排序
  • 原文地址:https://www.cnblogs.com/zpKang/p/13187475.html
Copyright © 2020-2023  润新知