Spring事务

事务的四个关键属性 ACID

  • 原子性:事务应该当作一个单独单元的操作,这意味着整个序列操作要么是成功,要么是失败的。
  • 一致性:这表示数据库的引用完整性的一致性,表中唯一的主键等。
  • 隔离性:可能同时处理很多有相同的数据集的事务,每个事务应该与其他事务隔离,以防止数据损坏。
  • 持久性:一个事务一旦完成全部操作后,这个事务的结果必须是永久性的,不能因系统故障而从数据库中删除。

事务隔离性

隔离性脏读不可重复度幻读
ISOLATION_READ_UNCOMMITTED
ISOLATION_READ_COMMITTED×
ISOLATION_REPEATABLE_READ××
ISOLATION_SERIALIZABLE×××

声明式 VS 编程式

EJB中两种处理事务的方式。

  • 声明式事务管理(CMT 容器管理事务 container-managed transactions);
  • 编程式事务管理(BMT Bean管理事务 Bean-Managed Transactions);

  • 在Bean管理的事务(BMT)中,EJB或消息驱动Bean中的代码显式地标记事务的边界。这给了极大的灵活性,但却很难维护。
  • 容器管理事务,这意味着你从业务代码中分离事务管理。仅仅使用注释或 XML 配置来管理事务。

Spring 支持使用 Spring AOP 框架的声明式事务管理,类似于EJB 的CMT 。

Spring 事务抽象

统一一致的事务抽象是Spring框架的一大优势,无论是全局事务还是本地事务,JTA、JDBC、Hibernate还是JPA,Spring都使用统一的编程模型,使得应用程序可以很容易地在全局事务与本地事务,或者不同的事务框架之间进行切换。

img

一致的事务模型

  • JDBC/Hibernate/myBatis

  • DataSource/JTA

Spring 事务抽象的关键接口

/**
 * This is the central interface in Spring's transaction infrastructure.
 * Applications can use this directly, but it is not primarily meant as API:
 * Typically, applications will work with either TransactionTemplate or
 * declarative transaction demarcation through AOP.
 */
public interface PlatformTransactionManager extends TransactionManager {

    TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
            throws TransactionException;

    void commit(TransactionStatus status) throws TransactionException;

    void rollback(TransactionStatus status) throws TransactionException;
}
  • DataSourceTransactionManager

  • HibernateTransactionManager

  • JtaTransactionManager

spring的事务属性定义接口

Spring事务管理的五大属性:隔离级别传播行为是否只读事务超时回滚规则

/**
 * Interface that defines Spring-compliant transaction properties.
 * Based on the propagation behavior definitions analogous to EJB CMT attributes.
 */
public interface TransactionDefinition {
}

Spring事务状态接口


/**
 * Representation of the status of a transaction.
 */
public interface TransactionStatus extends TransactionExecution, SavepointManager, Flushable {
    
	Object createSavepoint() throws TransactionException;

	void rollbackToSavepoint(Object savepoint) throws TransactionException;

	void releaseSavepoint(Object savepoint) throws TransactionException;

	boolean hasSavepoint();

	void flush();
}

Spring 编程式事务

TransactionTemplate

  • TransactionCallback

  • TransactionCallbackWithoutResult

PlatformTransactionManager

@Autowired
private TransactionTemplate transactionTemplate;
@Autowired
private JdbcTemplate jdbcTemplate;

@Override
public void run(String... args) throws Exception {
	transactionTemplate.execute(new TransactionCallbackWithoutResult() {
		@Override
		protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
			jdbcTemplate.execute("INSERT INTO FOO (ID, BAR) VALUES (1, 'aaa')");
			log.info("COUNT IN TRANSACTION: {}", getCount());
			transactionStatus.setRollbackOnly();
		}
	});
}

Spring 声明式事务

Spring采用AOP来实现声明式事务

@EnableTransactionManagement

@Transactional

image-20210330170637040

代理对象生成的核心类是AbstractAutoProxyCreator

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    if (bean != null) {
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        if (!this.earlyProxyReferences.contains(cacheKey)) {
            // 创建代理对象
            return wrapIfNecessary(bean, beanName, cacheKey);
        }
    }
    return bean;
}

Spring事务管理的五大属性:隔离级别传播行为是否只读事务超时回滚规则

Spring事务传播性

Spring 事务传播性是指当多个含有事务的方法嵌套调用时,处理事务的规则

PROPAGATION_REQUIRED

Spring 默认的事务传播行为,支持当前事务;如果不存在,则创建一个新的。类似于同名的EJB事务属性。这个传播行为可以保证多个嵌套的事务方法在同一个事务内执行,同时回滚

@Service
public class ServiceA {
    @Autowired
    private ServiceB serviceB;
    
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public void methodA() {
        //1.1 做自己的入库操作
        insert()
        System.out.println("save something to db");
        //1.2 调用服务 B 的入库操作
        serviceB.methodB();
    }
}

@Service
public class ServiceB {
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    public void methodB() {
        insert();
        System.out.println("save something to db");
        // 抛出异常
        throw new RuntimeException();
    }
}

try-catch异常

methodA 方法 catch 住异常了,但还是会被回滚

@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
public void methodA() {
    //1.1 做自己的入库操作
     insert()
    System.out.println("save something to db");
    //1.2 调用服务 B 的入库操作,catch 主异常
    try{
        serviceB.methodB();
    }catch(Exception e){
        
    }
}

PROPAGATION_REQUIRES_NEW

这个传播行为是每次都新开启一个事务。如果外层调用方已经开启了事务,就先把外层的事务挂起。回滚时外层事务方catch住了异常,并没有向外抛出,则外层事务不会回滚。否则,也将回滚。

@Service
public class ServiceB {
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
    public void methodB() {
            insert()
        System.out.println("save something to db");
    }
}

PROPAGATION_SUPPORTED

事务可有可⽆,不是必须的。如果外层调用方开启了事务,那当前方法加入到外层事务。否则,是用非事务方式执行。

PROPAGATION_NOT_SUPPORTED

不⽀持事务,按⾮事务⽅式运⾏。如果外层调用方已经开启了事务,就先把外层的事务挂起。

PROPAGATION_NEVER

不⽀持事务,如果外层有事务则抛异常

IllegalTransactionStateException(
                    "Existing transaction found for transaction marked with propagation 'never'")

PROPAGATION_MANDATORY

外层⼀定要有事务,不然就抛异常

IllegalTransactionStateException(
                    "No existing transaction found for transaction marked with propagation 'mandatory'")

PROPAGATION_NESTED

当外层调用方存在事务时,当前方法合并到外层事务,如果外层不存在事务,就当前开启事务,这点和 PROPAGATION_REQUIRED 传播性一致,不同的是,PROPAGATION_NESTED传播行为的特点是可以保存状态保存点,当事务回滚时,可以回滚到某一个保存点上,从而避免所有嵌套事务都回滚。

@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
public void methodA() {
    //1.1 做自己的入库操作
    insert()
    System.out.println("save something to db");
    //1.2 调用服务 B 的入库操作
    try{
    	serviceB.methodB();
    }catch(Exception e) { }
    //1.3 执行更新操作
    update();
}

@Service
public class ServiceB {
		@Transactional(rollbackFor = Exception.class, propagation = Propagation.NESTED)
    public void methodB() {
            insert()
        System.out.println("save something to db");
    }
}

事务不生效的情况

  • 除非特殊配置(比如使用 AspectJ 静态织入实现 AOP),否则只有定义在 public 方法上的 @Transactional 才能生效。原因是,Spring 默认通过动态代理的方式实现 AOP,对目标方法进行增强,private 方法无法代理到,Spring 自然也无法动态增强事务处理逻辑。
  • 必须通过代理过的类从外部调用目标方法才能生效。Spring 通过 AOP 技术对方法进行增强,要调用增强过的方法必然是调用代理后的对象。
@Service
@Slf4j
public class UserService {
    @Autowired
    private UserRepository userRepository;

    //一个公共方法供Controller调用,内部调用事务性的私有方法
    public int createUserWrong1(String name) {
        try {
            this.createUserPrivate(new UserEntity(name));
        } catch (Exception ex) {
            log.error("create user failed because {}", ex.getMessage());
        }
        return userRepository.findByName(name).size();
    }

    //标记了@Transactional的private方法
    @Transactional
    private void createUserPrivate(UserEntity entity) {
        userRepository.save(entity);
        if (entity.getName().contains("test"))
            throw new RuntimeException("invalid username!");
    }

    //根据用户名查询用户数
    public int getUserCount(String name) {
        return userRepository.findByName(name).size();
    }
}

事务不回滚的情况

* 只有异常传播出了标记了 @Transactional 注解的方法,事务才能回滚。

* 默认情况下,出现 RuntimeException(非受检异常)或 Error 的时候,Spring 才会回滚事务。

DefaultTransactionAttribute

@Overridepublic boolean rollbackOn(Throwable ex) { 
  return (ex instanceof RuntimeException || ex instanceof Error);
}
@Service
@Slf4j
public class UserService {
    @Autowired
    private UserRepository userRepository;
    
    //异常无法传播出方法,导致事务无法回滚
    @Transactional
    public void createUserWrong1(String name) {
        try {
            userRepository.save(new UserEntity(name));
            throw new RuntimeException("error");
        } catch (Exception ex) {
            log.error("create user failed", ex);
        }
    }

    //即使出了受检异常也无法让事务回滚
    @Transactional
    public void createUserWrong2(String name) throws IOException {
        userRepository.save(new UserEntity(name));
        otherTask();
    }

    //因为文件不存在,一定会抛出一个IOException
    private void otherTask() throws IOException {
        Files.readAllLines(Paths.get("file-that-not-exist"));
    }
}

手动设置让当前事务处于回滚状态

@Transactional
public void createUserRight1(String name) {
    try {
        userRepository.save(new UserEntity(name));
        throw new RuntimeException("error");
    } catch (Exception ex) {
        log.error("create user failed", ex);
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    }
}

Spring事务切面

事务拦截器TransactionInterceptorinvoke方法中,通过调用父类TransactionAspectSupportinvokeWithinTransaction方法进行事务处理

// TransactionInterceptor父类TransactionAspectSupport.class
protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation) throws Throwable {
    // If the transaction attribute is null, the method is non-transactional.
    // 查询目标方法事务属性、确定事务管理器、构造连接点标识(用于确认事务名称)
    final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);
    final PlatformTransactionManager tm = determineTransactionManager(txAttr);
    final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

    if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
        // 事务获取
        TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
        Object retVal = null;
        try {
            // 通过回调执行目标方法
            retVal = invocation.proceedWithInvocation();
        }
        catch (Throwable ex) {
            // 目标方法执行抛出异常,根据异常类型执行事务提交或者回滚操作
            completeTransactionAfterThrowing(txInfo, ex);
            throw ex;
        }
        finally {
            // 清理当前线程事务信息
            cleanupTransactionInfo(txInfo);
        }
        // 目标方法执行成功,提交事务
        commitTransactionAfterReturning(txInfo);
        return retVal;
    } else {
        // 带回调的事务执行处理,一般用于编程式事务
        ...
    }
}

Spring事务同步

提到事务传播机制时,我们经常提到一个条件“如果当前已有事务”,那么Spring是如何知道当前是否已经开启了事务呢?在AbstractPlatformTransactionManager中是这样做的:

// AbstractPlatformTransactionManager.class
@Override
public final TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException {
    Object transaction = doGetTransaction();
    // 参数为null时构造默认值
    ...
    if (isExistingTransaction(transaction)) {
        // Existing transaction found -> check propagation behavior to find out how to behave.
        return handleExistingTransaction(definition, transaction, debugEnabled);
    }
    ...

// 获取当前事务对象
protected abstract Object doGetTransaction() throws TransactionException;

// 判断当前事务对象是否包含活跃事务
protected boolean isExistingTransaction(Object transaction) throws TransactionException {
    return false;
}

img

TransactionSynchronizationManager通过ThreadLocal对象在当前线程记录了resourcessynchronizations属性。resources是一个HashMap,用于记录当前参与事务的事务资源,方便进行事务同步,在DataSourceTransactionManager的例子中就是以dataSource作为key,保存了数据库连接,这样在同一个线程中,不同的方法调用就可以通过dataSource获取相同的数据库连接,从而保证所有操作在一个事务中进行。synchronizations属性是一个TransactionSynchronization对象的集合,AbstractPlatformTransactionManager类中定义了事务操作各个阶段的调用流程