likes
comments
collection
share

事务Transactional

作者站长头像
站长
· 阅读数 11

一. 什么是事务

简单的说事务是逻辑上的一组操作,要么都执行,要么都不执行;

在我们的日常开发中,我们系统的每个业务方法可能包括了多个原子性的数据库操作,例如下面的saveUserInfo(),就包含多个原子性的数据库操作,这些原子性的数据库操作,要么都执行,要么都不执行。

public void saveUserInfo() {
  userInfoDao.save(userBase);
  10 / 0;
  userInfoDetail.save(userInfoDetail);
}

但是,我们需要注意的是:事务能否生效的关键是数据库引擎是否支持。比如我们常用的MySQL数据库,默认使用支持事务的innodb引擎,如果我们使用myisam引擎,那么就不再支持事务了。

事务举例:

比如张三要给李四转一万块钱,这个转账会涉及两个关键操作

1.张三的银行账户余额减少10000

2.李四的银行账户余额增加10000

如果这两个操作之间突然出现了错误,比如银行系统崩溃、网络故障、服务宕机;导致张三余额减少,而李四的余额没有增加,那么这样就不对了。事务就是要保证这两个关键操作要么都成功,要么都失败。

public class TransferAccountsServiceImpl{
  @Resource
  private ITransferAccountsDao transferAccountsDao;
  
  @Transactional(propagation = Propagation.REQUIRED)
  public void transferAccount() {
    transferAccountsDao.addMonney(10000, "lisi");
    10 / 0;
    transferAccountsDao.reduceMonney(10000, "zhangsan");
  }
}

二. 事务的特点ACID

  1. 原子性(Atomicity):事务最小的执行单位,不允许分割,事务的原子性确保动作要么全部完成,要么完全失败。
  2. 一致性(Consistency):执行事务前后,数据保持一致,例如在上面的转账例子中,无论事务是否成功,转账者和收款人的总额应该是不变的。
  3. 隔离性(Isolation):并发访问数据库时,一个用户的事务不被其它事务干扰,各并发事务之间的数据库是独立的。
  4. 持久性(Durability):一个事务被提交后,它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。

三. Spring对事务的支持

程序是否支持事务的先决条件是数据库,比如使用MySQL的话,如果选择的是innodb,那么支持事务,如果选择的是myisam,那么从根上就不支持事务了

思考1:MySQL怎么保证原子性?

如果要保证原子性,就需要在发生异常时,对已经执行的操作进行回滚,在MySQL中,恢复机制是通过回滚日志实现的,所有事务进行的修改,都会先记录到这个回滚日志中,然后再执行相关的操作。

如果在执行过程中遇到异常,我们直接利用回滚日志中的信息将数据回滚到修改之前的样子即可,并且回滚日志会先将数据持久化到磁盘上,这样就可以保证即便在遇到数据库突然宕机,当用户再次重启数据库时,数据库还是能够通过查回滚日志来回滚之前未完成的事务。

1. Spring支持两种事务管理

编程事务管理

通过TransactionTemplate或者TransactionManager手动管理事务,在实际应用中却很少使用,下面通过代码来演示,使用TransactionTemplate进行编程式事务管理

@Autowired
private TransactionTemplate transactionTemplate;

public void testTransactionTemplate() {
  transactionTemplate.execute(new TransactionCallbackWithoutResult() {
    protected void doInTransactionWithoutResult(final TransactionStatus transactionStatus) {
      try {
        //TODO 业务代码
      } catch (final Exception e) {
        // 异常时回滚
        transactionStatus.setRollbackOnly();
      }
    }
  });
}

使用TransactionManager进行编程式事务管理

@Resource
private PlatformTransactionManager transactionManager;

public void testTransactionManager() {
  final TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
  try {
    //TODO 业务代码
    transactionManager.commit(status);
  } catch (final Exception e) {
    // 异常时回滚
    transactionManager.rollback(status);
  }
}

声明式事务管理

声明式事务管理,实际上是通过AOP实现,基于@Transactional的注解使用最多

使用@Transactional注解进行事务管理

@Transactional
public void testTransactional() {
  userInfoDao.save(userInfo);
  userInfoDetailDao.save(userInfoDetail);
}

2. Spring事务管理接口介绍

Spring框架中,事务管理最重要的3个接口:

  1. PlatformTransactionManager:平台事务管理器,Spring事务策略核心。
  2. TransactionDefinition:事务定义信息(事务隔离级别、传播行为、超时、只读、回滚规则)。
  3. TransactionStatus:事务运行状态。

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

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

事务管理器:PlatformTransactionManager

PlatformTransactionManager:通过这个接口,Spring为各个数据库持久层框架,例如JDBC(DataSourceTransactionManager)Hibernate(HibernateTransactionManager)JPA(JpaTransactionManager)等提供了对应的事务管理器,具体如何实现,就根据各个框架的特性实际出发。

SpringBootPlatformTransactionManager接口定义如下:

package org.springframework.transaction;

import org.springframework.lang.Nullable;

public interface PlatformTransactionManager extends TransactionManager {
    TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;

    void commit(TransactionStatus status) throws TransactionException;

    void rollback(TransactionStatus status) throws TransactionException;
}

SpringPlatformTransactionManager接口定义如下:

package org.springframework.transaction;

public interface PlatformTransactionManager {
    TransactionStatus getTransaction(TransactionDefinition var1) throws TransactionException;

    void commit(TransactionStatus var1) throws TransactionException;

    void rollback(TransactionStatus var1) throws TransactionException;
}

思考2:为什么要将PlatformTransactionManager定义成接口呢?

将事务管理行为抽象出来,然后不同的平台去实现这个接口,这样我们可以保证提供给外部的行为不变,方便扩展。

事务属性:TransactionDefinition

事务管理器接口PlatformTransactionManager通过getTransaction(TransactionDefinition var1)方法来得到事务,这个方法的参数是TransactionDefinition类型,这个类定义了一些基本的事务属性

事务属性包含了以下5个方面:

  • 隔离级别
  • 传播行为
  • 回滚规则
  • 是否只读
  • 事务超时

TransactionDefinition接口定义如下:

package org.springframework.transaction;

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();
}

事务状态:TransactionStatus

TransactionStatus接口用来记录事务的状态,这个接口定义了一组方法,用来获取或判断事务相应的状态信息,PlatformTransactionManager.getTransaction(TransactionDefinition var1)方法返回一个TransactionStatus对象。

TransactionStatus接口定义如下:

package org.springframework.transaction;

import java.io.Flushable;

public interface TransactionStatus extends SavepointManager, Flushable {
    boolean isNewTransaction();

    boolean hasSavepoint();

    void setRollbackOnly();

    boolean isRollbackOnly();

    void flush();

    boolean isCompleted();
}

3. 事务注解详解

在实际的业务开发中,大家一般使用@Transactional注解来开启事务,但很多人并不是很清楚这个注解中的参数是什么意思?有什么用?下面我将通过一些样例代码来介绍这个注解。

事务传播行为是为了解决业务层方法之间互相调用的事务问题

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

例如:

DemoService1类中的service1()方法中调用了DemoService2类中的service2()方法。这就涉及到了业务层方法之间互相调用的事务问题了,如果在service2方法中出现了异常需要回滚,如何配置事务传播行为才能让service1也跟着回滚呢?

@Service
public class DemoService1 {
  @Resource
  private DemoService2 demoService2;
  
  public void service1() {
    demoService2.service2();
    // 
  }
}

public class DemoService2 {
  @Transactional(propagation = Propagation.XXX)
  public void service2() {
    //TOOD
    
  }
}

TransactionDefinition中,定义了7个表示传播行为的常量

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;
}

为了方便使用,Spring又定义了一个枚举类Propagation

package org.springframework.transaction.annotation;

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;
    }
}

事务传播行为可能的值

  1. TransactionDefinition.PROPAGATION_REQUIRED 使用最多的一个事务传播行为,平常我们使用的@Transactional注解默认使用的就是这个事务传播行为。

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

例如:

上面的service1()service2()方法都使用PROPAGATION_REQUIRED传播事务行为,两者使用的是同一个事务,只要其中的一个方法回滚,整个事务就回滚

@Service
public class DemoService1 {
  @Resource
  private DemoService2 demoService2;
  
  @Transactional(propagation = Propagation.REQUIRED)
  public void service1() {
    
    demoService2.service2();
    
  }
}

public class DemoService2 {
  @Transactional(propagation = Propagation.REQUIRED)
  public void service2() {
    //TOOD
  }
}
  1. TransactionDefinition.PROPAGATION_REQUIRES_NEW 创建一个新事务,如果当前存在事务,把当前挂起,也就是说无论外部方法是否开启事务,Propagation.REQUIRES_NEW修饰的内部方法会新开启自己的事务,且事务相互独立,互不打扰。

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

@Service
public class DemoService1 {
  @Resource
  private DemoService2 demoService2;
  
  @Transactional(propagation = Propagation.REQUIRED)
  public void service1() {
    demoService2.service2();
    10 / 0
  }
}

public class DemoService2 {
  @Transactional(propagation = Propagation.REQUIRES_NEW)
  public void service2() {
    //TOOD
  }
}
  1. TransactionDefinition.PROPAGATION_NESTED 如果当前存在事务,就在嵌套事务内执行,如果当前没有事务,就执行与TransactionDefinition.PROPAGATION_REQUIRED类似的操作。

在外部方法开启事务的情况下,在内部开启一个新的事务,作为嵌套事务存在;如果外部方法没有开启事务,则单独开启一个事务,与PROPAGATION_REQUIRED类似。

例如:如果service2()回滚,service1()不会回滚,如果service1()回滚,service2()也回滚

@Service
public class DemoService1 {
  @Resource
  private DemoService2 demoService2;
  
  @Transactional(propagation = Propagation.REQUIRED)
  public void service1() {
    demoService2.service2();
  }
}

public class DemoService2 {
  @Transactional(propagation = Propagation.NESTED)
  public void service2() {
    //TOOD
  }
}
  1. TransactionDefinition.PROPAGATION_MANDATORY 如果当前存在事务,则加入该事务,如果当前没有事务,则抛出异常。
  2. TransactionDefinition.PROPAGATION_SUPPORTS 如果当前存在事务,则加入该事务,如果当前没有事务,则以非事务方式继续运行。
  3. TransactionDefinition.PROPAGATION_NOT_SUPPORTED 以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  4. TransactionDefinition.PROPAGATION_NEVER 以非事务方式运行,如果当前存在事务,则抛出异常。

事务隔离级别

TransactionDefinition接口中定义了5个表示隔离级别的常量

package org.springframework.transaction;

public interface TransactionDefinition {

  int ISOLATION_DEFAULT = -1;
  int ISOLATION_READ_UNCOMMITTED = 1;
  int ISOLATION_READ_COMMITTED = 2;
  int ISOLATION_REPEATABLE_READ = 4;
  int ISOLATION_SERIALIZABLE = 8;
  
}

和事务传播行为一样,Spring又定义了一个枚举类Isolation

package org.springframework.transaction.annotation;

public enum Isolation {
  DEFAULT(-1),
  READ_UNCOMMITTED(1),
  READ_COMMITTED(2),
  REPEATABLE_READ(4),
  SERIALIZABLE(8);

  private final int value;

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

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

事务隔离级别介绍:

隔离级别描述
TransactionDefinition.ISOLATION_DEFAULT使用数据库默认的隔离级别,MySQL默认采用REPEATABLE_READOracle默认采用READ_COMMITTED
TransactionDefinition.ISOLATION_READ_UNCOMMITTED最低隔离级别,这个隔离级别使用比较少,因为它允许读取尚未提交的数据变更,可能会导致脏读、幻读、不可重复读
TransactionDefinition.ISOLATION_READ_COMMITTED允许读取并发事务已经提交的数据,可以有效阻止脏读,但幻读、不可重复读仍然有可能发生
TransactionDefinition.ISOLATION_REPEATABLE_READ对同一个字段多次读取结果都是一致的,除非数据被本身事务修改,可以阻止脏读、不可重复读,但有可能发生幻读
TransactionDefinition.ISOLATION_SERIALIZABLE最高的隔离级别,完全遵从ACID的隔离级别,所有的事务逐个执行,保证事务之间互不干扰,可以有效防止脏读、幻读、不可重复读,这种隔离级别会影响程序性能,通常情况下也不会用到该级别

事务超时属性

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

事务只读属性

TransactionDefinition接口中定义了isReadOnly()

package org.springframework.transaction;

public interface TransactionDefinition {

  boolean isReadOnly();

}

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

思考3:为什么数据查询操作还要启用事务支持呢?

MySQL 默认对每一个新建立的连接都启用了autocommit模式。在该模式下,每一个发送到 MySQL 服务器的sql语句都会在一个单独的事务中进行处理,执行结束后会自动提交事务,并开启一个新的事务。如果在方法加上了Transactional注解,这个方法执行的所有sql会被放在一个事务中。如果声明了只读事务的话,数据库就会去优化它的执行,并不会带来其他的效率问题。如果不加Transactional,每条sql会开启一个单独的事务,期间被其它事务改了数据,都会实时读取到最新值。

事务回滚规则

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

如果想要回滚自定义的异常,代码可以如下这样写:

@Transactional(rollbackFor= MyException.class)

Transactional注解

@Transactional 的作用范围
  • 方法推荐将注解用于方法上,不过需要注意的是:该注解只能应用到 public 方法上,否则不生效。
  • :如果将注解用在类上,表明该类中所有的 public 方法都生效。
  • 接口:不推荐在接口上使用。
@Transactional 的参数

@Transactional注解源码如下,里面包含了基本事务属性的配置:

package org.springframework.transaction.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
  String value() 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 {};
}

@Transactional 的常用参数介绍

属性名说明
propagation事务的传播行为,默认值为 REQUIRED
isolation事务的隔离级别,默认值采用 DEFAULT
timeout事务的超时时间,默认值为-1(不会超时),如果超过该时间限制但事务还没有完成,则自动回滚事务
readOnly指定事务是否为只读事务,默认值为 false
rollbackFor用于指定能够触发事务回滚的异常类型,并且可以指定多个异常类型
@Transactional 事务注解原理

@Transactional 的工作机制是基于 AOP 实现的,AOP 又是使用动态代理实现的。如果目标对象实现了接口,默认情况下会采用 JDK 的动态代理,如果目标对象没有实现了接口,会使用 CGLIB 动态代理。

createAopProxy() 方法 决定了是使用 JDK 还是 Cglib 来做动态代理,源码如下:

package org.springframework.aop.framework;

import java.io.Serializable;
import java.lang.reflect.Proxy;
import org.springframework.aop.SpringProxy;

public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
  public DefaultAopProxyFactory() {
  }

  public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
    if (!config.isOptimize() && !config.isProxyTargetClass() && !this.hasNoUserSuppliedProxyInterfaces(config)) {
      return new JdkDynamicAopProxy(config);
    } else {
      Class<?> targetClass = config.getTargetClass();
      if (targetClass == null) {
        throw new AopConfigException("TargetSource cannot determine target class: Either an interface or a target is required for proxy creation.");
      } else {
        return (AopProxy)(!targetClass.isInterface() && !Proxy.isProxyClass(targetClass) ? new ObjenesisCglibAopProxy(config) : new JdkDynamicAopProxy(config));
      }
    }
  }

  private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) {
    Class<?>[] ifcs = config.getProxiedInterfaces();
    return ifcs.length == 0 || ifcs.length == 1 && SpringProxy.class.isAssignableFrom(ifcs[0]);
  }
}

如果一个类或者一个类中的 public 方法上被@Transactional 注解修饰,Spring 容器就会在启动的时候为其创建一个代理类,在调用被@Transactional 注解修饰的 public 方法时,实际调用的是TransactionInterceptor 类中的 invoke()方法。这个方法的作用就是在目标方法之前开启事务,方法执行过程中如果遇到异常的时候回滚事务,方法调用完成之后提交事务。

Spring AOP 自调用问题

当方法被@Transactional 注解修饰时,Spring 事务管理器只会在被其他类方法调用的时候生效,而不会在一个类中方法调用生效。

这是由Spring AOP 工作原理决定的。Spring AOP 使用动态代理来实现事务的管理,它会在运行的时候为带有 @Transactional 注解的方法生成代理对象,并在方法调用的前后应用事物逻辑。如果该方法被其他类调用,代理对象就会拦截方法调用并处理事务。但是在同类中的其他方法内部调用的时候,我们代理对象就无法拦截到这个内部调用,因此事务也就失效了。

DemoService3 类中的method1()调用method2()就会导致method2()的事务失效。解决办法就是避免同一类中自调用或者使用 AspectJ 取代 Spring AOP 代理

样例:

@Service
public class DemoService3 {

private void method1() {
     method2();
     //TODO
}
@Transactional
 public void method2() {
     //TODO
  }
}

在自调用的时候开启事务,这是因为使用了 AopContext.currentProxy() 方法来获取当前类的代理对象,然后通过代理对象调用 method2()。这样就相当于从外部调用了 method2(),因此事务注解会生效。

但是我们一般也不会在代码中这么写

样例(不推荐):

@Service
public class DemoService4 {

private void method1() {
  // 先获取该类的代理对象,然后通过代理对象调用method2。
  ((MyService)AopContext.currentProxy()).method2(); 
  //TODO
}
  
@Transactional
	public void method2() {
  	//TODO
 	}
}

四. 总结

  • @Transactional 注解只有作用在 public 修饰的方法上才生效,不推荐在接口上使用。
  • 避免同一个类中调用 @Transactional 注解的方法,这样会导致事务失效。
  • 正确的设置 @TransactionalrollbackForpropagation 属性,否则事务可能会回滚失败;
  • 使用 @Transactional 注解的方法所在的类必须被 Spring 管理,否则不生效;
  • 底层使用的数据库必须支持事务机制,否则不生效;