《Spring系列》第15章 声明式事务(一) 基础使用

时间:2021-03-30 01:19:09

一、ACID特性

⑴ 原子性(Atomicity)
       原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚,这和前面两篇博客介绍事务的功能是一样的概念,
    因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。

⑵ 一致性(Consistency)
  一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。
	转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性。

⑶ 隔离性(Isolation)
  隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。

  即要达到这么一种效果:对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。

  关于事务的隔离性数据库提供了多种隔离级别,稍后会介绍到。

⑷ 持久性(Durability)
  持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。

  例如我们在使用JDBC操作数据库时,在提交事务方法后,提示用户事务操作完成,当我们程序执行完成直到看到提示后,就可以认定事务以及正确提交,
  即使这时候数据库出现了问题,也必须要将我们的事务完全执行完成,否则就会造成我们看到提示事务处理完毕,但是数据库因为故障而没有执行事务的重大错误。

二、事务

Spring的事务管理有两种方式:一种是传统的编程式事务管理,即通过编写代码实现的事务管理;另一种是基于 AOP 技术实现的声明式事务管理。由于在实际开发中,编程式事务管理很少使用,所以我们只对 Spring 的声明式事务管理进行详细讲解。

Spring 声明式事务管理在底层采用了 AOP 技术,其最大的优点在于无须通过编程的方式管理事务,只需要在配置文件中进行相关的规则声明,就可以将事务规则应用到业务逻辑中。

Spring 实现声明式事务管理主要有两种方式:

  • 基于 XML 方式的声明式事务管理。
  • 通过 Annotation 注解方式的事务管理。

1.基于XML的声明式事务

目前已经很少写XML的事务管理了

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:contxt="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:aop="http://www.springframework.org/schema/aop"
       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/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/tx/spring-aop.xsd
">

    <!--开启包扫描-->
    <contxt:component-scan base-package="com.jianan"/>

    <!--数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
          destroy-method="close">
        <property name="url" value="jdbc:mysql:///jianan"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    </bean>

    <!--连接-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!--注入 dataSource-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--事务管理-->
    <bean id="transactionManager"
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--注入数据源-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--配置通知-->
    <tx:advice id="advice">
        <tx:attributes>
            <tx:method name="update" propagation="REQUIRED" />
        </tx:attributes>
    </tx:advice>

    <!--aop配置-->
    <aop:config>
        <!--配置切入点-->
        <aop:pointcut id="pt" expression="execution(* com.jianan.spring5.tx.UserDao.*(..))" />
        <!--配置切面-->
        <aop:advisor advice-ref="advice" pointcut-ref="pt" />
    </aop:config>

</beans>

2.基于注解的声明式事务

  1. @EnableTransactionManagement开启事务管理
  2. @Transactional 事务

三、@Transactional 注解介绍

  1. @Transactional,这个注解添加到类上面,也可以添加方法上面
  2. 如果把这个注解添加类上面,这个类里面所有的方法都添加事务
  3. 如果把这个注解添加方法上面,为这个方法添加事务

看一下其源码

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
    @AliasFor("transactionManager")
    String value() default "";

    @AliasFor("value")
    String transactionManager() default "";

    // 事务传播行为
    Propagation propagation() default Propagation.REQUIRED;

    // 事务的隔离级别
    Isolation isolation() default Isolation.DEFAULT;
    
    // 超时时间
    int timeout() default -1;

    boolean readOnly() default false;
  
    // 回滚异常
    Class<? extends Throwable>[] rollbackFor() default {};

    String[] rollbackForClassName() default {};
    
    // 不回滚异常
    Class<? extends Throwable>[] noRollbackFor() default {};

    String[] noRollbackForClassName() default {};
}

1) propagation 事务传播行为

多事务方法直接进行调用,这个过程中事务 是如何进行管理的,举例就是当一个方法A,调用到另外一个事务方法B,方法A的事务是如何传播到方法B的

Spring提供了7种传播行为

行为 描述
Propagation.REQUIRED 如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务
假如方法A存在事务,方法A调用事务方法B,此时A B 共用一套方法
假如方法A不存在事务,方法A调用事务方法B,此时B创建一个新的事务
默认值
Propagation.REQUIRES_NEW 重新创建一个新的事务,如果当前存在事务,延缓当前的事务
方法A调用事务方法B,此时无论方法A是否有事务,都会直接为B创建新的事务
Propagation.SUPPORTS 如果当前存在事务,则加入该事务;如果当前不存在事务,则以非事务的方式继续运行
Propagation.NOT_SUPPORTED 以非事务的方式运行,如果当前存在事务,暂停当前的事务
Propagation.MANDATORY 如果当前存在事务,则加入该事务;如果当前不存在事务,则抛出异常
Propagation.NEVER 以非事务的方式运行,如果当前存在事务,则抛出异常
Propagation.NESTED 如果没有,就新建一个事务;如果有,就在当前事务中嵌套其他事务

上面的七种行为,对应Java种的Propagation枚举类

public enum Propagation {
    REQUIRED(0),// 意思代表必须,方法必须有事务
    SUPPORTS(1),// 意思为支持 也就是方法B跟随方法A的事务状态
    MANDATORY(2),// 意思为强制的  也就是方法B必须使用事务,那么就看方法A有没有,有就加入,没有就报错
    REQUIRES_NEW(3),// 意思代表必须新的,也就是方法B必须创建新的事务
    NOT_SUPPORTED(4),// 意思为不支持,也就是暂停事务,全部以非事务运行
    NEVER(5),// 意思为绝不  就是以非事务运行
    NESTED(6);// 意思为嵌套  就是方法A有事务,方法B就嵌套一个事务

    private final int value;

    private Propagation(int value) {
        this.value = value;
    }

    public int value() {
        return this.value;
    }
}

2) isolation 事务隔离级别

事务隔离级别对应Java中的4种隔离级别,这些隔离级别就是为了问题 脏读幻读不可重复读

解释 描述
脏读 读未提交的
不可重复读 主要针对查询,不可重复读是指在对于数据库中的某个数据,一个事务范围内多次查询却返回了不同的数据值,
这是由于 在查询间隔,被另一个事务修改并提交了
幻读 主要针对修改,在事务修改后,发现还有1行未修改,因为另一个事务插入了新的一条,好似幻觉一样

数据库隔离级别

隔离级别 描述 脏读 不可重复 幻读 备注
Serializable 串行化
Repeatable read 可重复读 × 默认值
Read committed 读已提交 × ×
Read uncommitted 读未提交 × × ×

3) timeout 超时时间

  1. 事务需要在一定时间内进行提交,如果不提交进行回滚
  2. 默认值是 -1 ,设置时间以秒单位进行计算

4) readOnly 是否只读

  1. 读:查询操作,写:添加修改删除操作
  2. readOnly 默认值 false,表示可以查询,可以添加修改删除操作
  3. 设置 readOnly 值是 true,设置成 true 之后,只能查询

5) rollbackFor 回滚

设置进行回滚的异常

6) noRollbackFor 不回滚

设置不进行回滚的异常