jdbc一个connection对应的是一个事物

Spring事务管理中的Connection-Passing

对于层次划分清晰的应用来说,我们通常将事务管理放在Service层,而将数据访问逻辑放在Dao层,这样做的目的是不用因为将事务管理代码放在DAO层,而降低数据访问逻辑的重要性,也可以将Service层根据相应逻辑,来决定提交或者回滚事务。一般的Service对象可能需要在同一个业务方法中调用多个数据访问对象的方法。比如:

public void serviceMethod(){
	dao1.add();
	dao2.delete();
}

因为JDBC局部事务是控制是由java.sql.Connection来完成的,要保证两个DAO的数据访问处于一个事务中,我们需要保证他们使用的是同一个java.sql.Connection.
通常采用称为connection-passing的方式,即为当前同一个事务的各个dao的数据访问方法传递当前事务对应的同一个Connection。
传递java.sql.Connection,最好的办法是整个事务对应的java.sql.Connection实例放到统一的一个地方,但要保证每个业务请求的Connection又能各不干扰。或许你已经想到了ThreadLocal。对该类不理解的可以去看我之前的那篇文章ThreadLocal的一些理解。今天我们看看Spring是如何控制ThreadLocal为其服务的。
首先从开始事务进行分析

protected void doBegin(Object transaction, TransactionDefinition definition) {
        DataSourceTransactionManager.DataSourceTransactionObject txObject = (DataSourceTransactionManager.DataSourceTransactionObject)transaction;
        Connection con = null;
        try {
            if (txObject.getConnectionHolder() == null || txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
//如果当前事务ConnectionHolder为空或者处在事务同步中
                Connection newCon = this.dataSource.getConnection();
//获取数据库连接
                if (this.logger.isDebugEnabled()) {
                    this.logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
                }
                txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
//true代表这是新的连接
//2
            }
            txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
            con = txObject.getConnectionHolder().getConnection();
            Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
            txObject.setPreviousIsolationLevel(previousIsolationLevel);
            if (con.getAutoCommit()) {
                txObject.setMustRestoreAutoCommit(true);
                if (this.logger.isDebugEnabled()) {
                    this.logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
                }
                con.setAutoCommit(false);
            }
            txObject.getConnectionHolder().setTransactionActive(true);
//激活事务
            int timeout = this.determineTimeout(definition);
            if (timeout != -1) {
                txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
            }
            if (txObject.isNewConnectionHolder()) {
//如果是本次是新的连接
                TransactionSynchronizationManager.bindResource(this.getDataSource(), txObject.getConnectionHolder());
//将该ConnectionHolder绑定到当前线程 下面详细讲解
            }
        } catch (Throwable var7) {
            if (txObject.isNewConnectionHolder()) {
                DataSourceUtils.releaseConnection(con, this.dataSource);
                txObject.setConnectionHolder((ConnectionHolder)null, false);
            }
            throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", var7);
        }
    }

上面方法主要就是获取连接并设置事务的各种属性信息,关键的是将DataSource和txObject.getConnectionHolder()传入了bindResource中,ConnectionHolder对象就包装着本次事务所获取的连接。我们来看看bindResource方法

##该方法在TransactionSynchronizationManager类中##

private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal("Transactional resources");
... //省略部分代码
 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 = (Map)resources.get();
        if (map == null) {
            map = new HashMap();
            resources.set(map);
        }
        Object oldValue = ((Map)map).put(actualKey, value);
        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() + "]");
        } else {
            if (logger.isTraceEnabled()) {
                logger.trace("Bound value [" + value + "] for key [" + actualKey + "] to thread [" + Thread.currentThread().getName() + "]");
            }
        }
    }

该方法我就不逐步分析了,如果熟悉ThreadLocal机制的同学一定也会很快理解。总的来说,该方法就是将传进来的key和value作为键值对存储在HashMap中,再把HashMap存到ThreadLocal中。此后每个线程从该ThreadLocal中get到的一定是属于自己线程的HashMap,从而取值。

静态变量:

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

TransactionSynchronizationManager内部用ThreadLocal对象存储资源,ThreadLocal存储的为DataSource生成的actualKey为key值和ConnectionHolder作为value值封装成的Map。

在某个线程第一次调用时候,封装Map资源为:key值为DataSource生成actualKey【Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);】value值为DataSource获得的Connection对象封装后的ConnectionHolder。


ok,现在我们知道了Connection是如何绑定线程并放在Spring容器中,继续看是在何时需要获取该Connection的吧,我们给TransactionSynchronizationManager类中的getResource方法打上断点。


调用getResource方法来获取ConnectionHandler的时间点有下面这些:

  1. 执行sql的时候,会通过DataSourceUtils#doGetConnection方法调用getResource的连接,这里就不放代码了。
  2. 事务开启、提交或者回滚。
  3. 连接的释放
  4. 在获取DataSourceTransactionObject时,代码如下。
protected boolean isExistingTransaction(Object transaction) {
      DataSourceTransactionManager.DataSourceTransactionObject txObject = (DataSourceTransactionManager.DataSourceTransactionObject)transaction;
      return txObject.getConnectionHolder() != null && txObject.getConnectionHolder().isTransactionActive();
  }

DataSourceTransactionManager判断是否存在当前事务的两个标准就是ConnectionHolder是否为空和TransactionActive是否为true,如果之前该连接上调用了doBegin创建事务,则这里肯定会返回true。
完成本次事务的所有业务逻辑之后则会在提交事务完成后,调用TransactionSynchronizationManager类的doUnbindResource方法

private static Object doUnbindResource(Object actualKey) {
        Map<Object, Object> map = (Map)resources.get();
        if (map == null) {
            return null;
        } else {
            Object value = map.remove(actualKey);
            if (map.isEmpty()) {
                resources.remove();
            }
            if (value instanceof ResourceHolder && ((ResourceHolder)value).isVoid()) {
                value = null;
            }
            if (value != null && logger.isTraceEnabled()) {
                logger.trace("Removed value [" + value + "] for key [" + actualKey + "] from thread [" + Thread.currentThread().getName() + "]");
            }
            return value;
        }
    }

该方法就是移除指定资源或者Map。
总结:
因为代理的原因,Spring的Connection-Passing机制确保每个被代理事务管理的方法中所有同一个线程(为什么要强调这个,因为事务过程的Connection就是用ThreadLocal管理的)
的对数据库操作都在同一个Connection(Session会话)中执行,因为提交和回滚都以Connection为单位。即不同的Connection提交和回滚不会影响另一个Connection的执行过程。
以上就是Spring利用ThreadLocal来保证每个线程调用的每个业务方法中使用的是同一个Connection,以确保事务的控制。

思考一下

@Override
	public void transfer(final String inUser, final String outUser, final int money) throws Exception{

		new Thread(new Runnable() {
			@Override
			public void run()
			{
				manager.out(outUser, money); //1
			}
		}).start();
		int i=1/0;//拟突发断电
		accountDao.in(inUser, money);
	}

如上会回滚注释1处的执行代码吗?

答案是不会。前面我们说过Connection是绑定在线程的。transfer方法是一个事务方法。Spring事务管理器在事务方法和事务结束
过程中都会获得绑定在该线程的Connection,因此事务的提交和回滚只针对该Connection有效,也就是说其他线程调用的数据访问方法
不会由当前事务方法的Connection管理。因此如上所示,注释1处的代码在另一个线程中执行,其Connection和当前transfer方法的事务
Connection大概率不是同一个。*(也有可能是同一个,有可能事务rollback之后释放连接,刚好轮到该线程获取上次释放的连接,我们可以设置执行延迟,
但无论如何已经是两个事务边界了。)因此,在断电之后,注释1处的代码并没有回滚。

理解Spring事务管理是由JDBC的Connection来确定事务边界的有助于理解后续的Spring事务处理分布式事务的局限性。因此分布式情况下,可能有多个操作
都运行在不同机器上的服务方法组合,因为我们需要知道所有方法的结果,并且进行全部提交和全部回滚,以确保一致性,而普通的事务管理无法做到这些,
如何有效地确保这些方法执行的正确性,当然这就属于分布式事务的范畴了,我们这里不做讨论。

面试题:

一个Controller调用两个Service,这两Service又都分别调用两个Dao,问其中用到了几个数据库连接池的连接?
分情况讨论:

  • 如果这两个service,每个Service调用的两个dao都是在同一个线程同一个数据源并且开启了事务管理,则每个service都会使用一个Connection。
  • 如果不在Spring事务管理下,则无论在不在同一个数据源在不在同一个线程,每次调用Dao执行sql,都会使用DataSourceUtils.doGetConnection获取一个连接,执行完之后释放。
    所以该题目应该是用到2-4个数据库连接。

    数据源就是我们配置的DataSource。(即便一个数据源使用了多个Mysql数据库也是在一个连接中,url不指定Database,sql语句中指定Database,
    例如spring.datasource.url=jdbc:mysql://localhost:3306,此时就可以sql操作多个数据库,通过database.table,此时在同一个事务管理下的service,即便使用了两个Dao,
    操作两个完全不同的数据库,d1.t1,d2.t2,但是因为在同一个数据源中,同一个线程中,则也会共用一个数据库连接,事务也会生效)

(分布式事务初了解)[http://www.importnew.com/26349.html]
(浅谈事务和一致性:刚性or柔性?)[https://juejin.im/post/5aa8b8636fb9a028c67567c6]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值