• 深入学习Spring框架(四)- 事务管理


    1.什么是事务?

      事务(Transaction)是一个操作序列。这些操作要么都做,要么都不做,是一个不可分割的工作单位,是数据库环境中的逻辑工作单位。事务是为了保证数据库的完整性。例如:A给B转账,需要先减掉A的账户余额再加到B的账户上,这两个操作是一个整体,不可能扣掉A的钱不给B加上,或者只给B加没有扣掉A的钱。也就是,一个事务每项任务都必须正确执行。如果有任一任务执行失败,则整个事务就会被终止。此前对数据所作的任何修改都将被撤销。

    2.Spring事务控制

      JDBC默认有进行事务的处理,但它是在持久层(dao层)处理的,一般情况我们需要把事务处理放到业务层(service层)。因为项目中有可能是几个功能为一个完整的事务,如转账的A余额减少和B余额增加为一个事务,而这在持久层是两个操作。

      spring框架为我们提供了一组事务控制的应用程序接口(API)。使用Spring是事务代理,可以配置一次,不用重复编写事务处理代码。spring的事务控制都是基于AOP的,它既可以使用编程的方式实现,也可以使用配置的方式实现。我们学习的重点是使用配置的方式实现。

    3.数据库并发问题(C)

      数据库的并发问题即多个客户端同时同时访问数据库中某一条数据引发的问题,例如:秒杀抢购。这些问题归结为5类,包括3类数据读问题(脏读,不可重复读,幻读)和2类数据更新问题(第一类丢失更新,第二类丢失更新)。
      第一类更新丢失:
      两个事务更新相同数据,如果一个事务提交,另一个事务回滚,第一个事务的更新会被回滚

      

      第二类丢失更新:
      多个事务同时读取相同数据,并完成各自的事务提交,导致最后一个事务提交会覆盖前面所有事务对数据的改变

      

      

      脏读
      第二个事务查询到第一个事务未提交的更新数据,第二个事务根据该数据执行,但第一个事务回滚,第二个事务操作脏数据

      

      幻读:
      一个事务查询到了另一个事务已经提交的新数据,导致多次查询数据不一致

      

      不可重复读:
      一个事务查询到另一个事务已经修改的数据,导致多次查询数据不一致

      

    4.数据库事务的隔离级别(C)

      针对上述的事务并发问题,数据库提供了不同的事务隔离级别来处理不同的事务并发问题,事务隔离级别定义如下:

      

      针对不同隔离级别可以解决的的如下五类问题:

      

      

    5.解决丢失更新的方案

      要解决丢失更新问题可以给数据库表数据加锁:
        1.悲观锁
          在操作当前数据的事务开启事务就使用for update 锁住当前数据(在Hibernate和MyBatis中都有悲观锁对应的解决方案)

          

        2.乐观锁
          为表添加一个version字段。当前事务操作的时候都会比对当前事务情况的多次操作的版本号是否一致,如果不一致认为数据已经被更新(在Hibernate和MyBatis中都有乐观锁对应的解决方案)

    6.Spring对事务的支持

      Spring的事务管理主要包括3个接口:

      

      TransactionDefinition:

        该接口主要定义了:事务的传播行为(规则),事务的隔离级别,获得事务信息的方法。所以在配置事务的传播行为,事务的隔离级别已经需要获得事务信息时,可以通过查阅该类的代码获得相关信息。
        TransactionDefinition源码:

    public interface TransactionDefinition {
    
         //事务的传播行为
        int PROPAGATION_REQUIRED = 0;
    
        int PROPAGATION_SUPPORTS = 1;
    
        int PROPAGATION_MANDATORY = 2;
    
        int PROPAGATION_REQUIRES_NEW = 3;
    
        int PROPAGATION_NOT_SUPPORTED = 4;
        int PROPAGATION_NEVER = 5;
        int PROPAGATION_NESTED = 6;
        //事务的隔离级别
        int ISOLATION_DEFAULT = -1;
    
        int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED;
    
        int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED;
    
        int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ;
    
        int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE;
        //事务超时管理
        int TIMEOUT_DEFAULT = -1;
    
        //获得事务信息
        int getPropagationBehavior();
    
        int getIsolationLevel();
    
        int getTimeout();
    
        boolean isReadOnly();
        
        String getName();
    
    }

      事务传播规则

      

      PlatformTransactionManager事务管理器

    PlatformTransactionManager:接口统一抽象处理事务操作相关的方法;

    1:TransactionStatus getTransaction(TransactionDefinition definition):
      根据事务定义信息从事务环境中返回一个已存在的事务,或者创建一个新的事务,并用TransactionStatus描述该事务的状态。
    2:void commit(TransactionStatus status):
      根据事务的状态提交事务,如果事务状态已经标识为rollback-only,该方法执行回滚事务的操作。
    3:void rollback(TransactionStatus status):
      将事务回滚,当commit方法抛出异常时,rollback会被隐式调用

    在使用spring管理事务的时候,首先得告诉spring使用哪一个事务管理器,使用不同的框架(JdbcTemplate,MyBatis,Hibernate/JPA )使用事务管理器都不同

    7.Spring事务的配置

    Spring支持编程式事务管理和声明式事务管理。
    编程式事务管理:事务和业务代码耦合度太高。
    声明式事务管理:侵入性小,把事务从业务代码中抽离出来,使用AOP配置到配置文件中,提高维护性。

      7.1声明式事务管理xml方式配置

    1.导入jar包

    2.创建配置文件并引入约束和命名空间

    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        ">

        配置文件

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:p="http://www.springframework.org/schema/p"
        xmlns:context="http://www.springframework.org/schema/context"
        xmlns:aop="http://www.springframework.org/schema/aop"
        xmlns:tx="http://www.springframework.org/schema/tx"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="
            http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/aop
            http://www.springframework.org/schema/aop/spring-aop.xsd
            http://www.springframework.org/schema/tx
            http://www.springframework.org/schema/tx/spring-tx.xsd
            ">
        <!-- 配置注解包扫描 -->
        <context:component-scan base-package="com.gjs"/>
        
        <!-- 读取db.properties 配置文件 -->
        <context:property-placeholder location="classpath:db.properties"/>
        
        <!-- 创建druid连接池  -->
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
            
            <!-- 属性注入 setter方法注入  -->
            <property name="driverClassName" value="${jdbc.driverClassName}"/>
            <property name="url" value="${jdbc.url}"/>
            <property name="username" value="${jdbc.username}"/>
            <property name="password" value="${jdbc.password}"/>
            <property name="maxActive" value="${jdbc.maxActive}"/>
        </bean>
        
        <!-- 配置 JdbcTemplate 模板类 -->
        <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
            <!-- 使用构造器注入,注入 dataSource 连接池 -->
            <constructor-arg name="dataSource" ref="dataSource"/>
        </bean>
        
        <!-- 1. 配置事务管理器 -->
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <!-- 注入数据源(连接池) -->
            <property name="dataSource" ref="dataSource"></property>
        </bean>
    
        
        
        <!-- 2.事务相关配置:使用tx标签 -->
        <!-- 配置事务通知 
            id:通知唯一标识
            transaction-manager:依赖的事务管理器
        -->
        <tx:advice id="txAdvice" transaction-manager="transactionManager">
            <tx:attributes>
                <!-- 
                     <tx:method name=""/> 
                     需要被事务管理的方法 ,一般是都是service层的方法
                     name : 被事务管理的方法名
                     isolation :事务的隔离级别(REPEATABLE_READ)
                     propagation : 事务的传播规则 (REQUIRED)
                     read-only :是否是只读事务,默认false(DML 非只读事务,DQL :只读事务-效率相对高一些)
                     timeout:配置事务超时时间
                 
                  -->
                  <!-- 事务方法配置细节 
                     在实际开发中service的方法必须遵循一定编写规则
                     DQL最好指定 前缀规则 : selectXxx,queryXxx, findXxx,getXxx
                     我们可以使用 * 来配置指定规则的所有方法
                     
                     DQL 一般配 只读事务
                         read-only="true"
                     DML 一般配 非只读事务
                         read-only="false"
                 -->
                 <!-- DQL -->
                <tx:method name="select*" read-only="true" isolation="REPEATABLE_READ" propagation="REQUIRED"/>
                <tx:method name="query*" read-only="true" isolation="REPEATABLE_READ" propagation="REQUIRED"/>
                <tx:method name="find*" read-only="true" isolation="REPEATABLE_READ" propagation="REQUIRED"/>
                <tx:method name="get*" read-only="true" isolation="REPEATABLE_READ" propagation="REQUIRED"/>
                <!-- 非DQL -->
                <tx:method name="*" read-only="false" isolation="REPEATABLE_READ" propagation="REQUIRED"/>
            </tx:attributes>
        </tx:advice>
        
        <!-- 3.配置AOP把事务切到service层 -->
        <aop:config proxy-target-class="true">
            <!-- 配置切入点 -->
            <aop:pointcut expression="execution(* com.gjs.service..*.*(..))" id="pt"/>
            <!-- 配置切面:切入点+通知
                <aop:aspect></aop:aspect> :使用自定义事务管理器进行aop配置事务
                <aop:advisor advice-ref="" pointcut-ref=""/>:使用spring的事务管理器
                advice-ref="":通知引用
                pointcut-ref="":切入点引用
             -->
            <aop:advisor advice-ref="txAdvice" pointcut-ref="pt"/>
        </aop:config>
        
    </beans>

      7.2声明式事务管理注解方式配置

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:p="http://www.springframework.org/schema/p"
        xmlns:context="http://www.springframework.org/schema/context"
        xmlns:aop="http://www.springframework.org/schema/aop"
        xmlns:tx="http://www.springframework.org/schema/tx"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="
            http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/aop
            http://www.springframework.org/schema/aop/spring-aop.xsd
            http://www.springframework.org/schema/tx
            http://www.springframework.org/schema/tx/spring-tx.xsd
            ">
        <!-- 配置注解包扫描 -->
        <context:component-scan base-package="com.gjs"/>
        
        <!-- 读取db.properties 配置文件 -->
        <context:property-placeholder location="classpath:db.properties"/>
        
        <!-- 创建druid连接池  -->
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
            
            <!-- 属性注入 setter方法注入  -->
            <property name="driverClassName" value="${jdbc.driverClassName}"/>
            <property name="url" value="${jdbc.url}"/>
            <property name="username" value="${jdbc.username}"/>
            <property name="password" value="${jdbc.password}"/>
            <property name="maxActive" value="${jdbc.maxActive}"/>
        </bean>
        
        <!-- 配置 JdbcTemplate 模板类 -->
        <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
            <!-- 使用构造器注入,注入 dataSource 连接池 -->
            <constructor-arg name="dataSource" ref="dataSource"/>
        </bean>
        
        <!-- 配置事务管理器 -->
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <!-- 注入数据源(连接池) -->
            <property name="dataSource" ref="dataSource"></property>
        </bean>
    
        
        <!-- 开启 事务处理的注解驱动 
             transaction-manager:引用的事务管理器
         -->
         <tx:annotation-driven transaction-manager="transactionManager"/>
        
        
    </beans>
    @Service
    /* @Transactional
     * 贴上此当前类已经被Spring事务管理
     * 注意: @Transactional 只能对当前贴的Service类有效
     *  常用属性 :
     *   isolation=Isolation.REPEATABLE_READ,  隔离级别
    propagation=Propagation.REQUIRED,传播规则
    readOnly=true 是否只读事务
     *  
     */@Transactional(isolation=Isolation.DEFAULT,propagation=Propagation.REQUIRED)
    public class AccountServiceImpl implements AccountService{
    @Autowired
    private AccountDao dao;
     
    public void trans(Integer transOutId,
    Integer transInId, Integer money) {
    dao.tranOut(transOutId, money);
    System.out.println(1 / 0);// 模拟断电
    dao.tranIn(transInId, money);
    }
    //单独为某一个方法配置具体的事物细节:如查询方法,事物是只读的
    @Transactional(readOnly=true)
    public Object getUser() {
    //查询操作
    return null;
    }
    @Transactional(readOnly=true)
    public List<Object> getUsers() {
    //查询操作
    return null;
    }
    }
  • 相关阅读:
    微信小程序路由跳转方式
    事务的隔离级别 乐观锁和悲观锁 f查询 q查询
    手机短信验证
    用git来操作项目
    git使用
    redis数据库 Python使用redis
    DRF框架:接口 ,restfui接口规范,基于restful规范的原生Django接口,Postman接口工具
    vue初识
    Win10玩游戏时听歌音量忽大忽小
    ASP.NET Core 2.0 WebApi全局配置及日志
  • 原文地址:https://www.cnblogs.com/gaojinshun/p/11196980.html
Copyright © 2020-2023  润新知