Spring事务总结

Spring事务总结

1.MySQL中事务的回滚

MySQL中事务的回滚是根据**回滚日志(undo log)实现的,所有的事务都会先记录到这个文件中,然后再执行相关的操作。如果执行过程出现异常,我们直接用回滚日志**中的信息将数据回滚到事务发生之前就可以了。MySQL设置是先写log再去执行事务的。

2.Spring支持的事务管理

###2.1Spring的事务分为声明式事务与编程式事务

我们这里主要讲的是声明式事务,编程式事务很少使用的,想要了解可以查看相关资料。

@Autowired
private TransactionTemplate transactionTemplate;
public void testTransaction() {

        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {

                try {

                    // ....  业务代码
                } catch (Exception e){
                    //回滚
                    transactionStatus.setRollbackOnly();
                }

            }
        });
}

#####编程式的事务:

使用原生的JDBC API进行事务管理
①获取数据库连接Connection对象
②取消事务的自动提交
③执行操作
④正常完成操作时手动提交事务
⑤执行失败时回滚事务
⑥关闭相关资源

#####声明式的事务:

在配置文件中做了相关的事务配置后就可以使用@Transactional注解进行事务管理了:

@Transactional(propagation=propagation.PROPAGATION_REQUIRED)
public void aMethod {
  //do something
}

2.2Spring事务管理接口

Spring从不同的事务管理API中抽象出了一整套事务管理机制,让事务管理代码从特定的事务技术中独立出来。开发人员通过配置的方式进行事务管理,而不必了解其底层是如何实现的。

  • PlatformTransactionManager: Spring的核心事务管理抽象,它为事务管理封装了一组独立于技术的方法。无论使用Spring的哪种事务管理策略(编程式或声明式),事务管理器都是必须的。
  • TransactionDefinition:事务定义信息(事务隔离级别、传播行为、超时、只读、回滚规则)。
  • TransactionStatus:事务运行状态。

我们可以把 PlatformTransactionManager 接口可以被看作是事务上层的管理者,而 TransactionDefinitionTransactionStatus 这两个接口可以看作是事物的描述。

PlatformTransactionManager 会根据 TransactionDefinition 的定义比如事务超时时间、隔离界别、传播行为等来进行事务管理 ,而 TransactionStatus 接口则提供了一些方法来获取事务相应的状态比如是否新事务、是否可以回滚等等。

通过**PlatformTransactionManager**接口,Spring 为各个平台如 JDBC(DataSourceTransactionManager)、Hibernate(HibernateTransactionManager)、JPA(JpaTransactionManager)等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。

###2.3TransactionDefinition:事务属性

Q:何为事务属性?

A:事务的基本配置,包括 1 隔离级别 2 传播行为 3 回滚机制 4 是否只读 5 事务超时

2.4TransactionStatus:事务状态

TransactionStatus接口用来记录事务的状态 该接口定义了一组方法,用来获取或判断事务的相应状态信息。

	boolean isNewTransaction(); // 是否是新的事物
    boolean hasSavepoint(); // 是否有恢复点
    void setRollbackOnly();  // 设置为只回滚
    boolean isRollbackOnly(); // 是否为只回滚
    boolean isCompleted; // 是否已完成

3.事务属性详解

3.1事务传播行为

当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继 续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。 事务的传播行为可以由传播属性指定。

比如A类中有个a方法,其调用了B类中的b方法。如果b方法发生异常要回滚,如何让a方法也跟着回滚?此时可以配置事务的传播行为。

Class A {
    @Transactional(propagation=propagation.xxx)
    public void a() {
        //do something
        B b = new B();
        b.b();
    }
}

Class B {
    @Transactional(propagation=propagation.xxx)
    public void b() {
       //do something
    }
}

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 = 1;
    int ISOLATION_READ_COMMITTED = 2;
    int ISOLATION_REPEATABLE_READ = 4;
    int ISOLATION_SERIALIZABLE = 8;
    int TIMEOUT_DEFAULT = -1;

    int getPropagationBehavior();

    int getIsolationLevel();

    int getTimeout();

    boolean isReadOnly();

    String getName();
}

但为了方便使用,注解内配置的是枚举类:

public enum Propagation {
    REQUIRED(0),
    SUPPORTS(1),
    MANDATORY(2),
    REQUIRES_NEW(3),
    NOT_SUPPORTED(4),
    NEVER(5),
    NESTED(6);

    private final int value;

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

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

正确的事务传播行为可能的值如下

0REQUIRED使用的最多的一个事务传播行为,我们平时经常使用的@Transactional注解默认使用就是这个事务传播行为。如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务
1SUPPORTS如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
2MANDATORY如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)
3REQUIRES_NEW创建一个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。
4NOT_SUPPORTED以非事务方式运行,如果当前存在事务,则把当前事务挂起。
5NEVER以非事务方式运行,如果当前存在事务,则抛出异常。
6NESTED如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于REQUIRED

0 REQUIRED

  1. 如果外部方法没有开启事务的话,**Propagation.REQUIRED**修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。
  2. 如果外部方法开启事务并且被**Propagation.REQUIRED修饰,所有Propagation.REQUIRED**修饰的内部方法和外部方法均属于同一事务 ,只要一个方法回滚,整个事务均回滚。

例如上边的例子如果都配置**@Transactional(propagation=propagation.REQUIRED)**,两者使用的就是同一个事务,只要其中一个方法回滚,整个事务均回滚。

3 REQUIRES_NEW

上面的**b()使用PROPAGATION_REQUIRES_NEW事务传播行为修饰,a()还是用PROPAGATION_REQUIRED修饰的话。如果a()发生异常回滚,b()**不会跟着回滚,因为 **b()**开启了独立的事务。但是,如果 **b()抛出了未被捕获的异常并且这个异常满足事务回滚规则的话,a()**同样也会回滚,因为这个异常被 **a()**的事务管理机制检测到了。

6 NESTED

  1. 在外部方法未开启事务的情况下**Propagation.NESTEDPropagation.REQUIRED**作用相同,修饰的内部方法都会新开启自己的事务,且开启的事务相互独立,互不干扰。
  2. 如果外部方法开启事务的话,**Propagation.NESTED**修饰的内部方法属于外部事务的子事务,外部主事务回滚的话,子事务也会回滚,而内部子事务可以单独回滚而不影响外部主事务和其他子事务。

如下:

Class A {
    @Transactional(propagation=propagation.PROPAGATION_REQUIRED)
    public void a() {
        //do something
        B b = new B();
        b.b();
        b.b2();
    }
}

Class B {
    @Transactional(propagation=propagation.PROPAGATION_NESTED)
    public void b() {
       //do something
    }
    
    @Transactional(propagation=propagation.PROPAGATION_NESTED)
    public void b2() {
       //do something
    }
}

a() 回滚的话,b()b2()都要回滚,而b()回滚的话,并不会造成 a()b()回滚。

若是错误的配置以下 3 种事务传播行为,事务将不会发生回滚,使用的很少。

  • TransactionDefinition.PROPAGATION_SUPPORTS
  • TransactionDefinition.PROPAGATION_NOT_SUPPORTED
  • TransactionDefinition.PROPAGATION_NEVER

3.2事务隔离级别

假设现在有两个事务:Transaction01 和 Transaction02 并发执行。

  1. 脏读

①Transaction01 将某条记录的 AGE 值从 20 修改为 30。

②Transaction02 读取了 Transaction01 更新后的值:30。

③Transaction01 回滚,AGE 值恢复到了 20。

④Transaction02 读取到的 30 就是一个无效的值。

  1. 不可重复读

①Transaction01 读取了 AGE 值为 20。

②Transaction02 将 AGE 值修改为 30。

③Transaction01 再次读取 AGE 值为 30,和第一次读取不一致。

  1. 幻读

①Transaction01 读取了 STUDENT 表中的一部分数据。

②Transaction02 向 STUDENT 表中插入了新的行。

③Transaction01 读取了 STUDENT 表时,多出了一些行。

Spring中的事务隔离级别与MySQL对应,具体可以去看MySQL里的事务隔离级别

用@Transactional 注解声明式地管理事务时可以在@Transactional 的 isolation 属 性中设置隔离级别

3.2事务超时

所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition 中以**int** 的值来表示超时时间,其单位是秒,默认值为-1。

3.3 事务只读属性

@Transactional(propagation = Propagation.REQUIRED,timeout = -1,readOnly = true)

对于只有读取数据查询的事务,可以指定事务类型为 readOnly,即只读事务。只读事务不涉及数据的修改,数据库会提供一些优化手段,适合用在有多条数据库查询操作的方法中。

  1. 如果你一次执行单条查询语句,则没有必要启用事务支持,数据库默认支持 SQL 执行期间的读一致性;
  2. 如果你一次执行多条查询语句,例如统计查询,报表查询,在这种场景下,多条查询 SQL 必须保证整体的读一致性,否则,在前条 SQL 查询之后,后条 SQL 查询之前,数据被其他用户改变,则该次整体的统计查询将会出现读数据不一致的状态,此时,应该启用事务支持

原因:

MySQL 默认对每一个新建立的连接都启用了autocommit模式。在该模式下,每一个发送到 MySQL 服务器的sql语句都会在一个单独的事务中进行处理,执行结束后会自动提交事务,并开启一个新的事务。

但是,如果你给方法加上了Transactional注解的话,这个方法执行的所有sql会被放在一个事务中。如果声明了只读事务的话,数据库就会去优化它的执行,并不会带来其他的什么收益。

如果不加Transactional,每条sql会开启一个单独的事务,中间被其它事务改了数据,都会实时读取到最新值。

为了保证多条查询时后,数据不受影响,故开启事务支持。

3.4事务回滚规则

默认情况下,事务只有遇到运行期异常(RuntimeException 的子类)时才会回滚,Error 也会导致事务回滚,但是,在遇到检查型(Checked)异常时不会回滚。

定义零(0)个或多个异常类,这些类必须是Throwable的子类,指示哪些异常类型必须引起事务回滚。

/**
	 * Defines zero (0) or more exception {@link Class classes}, which must be a
	 * subclass of {@link Throwable}, indicating which exception types must cause
	 * a transaction rollback.
	 * <p>This is the preferred way to construct a rollback rule, matching the
	 * exception class and subclasses.
	 * <p>Similar to {@link org.springframework.transaction.interceptor.RollbackRuleAttribute#RollbackRuleAttribute(Class clazz)}
	 */
	Class<? extends Throwable>[] rollbackFor() default {};

如果你想要回滚你定义的特定的异常类型的话

@Transactional(rollbackFor= MyException.class)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值