第六章 Spring事务传播属性分析笔记

本文详细介绍了Spring事务的创建与开启过程,包括从数据源获取连接、设置只读事务、关闭自动提交、建立ThreadLocal绑定,以及嵌套事务分析。同时,探讨了事务传播属性,如PROPAGATION_REQUIRES_NEW和PROPAGATION_NESTED的处理。最后,对比了注解事务与编程式事务的优缺点,并给出了使用示例。
摘要由CSDN通过智能技术生成

接着上面第五章关于事务传播属性的源码分析:

一、创建事务状态对象开启事务

第一:创建事务状态对象,事务状态就是记录事务流转过程中状态数据的,有一个数据非常重要,直接决定了提交,回滚和恢复绑定操作,就是newTransaction属性,这个属性要牢记。如果为true就代表当前事务允许单独提交和回滚,一般是第一次创建事务或者事务传播属性为PROPAGATION_REQUIRES_NEW的时候。如果为false则当前事务不能单独提交和回滚。

第二:doBegin该方法则是开启事务的核心方法,点进去看看:

1、从数据源连接池中获取连接

org.springframework.jdbc.datasource.DataSourceTransactionManager#doBegin

protected void doBegin(Object transaction, TransactionDefinition definition) {
   DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
   Connection con = null;
   try {
      //如果没有数据库连接
      if (!txObject.hasConnectionHolder() || txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
         //从连接池里面获取连接
         Connection newCon = obtainDataSource().getConnection();
         if (logger.isDebugEnabled()) {
            logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
         }
         //把连接包装成ConnectionHolder,然后设置到事务对象中
         txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
      }

这块从数据源获取到连接对象,有一个数据源要提一下,就是动态切换数据源的一个抽象类:

AbstractRoutingDataSource,如果有多数据源的需求,可以自己定义数据源对象然后往自定义对象中设置目标数据源和默认数据源,通过切面控制动态切换逻辑:

1.1、动态数据源代码示例:

代码详见com.chj.datasource.DynamicDataSource,从ThreadLocal中获取连接字符串:

public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        String ds = DynamicDataSourceHolder.getDs();
        System.out.println("=========选择的数据源:" + ds);
        return ds;
    }
}

注意:ThreadLocal类,用来保存当前线程和数据源字符串的关系

public class DynamicDataSourceHolder {
    private static ThreadLocal<String> local = new ThreadLocal<String>();
    public static String getDs() {
        return local.get();
    }
    public static ThreadLocal getLocal() {
        return local;
    }
}

1.2、切面类代码示例:

通过获取调用方法上面的注解来获取选择的数据源字符串,然后把这个字符串设置到ThreadLocal中跟当前线程绑定:com.chj.aop.aspectj.AspectDs

@Component
@Aspect
@Order(-1)
public class AspectDs {
    @Before(value = "@annotation(targetSource)",argNames = "joinPoint,targetSource")
    public void xx(JoinPoint joinPoint, TargetSource targetSource) {
        System.out.println("========AspectDs.xx");
        String value = targetSource.value();
        if(value != null && !"".equals(value)) {
            DynamicDataSourceHolder.getLocal().set(value);
        } else {
            DynamicDataSourceHolder.getLocal().set("ds1");
        }
    }
}

1.3、动态数据源初始化:

com.chj.datasource.DataSourceConfiguration

@Bean
public DataSource dynamicDataSource() {
    ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
    try {
        comboPooledDataSource.setDriverClass(driverClass);
        comboPooledDataSource.setJdbcUrl(jdbcUrl);
        comboPooledDataSource.setUser(user);
        comboPooledDataSource.setPassword(password);
        comboPooledDataSource.setMinPoolSize(10);
        comboPooledDataSource.setMaxPoolSize(100);
        comboPooledDataSource.setMaxIdleTime(1800);
        comboPooledDataSource.setAcquireIncrement(3);
        comboPooledDataSource.setMaxStatements(1000);
        comboPooledDataSource.setInitialPoolSize(10);
        comboPooledDataSource.setIdleConnectionTestPeriod(60);
        comboPooledDataSource.setAcquireRetryAttempts(30);
        comboPooledDataSource.setBreakAfterAcquireFailure(false);
        comboPooledDataSource.setTestConnectionOnCheckout(false);
        comboPooledDataSource.setAcquireRetryDelay(100);
    } catch (PropertyVetoException e) {
        e.printStackTrace();
    }
    Map<Object, Object> targetDataSources = new HashMap<>();
    targetDataSources.put("ds1",comboPooledDataSource);
    DynamicDataSource dynamicDataSource = new DynamicDataSource();
    dynamicDataSource.setTargetDataSources(targetDataSources);
    dynamicDataSource.setDefaultTargetDataSource(comboPooledDataSource);
    return dynamicDataSource;
}

这里建立了字符串数据源对象的映射关系,然后把映射关系保存到了动态数据源对象中,动态数据源对象通过钩子方法determineCurrentLookupKey就可以从ThreadLocal中获取到字符串,然后根据字符串从映射关系中找到选择的数据源对象。

2、设置只读事务

从这一点设置的时间点开始(时间点a)到这个事务结束的过程中,其他事务所提交的数据,该事务将看不见。设置只读事务就是告诉数据库,我这个事务内没有新增、修改、删除操作只有查询操作,不需要数据库锁等操作,减少数据库压力。

org.springframework.jdbc.datasource.DataSourceTransactionManager#doBegin

prepareTransactionalConnection(con, definition);
protected void prepareTransactionalConnection(Connection con, TransactionDefinition definition)
      throws SQLException {
   if (isEnforceReadOnly() && definition.isReadOnly()) {
      Statement stmt = con.createStatement();
      try {
         stmt.executeUpdate("SET TRANSACTION READ ONLY");
      }finally {
         stmt.close();
      }
   }
}

3、把自动提交关闭

org.springframework.jdbc.datasource.DataSourceTransactionManager#doBegin

if (con.getAutoCommit()) {
   txObject.setMustRestoreAutoCommit(true);
   if (logger.isDebugEnabled()) {
      logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
   }
   //关闭连接的自动提交,其实这步就是开启了事务
   con.setAutoCommit(false);
}

4、建立ThreadLocal的绑定关系

这个绑定关系就是前面提到的,从绑定关系中可以拿到map,map中建立的是数据源对象和连接对象的映射。

// Bind the connection holder to the thread.
if (txObject.isNewConnectionHolder()) {
   //如果是新创建的事务,则建立当前线程和数据库连接的关系
   TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
}

org.springframework.transaction.support.TransactionSynchronizationManager#bindResource

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);
   }
   Object oldValue = map.put(actualKey, value);
   // Transparently suppress a ResourceHolder that was marked as void...
   if (oldValue instanceof ResourceHolder && ((ResourceHolder) oldValue).isVoid()) {
      oldValue = null;
   }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值