# Spring事务管理

# Spring事务的传播属性有哪些

参考答案

Spring事务的传播属性源码定义

    //Spring源码中事务传播属性的枚举类定义
	public enum Propagation {
    //Spring事务的默认传播属性。
    //支持当前事务,如果当前有事务,那么加入事务,如果当前没有事务则新建一个。
    //默认值: 0。    
    REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),    
    //如果当前有事务则加入,如果没有则不用事务。
    //默认值: 1。    
    SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS)
    //强制的,支持当前事务,如果当前没有事务,则抛出异常。(当前必须有事务)。
    //默认值: 2。    
    MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),
    //支持当前事务,如果当前有事务,则挂起当前事务,然后新创建一个事务,
    //如果当前没有事务,则自己创建一个事务。
    //默认值: 3。
    REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),
    //以非事务方式执行操作,如果当前存在事务就把当前事务挂起,
    //执行完后恢复事务(忽略当前事务)。
    //默认值: 4。    
    NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),
    //以非事务方式执行,如果当前存在事务,则抛出异常。(当前必须不能有事务)。
    //默认值: 5。       
    NEVER(TransactionDefinition.PROPAGATION_NEVER),
    //如果当前存在事务,则嵌套在当前事务中。
    //如果当前没有事务,则新建一个事务自己执行(和required一样)。
    //嵌套的事务使用保存点作为回滚点,当内部事务回滚时不会影响外部事物的提交。
    //但是外部回滚会把内部事务一起回滚回去。(这个和新建一个事务的区别)。
    //默认值: 6。           
    NESTED(TransactionDefinition.PROPAGATION_NESTED);

    private final int value;

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

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

# Spring事务的隔离级别

参考答案

隔离级别的源码定义

//Spring事务的隔离级别源码类
public enum Isolation {
   //默认事务隔离级别:-1
   DEFAULT(TransactionDefinition.ISOLATION_DEFAULT),
   //读未提交: 1
   READ_UNCOMMITTED(TransactionDefinition.ISOLATION_READ_UNCOMMITTED),
   //读提交: 2
   READ_COMMITTED(TransactionDefinition.ISOLATION_READ_COMMITTED),
   //可重复读:4 
   REPEATABLE_READ(TransactionDefinition.ISOLATION_REPEATABLE_READ),
   //串行化:8
   SERIALIZABLE(TransactionDefinition.ISOLATION_SERIALIZABLE);

    private final int value;

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

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

隔离级别的影响

名字 隔离级别 脏读 不可重复读 幻读
读未提交 ISOLATION_READ_UNCOMMITTED
读提交 ISOLATION_READ_COMMITTED 不会
可重复读 ISOLATION_REPEATABLE_READ 不会 不会
串行化 ISOLATION_SERIALIZABLE 不会 不会 不会

隔离级别的概念

  • ISOLATION_DEFAULT(默认级别) : 使用后端数据库默认的隔离级别。
  • ISOLATION_READ_UNCOMMITTED(读未提交): 允许读取尚未提交的更改。可能导致脏读、幻影读或不可重复读。
  • ISOLATION_READ_COMMITTED(读提交): 允许从已经提交的并发事务读取。可防止脏读,但幻影读和不可重复读仍可能会发生。
  • ISOLATION_REPEATABLE_READ(可重复读): 对相同字段的多次读取的结果是一致的,除非数据被当前事务本身改变。可防止脏读和不可重复读,但幻影读仍可能发生。
  • ISOLATION_SERIALIZABLE(串行化): 完全服从ACID的隔离级别,确保不发生脏读、不可重复读和幻影读。这在所有隔离级别中也是最慢的,因为它通常是通过完全锁定当前事务所涉及的数据表来完成的。

扩展阅读

  • 脏读:一个事务可以读取另一个未提交事务的数据。需要注意的是这里针对的是数据本身,可以理解为针对单笔数据。

    分析:事务A开启进行了查询数据,同时事务B开启,修改了其中一笔数据,但并未提交,这时事务A又进行了查询,这时就有两笔不一样的数据,然后事务B并没有结束,事务B进行了事务回滚,这样事务A就读取了事务B修改后未提交的数据。因为这里出现这种根本原因是未对数据进行提交,就进行了读取。

    解决方案:设置未读提交即可。

  • 不可重复读:一个事务进行读取,分别读取到了不同的数据。需要注意的是这里针对的是数据本身,可以理解为针对单笔数据,重点是对数据的修改和删除,所以对行加锁就可以解决。

    分析:事务A对数据进行查询,这时事物B开启,对其中一笔数据进行了修改,然后进行了提交(这里进行了提交),然后事务A又对数据进行了查询,发现同一笔不同了,所以事务A读取了两笔不同的数据,两次读取同笔数据有了不同的数据。出现这种根本原因是在事务A进行读操作时,其他事务对数据进行了修改。

    解决方案:读提交是不足以解决的,需进行可重复读(Repeatable read)就能解决,加行级锁。

  • 幻读:一个事务进行读取,分别读取到了不同的数据。需要注意的是这里针对的是数据条数,可以理解为针对多笔数据是个数据集,重点是对数据的新增。

    分析:事务A对数据进行查询,这时事物B开启,对其中一笔数据进行了新增,然后进行了提交(这里进行了提交),然后事务A又对数据进行了查询,发现查询所得的结果集是不一样的。幻读针对的是多笔记录。读提交(Read Committed)是不足以解决的,需进行Serializable 序列化就能解决。

    解决方案:对表加锁就可以解决。

# 数据库是如何实现隔离级别的

参考答案

在实现上,数据库里面会创建一个视图,访问的时候以视图的逻辑结果为准。

  • 可重复读隔离级别,这个视图在事务启动时创建的,整个事务存在期间都用这个视图。
  • 读未提交隔离级别,直接返回记录上的最新值,没有视图概念,
  • 串行化隔离级别,直接使用加锁的方式来避免并行访问。

# Spring事务管理是如何实现的

参考答案

Spring事务管理是基于Spring AOP实现的,底层采用的是动态代理,处理时会拦截到带有@Transactional的注解,对目标类生成代理类,从而进行对目标方法的拦截,执行前开启事务,执行后提交事务。

扩展阅读

以代理方式声明的事务管理,只有目标方法是public修饰,并且必须在代理类外部调用,如果直接在目标类里面调用(内部类形式),事务依然不会生效。

注意事项

Spring团队建议您仅使用注释对具体类(以及具体类的方法)进行@Transactional注释,而不是对接口进行注释。您当然可以将@Transactional注释放置在接口(或接口方法)上,但这仅在您使用基于接口的代理时才可以预期地起作用。Java注释不是从接口继承的事实意味着,如果您使用基于类的代理(proxy-target-class="true")或基于编织的方面(mode="aspectj"),则代理和编织基础结构无法识别事务设置,并且不会包装对象在交易代理中。

# Spring事务@Transactional有哪些属性

参考答案

@Transactional默认设置如下:

  • 传播属性为 PROPAGATION_REQUIRED

  • 隔离级别为 ISOLATION_DEFAULT

  • 事务是读写的

  • 事务超时默认为基础事务系统的默认超时,如果不支持超时,则默认为无。

  • 任何RuntimeException触发器都会回滚,而任何检查Exception都不会回滚。

属性 类型 描述
value String 可选的限定词,指定要使用的事务管理器。
propagation enumPropagation 可选的传播设置。
isolation enumIsolation 可选的隔离级别。仅适用于REQUIRED或的传播值REQUIRES_NEW
timeout int (以秒为单位) 可选的事务超时。仅适用于REQUIRED或的传播值REQUIRES_NEW
readOnly boolean 读写与只读事务。仅适用于REQUIRED或的值REQUIRES_NEW
rollbackFor Class对象数组,必须从中派生Throwable. 必须引起回滚的异常类的可选数组。
rollbackForClassName 类名数组。这些类必须源自Throwable. 必须引起回滚的异常类名称的可选数组。
noRollbackFor Class对象数组,必须从中派生Throwable. 不能导致回滚的异常类的可选数组。
noRollbackForClassName String类名数组,必须从中派生Throwable. 不能引起回滚的异常类名称的可选数组。

# Spring事务核心类有哪些

参考答案

核心接口 I

  • PlanformTransactionManager: 定义了平台统一的获取事务、提交事务、回滚事务的方法,具体由各个厂商来实现。
  • TransactionStatus:定义了是否是新事务、是否有保存点、事务是否结束等状态值。
  • TransactionDefinition:定义了事务的传播属性、隔离级别、只读以及超时时间等属性值。

注解类 @

  • EnableTransactionManagement:开始事务的注解类。

核心类 C

  • TransactionAspectSupport:其invokeWithinTransaction核心方法,调用内部事务处理。
  • TransactionInterceptor: 事务拦截器,实现了AOP核心类MethodInterceptor,调用其invoke方法,执行调用内部事务处理。
  • AutoProxyRegistrar: 注册BeanDefinitions,实例化bean。

配置类 C

  • ProxyTransactionManagementConfiguration:定义事务增强器、拦截器。

枚举类 E

  • Propagation: 传播属性的枚举类。
  • Isolation:事务隔离级别的枚举类。

# Spring事务是否可继承

参考答案

@Transactional注解的作用可以传播到子类,即如果父类标了子类就不用标了。但倒过来就不行了。 子类标了,并不会传到父类,所以父类方法不会有事务。父类方法需要在子类中重新声明而参与到子类上的注解,这样才会有事务。

# Spring事务管理方式有哪两种

参考答案

  • 声明式事务管理:通过类或方法上添加@Transactional注解。
  • 编程式事务管理:通过定义TransactionTemplate对象来调用执行doInTransaction方法。
    // 编程式事务
    public class SimpleService implements Service {
    // 所有方法共享一个TransactionTemplate实例
    private final TransactionTemplate transactionTemplate;
    // 使用构造函数注入来提供PlatformTransactionManager
    public SimpleService(PlatformTransactionManager transactionManager) {
        this.transactionTemplate = new TransactionTemplate(transactionManager);
    }
    // 此方法可以不用添加@Transactional注解
    public Object someServiceMethod() {
        return transactionTemplate.execute(new TransactionCallback() {
            // 此方法中的代码在事务上下文中执行
            public Object doInTransaction(TransactionStatus status) {
                updateOperation1();
                return resultOfUpdateOperation2();
            }
        });
      }
   }

# Spring事务回滚什么时候触发

参考答案

以下通过Spring事务相关源码来分析事务回滚触发条件

Spring事务处理类源码

package org.springframework.transaction.interceptor.TransactionAspectSupport

protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {
// 判断当前是否存在事务
if (txInfo != null && txInfo.getTransactionStatus() != null) {
if (logger.isTraceEnabled()) {
logger.trace("Completing transaction for [" +txInfo.getJoinpointIdentification() +"] after exception: " + ex);
}
/**
*  TODO : 判断是否满足回滚条件
*  只有(RuntimeException或者error异常)才执行回滚操作
*  @see org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn
*/
if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
try {
// 处理回滚
txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
}
catch (TransactionSystemException ex2) {
logger.error("Application exception overridden by rollback exception", ex);
ex2.initApplicationException(ex);
throw ex2;
}
catch (RuntimeException | Error ex2) {
logger.error("Application exception overridden by rollback exception", ex);
throw ex2;
}
}
else {
// We don't roll back on this exception.
// Will still roll back if TransactionStatus.isRollbackOnly() is true.
// 如果 TransactionStatus.isRollbackOnly()为真,则仍然回滚。
try {
// 如果不满足回滚条件出现异常也会继续提交
txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
}
catch (TransactionSystemException ex2) {
logger.error("Application exception overridden by commit exception", ex);
ex2.initApplicationException(ex);
throw ex2;
}
catch (RuntimeException | Error ex2) {
logger.error("Application exception overridden by commit exception", ex);
throw ex2;
}
}
}
}

回滚条件的判断入口

package org.springframework.transaction.interceptor.DefaultTransactionAttribute
/**
 *  TODO : 回滚达成条件
 *  默认只有运行时异常或者Error级别的异常才返回true,进行回滚
 *  此外如果相对Exception异常进行回滚,可以对注解进行如下设置:
 *  @Transactional( rollbackFor = Exception.class, noRollbackFor = RuntimeException.class)
 *  @see  org.springframework.transaction.annotation#Transactional
 */
public boolean rollbackOn(Throwable ex) {
    return (ex instanceof RuntimeException || ex instanceof Error);
}
/**
 *  TODO : 通过编程方式回滚
 *  但此过程具有很大的侵入性,并将您的代码紧密耦合到Spring Framework的事务基础结构。
 *  Spring官方强烈建议尽可能使用声明性方法进行回滚,不用编程式触发。
 */
public void resolvePosition() {
    try {
        // some business logic...
    } catch (NoProductInStockException ex) {
        // 编程式触发回滚
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    }
}

# Spring事务内部如何存储绑定资源

参考答案

Spring事务中,对资源的存储和管理由抽象类 TransactionSynchronizationManager 来负责,统一存储到ThreadLocal中,以保证各个资源相对独立,对应的ThreadLocal存储如下:

  • ThreadLocal<Map<Object, Object>> :事务
  • ThreadLocal<Set<TransactionSynchronization>> :事务同步器
  • ThreadLocal<String> :当前事务名称
  • ThreadLocal<Boolean> :当前事务只读状态
  • ThreadLocal<Integer>:事务隔离级别
  • ThreadLocal<Boolean>:事务激活状态

TransactionSynchronizationManager源码

public abstract class TransactionSynchronizationManager {

	private static final Log logger = LogFactory.getLog(TransactionSynchronizationManager.class);

	private static final ThreadLocal<Map<Object, Object>> resources =
			new NamedThreadLocal<>("Transactional resources");

	private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
			new NamedThreadLocal<>("Transaction synchronizations");

	private static final ThreadLocal<String> currentTransactionName =
			new NamedThreadLocal<>("Current transaction name");

	private static final ThreadLocal<Boolean> currentTransactionReadOnly =
			new NamedThreadLocal<>("Current transaction read-only status");

	private static final ThreadLocal<Integer> currentTransactionIsolationLevel =
			new NamedThreadLocal<>("Current transaction isolation level");

	private static final ThreadLocal<Boolean> actualTransactionActive =
			new NamedThreadLocal<>("Actual transaction active");

   /**
   *  TODO : 事务资源绑定
   *   建立当前线程和Map的关系
   *   Map保存的key是数据源,value是数据库连接
   */
   public static void bindResource(Object key, Object value) throws IllegalStateException {
		Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
		Assert.notNull(value, "Value must not be null");
		Map<Object, Object> map = resources.get();
		// set ThreadLocal Map if none found
		if (map == null) {
			map = new HashMap<>();
			resources.set(map);
		}
		//DataSource生成的actualKey为key值
		//ConnectionHolder作为value值
		//为了标识连接从哪个数据源来
		Object oldValue = map.put(actualKey, value);
		// Transparently suppress a ResourceHolder that was marked as void...
		if (oldValue instanceof ResourceHolder && ((ResourceHolder) oldValue).isVoid()) {
			oldValue = null;
		}
		if (oldValue != null) {
			throw new IllegalStateException("Already value [" + oldValue + "] for key [" +
					actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");
		}
		if (logger.isTraceEnabled()) {
			logger.trace("Bound value [" + value + "] for key [" + actualKey + "] to thread [" +
					Thread.currentThread().getName() + "]");
		}
	}

}

扩展阅读

Spring 的事务管理是基于 JDBC 的事务的,如果要保证事务正确的执行,必须保证与数据库的链接是同一个。 在某个线程第一次调用时候, 首先建立当前线程和Map的关系,也就是ThreadLocal<Map<Object, Object>>。接着封装Map资源为:key值为DataSource生成actualKey【Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);】value值为DataSource获得的Connection对象封装后的ConnectionHolder

# 如何区别嵌套事务使用的连接对象是否是同一个

参考答案

查看当前事务操作之前是否有挂起业务,如果有挂起,则新建数据库连接,否则使用同一个数据库连接。

  • PROPAGATION_NESTED ,NESTED 事务传播属性就是使用上一个事务的连接对象。

# 为何嵌套事务执行完自动释放回滚点

参考答案

# 子事务没有try、catch发生异常了会触发全局回滚

参考答案

由于子事务发生异常被最外层的Spring事务捕获,因此不会按照保存点来回滚数据,而是直接全部回滚,如果只想再子事务内部自行回滚,则需要在子事务内部进行try、catch处理,不要向上抛出异常。