Spring事务( Transaction )

事务的概念

事务是一些sql语句的集合,作为一个整体执行,一起成功或者一起失败。

使用事务的时机

一个操作需要多天sql语句一起完成才能成功

程序中事务在哪里说明

加在业务类的方法上面(public方法上面),表示业务方法执行时,需要事务的支持。

不同的事务管理器

不同的数据库访问技术,处理事务是不同的

  1. 使用jdbc 访问数据库,事务处理

    public void updateAccount(){
    	Connection con = .....;
    	con.setAutoCommit(false);
    	state.insert();
    	state.update();
    	state.commit();
    	con.setAutoCommit(true);
    }
    
  2. MyBatis执行数据库,处理事务

    public void updateAccount(){
    	SqlSession sqlSession = SqlSessionFactory.openSession(false);
    	try{
    	 sqlSession.insert(...);
             sqlSession.update(...);
             sqlSession.commit();
    	}catch(Exception e){
    		sqlSession.rollback();
    	}
    }
    

spring统一管理事务,把不同的数据库访问技术的事务处理统一起来

使用spring的事务管理器,管理不同数据库访问技术的事务处理。开发人员只需要掌握spring的事务处理一个方案,就可以实现使用不同数据库访问技术的事务管理。

尽管事务面向的是spring,有spring管理事务,做事务提交和回滚。

spring事务管理器

spring框架使用事务管理器对象,管理所有的事务。

事务管理器接口: PlatFormTransactionManager

​ 作用 :定义了事务的操作,主要是commit() , rollback()

事务管理器有很多的实现类:一种数据库访问计数有一个实现类。由实现类具体完成事务的提交,回滚。

这意味着:JDBC或者MyBatis访问数据库有自己的事务管理实现类:DataSourceTransactionManager

​ hibernate框架,他的事务管理器实现类:HibernateTransactionManager

事务管理器的工作方式

spring集中统一管理事务,分配管理事务给具体的事务管理器对象。

事务提交和回滚的时机

当业务正常执行的时候,没有异常,事务是提交的。如果业务代码中出现了运行时异常,事务会发生回滚。

异常的分类:

  • Error : 错误,回滚事务
  • Exception:
    • 运行时异常:RuntimeException和他的子类都是运行时异常,在程序执行过程中抛出的异常
    • 受查异常:在编写代码时需要处理的异常(编译都无法通过),会提交事务。

方法抛出运行时异常事务回滚,其他的情况都是执行事务。

spring事务管理的实现方式:AOP中的环绕通知

环绕通知:在目标方法的前后都可以增加功能,不需要修改代码。

// spring给业务方法在执行时,增加事务的切面功能(一下为大致的实现步骤,不是真正的执行代码)
@Around("execution(* com.wang.*.*(..))")
public Object myAround(ProceedingJoinPoint pjp){
    try{
        PlatformTransactionManager.beginTransaction(); // 使用spring的事务管理器,开启事务
        pjp.proceed(); // 执行目标方法
        PlatformTransactionManager.commit(); // 业务方法正常执行,事务提交
    }catch(Exception e){
        PlatformTransactionManager.roolback(); // 业务方法非正常执行,事务回滚
    }
}

事务定义接口(TransactionDefinition )

事务定义接口TransactionDefinition中定义了事务描述相关的三类常量:事务隔离级别事务传播行为事务默认超时时限,及他们的操作。

给业务方法说明事务的属性。

隔离级别

定义

控制事务之间的影响速度

具体值
  • DEFAULT:采用DB默认的事务隔离级别。Mysql的默认为REPEATABLE_READOracle默认为READ_COMMITTED
  • READ_UNCOMMITTED:读未提交。未解决任何并发问题
  • READ_COMMITTER:读已提交。解决脏读,存在不可重复读和幻读
  • REPETABLE_READ:可重复读。解决脏读,不可重复读,存在幻读
  • SERIALIZABLE:串行化。不存在并发问题。

事务超时时间

以秒为单位,整数值,默认为-1

超时时间:表示一个业务方法最长的执行时间,到达时间没有执行完毕,spring会回滚事务。

传播行为

传播行为有7个值

定义:业务方法在调用的时候,事务在方法之间的传递和使用。

使用传播行为,表示方法有无事务。

  • PROPAGATION_REQUIRED

  • PROPAGATION_REQUIRES_NEW

  • PROPAGATION_SUPPORTS

    以上三个需要掌握

  • PROPAGATION_MANDATORY

  • PROPAGATION_NESTED

  • PROPAGATION_NEVER

  • PROPAGATION_NOT_SUPPORTED

  1. PROPAGATION_REQUIRED:spring默认传播行为,方法在调用的时候,如果存在事务就是用当前的事务,如果没有事务,就新建一个事务,方法在新事务中执行。
  2. PROPAGATION_SUPPORTS: 方法有事务可以正常执行,没有事务也可以正常执行。(查询操作)
  3. PROPAGATION_REQUIRES_NEW:方法需要一个新事务。如果调用方法的时候,存在一个事务,则原来的事务暂停,知道新事务执行完毕。如果方法调用的时候,没有事务,则新建一个事务,在新事务中执行代码。

spring框架使用自己的注解@Transactional控制事务

@Transactional注解,使用注解的属性控制事务(隔离级别,传播行为,超时)

​ 其中的属性:

  • propagation:事务的传播行为,他使用的Propagation类的枚举值,例如:Propagation.REQUIRED;
  • isolation:表示隔离级别,使用Isolation类的枚举值,表示隔离级别,默认是:Ioslation.DEFAULT;
  • readOnly:boolean类型的值,表示数据库操作是不是只读的,默认为false
  • timeout:事务超时,默认是-1,整数值,单位是秒,例如:timeout=20;
  • rollbackFor:表示回滚的异常类列表,他的值是一个数组,每个值是异常类型的class。
  • rollbackForClassName:表示回滚的异常类列表,是String类型的值
  • noRollbackFor:不需要回滚的异常类列表,是class类型的
  • noRollbackForClassName:不需要回滚的异常类列表,是String类型的值

该注解放置的位置:

  1. 在业务方法上面,实在public方法上面(大多数)
  2. 在类的上面(几乎见不到)

注解的使用步骤

  1. 在spring配置文件中,声明事务的内容;

    声明事务管理器,说明使用哪个事务管理器对象;

    声明使用注解管理事务,开启注解驱动

  2. 在类的源代码中,加入@Transactional

事务的控制模式:

  1. 编程式,在代码中编程控制事务
  2. 声明式事务,不用编码
步骤一:
<!--声明事务管理器(连接到数据库,做事务提交,事务回滚)-->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
    <!--指定数据源-->
    <property name="dataSource" ref="myDataSource"/>
</bean>

<!--开启事务注解驱动-->
<tx:annotation-driven transaction-manager="transactionManager"/>
<!--最后导入的包是以tx结尾-->
步骤二:

在具体的方法上加上@Transactional注解

	@Transactional(
            propagation = Propagation.REQUIRED,
            isolation = Isolation.DEFAULT,
            readOnly = false,
            timeout = 20,
            rollbackFor = {NullPointerException.class}
    )
    @Override
    public void buy(Integer goodId, Integer amount) throws Exception {
        System.out.println("buy方法的执行");
        Sale sale = new Sale();
        sale.setGid(goodId);
        sale.setNums(amount);
        // 生成销售记录
        saleDao.insertSale(sale);
        // 查询商品
        Good good = goodDao.selectById(goodId);
        if (good == null){
            throw new Exception("商品不存在");
        }else if (good.getAmount() < amount){
            throw new Exception("库存不足");
        }
        // 更新库存
        Good good1 = new Good();
        good1.setId(goodId);
        good1.setAmount(amount);
        goodDao.updateGood(good1);
        System.out.println("buy方法的完成");
    }
rollbackFor说明
  1. 框架首先检查方法抛出的异常是不是在rollbackFor数组中,如果在一定会发生回滚;
  2. 如果方法抛出的异常不在rollbackFor数组中,框架会继续检查抛出的异常是不是RuntimrException,如果是RuntimeException,一定会发生回滚。

使用rollbackFor可以实现发生受检查异常时让其发生回滚。

注解的使用特点

直接使用@Transactional会使用默认值

  • spring框架自己提供的事务控制
  • 适合中小型项目,注解都放置在了源代码中,不利于优化
  • 使用方便效率高

使用Aspectj框架在spring配置文件中,声明事务控制

使用aspectj的aop,声明事务控制叫做声明式事务

使用步骤

  1. 加入spring-aspectj的依赖
  2. spring的配置文件声明事务属性
    • 声明事务管理器
    • 声明业务方法需要的事务属性
    • 声明切入点表达式
步骤

基本上是模板化的方法

<!--声明式事务-->
<!--1.声明事务管理器-->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager2">
    <property name="dataSource" ref="myDataSource"/>
</bean>

<!--2.声明业务方法的事务属性(隔离级别,传播行为,超时)
            id:给业务方法配置事务段代码起个名字,唯一值
            transaction-manager:事务管理器的id
            -->
<tx:advice id="serviceAdvice" transaction-manager="transactionManager2">
    <!--给具体的业务方法增加事务的说明-->
    <tx:attributes>
        <!-- 给具体的业务方法,说明他需要的事务属性
                name:业务方法名称,配置name的值 1.业务方法的名称(类中的某一个方法) 2.带有部分通配符的方法名称 3.使用*
                propagation:指定传播行为
                isolation:隔离级别
                read-only:是否只读,默认为false
                timeout:超时时间
                rollback-for:指定回滚的异常类列表,使用的异常类的全限定名称(多个异常时用都好分割)
                -->
        <tx:method name="buy"
                   isolation="DEFAULT"
                   propagation="REQUIRED"
                   timeout="20"
                   rollback-for="java.lang.NullPointerException"
                   />

        <!--在业务方法有命名规则的时候,可以对一些方法使用事务-->
        <tx:method name="add*" propagation="REQUIRED" timeout="20" isolation="DEFAULT"/>
        <tx:method name="delete*" propagation="REQUIRED" timeout="20" isolation="DEFAULT"/>
        <!--以上方法以外的-->
        <tx:method name="*"/>
    </tx:attributes>
</tx:advice>
<!--通过上述方法的声明,但是我们并不知道是哪个类的方法,让上述声明生效的方法:切入点表达式-->
<!--声明切入点表达式:表示哪些包中的哪些类的方法参与事务-->
<aop:config>
    <!--声明切入点表达式
          expression:切入点表达式,表示哪些类中的哪些方法要参与事务
            id:切入点表达式的名称,唯一值
            -->
    <aop:pointcut id="servicePointCut" expression="execution(* *..service..*.*(..))"/>
    <!-- 关联切入点表达式和事务的通知-->
    <aop:advisor advice-ref="serviceAdvice" pointcut-ref="servicePointCut"/>
</aop:config>
优缺点
  • 缺点:理解难,配置复杂

  • 优点:代码和事务分开。控制事务源代码不用修改。

    ​ 能快速的了解和掌握项目的全部事务。适合大型项目。