死磕Spring之AOP篇 - Spring 事务详解 (10)

如果上面 transaction 数据源事务对象已有 Connection 连接,且正处于一个事务中,表示当前线程已经在一个事务中了

// DataSourceTransactionManager.java @Override protected boolean isExistingTransaction(Object transaction) { // 获取这个数据源事务对象 DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction; // 是否已有 Connection 连接,且正处于一个事务中 return (txObject.hasConnectionHolder() && txObject.getConnectionHolder().isTransactionActive()); }

调用 handleExistingTransaction(..) 方法,根据 Spring 事务传播级别进行不同的处理,同时创建一个 DefaultTransactionStatus 事务状态对象并返回

否则,当前线程没有事务

如果是 MANDATORY 事务传播级别(当前线程已经在一个事务中,则加入该事务,否则抛出异常),因为当前线程没有事务,此时抛出异常

否则,如果事务传播级别为 REQUIRED | REQUIRES_NEW | NESTED

创建一个“空”的被挂起的资源对象

创建一个 DefaultTransactionStatus 事务状态对象,设置相关属性,这里 newTransaction 参数为 true(记住),表示是一个新的事务

【关键】调用 doBegin(..) 方法,执行 begin 操作,如果没有 Connection 数据库连接,则通过 DataSource 创建一个新的连接;会设置 Connection 的隔离级别、是否只读,并执行 Connection#setAutoCommit(false) 方法,不自动提交;同时将 DataSource(数据源对象)和 ConnectionHolder(数据库连接持有者)保存至 ThreadLocal 中

借助 TransactionSynchronizationManager 事务同步管理器设置相关 ThreadLocal 变量,例如当前事务的隔离级别、是否只读、是否处于事务中等

返回上面创建的 DefaultTransactionStatus 事务状态对象

否则,创建一个“空”的事务状态对象

创建一个 DefaultTransactionStatus 事务状态对象,不使用事务

整个处理过程稍微有点复杂,不过流程非常清晰,当没有事务时,根据事务的传播级别决定是否需要创建一个事务,创建过程主要在上面的第 4.2.3 步;当正处于一个事务中时,在 3.1 步,根据事务的传播级别判断是否需要创建一个新的事务,或者加入该事务等操作;接下来我们来看看这两种情况

1.2 doBegin 方法

doBegin(..) 方法,创建一个新的事务,如下:

// AbstractPlatformTransactionManager.java @Override protected void doBegin(Object transaction, TransactionDefinition definition) { DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction; Connection con = null; try { // <1> 如果没有 Connection 数据库连接,或者连接处于事务同步状态 if (!txObject.hasConnectionHolder() || txObject.getConnectionHolder().isSynchronizedWithTransaction()) { // <1.1> 通过 DataSource 创建一个 Connection 数据库连接 Connection newCon = obtainDataSource().getConnection(); if (logger.isDebugEnabled()) { logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction"); } // <1.2> 重置 ConnectionHolder 连接持有者,封装刚创建的数据库连接 txObject.setConnectionHolder(new ConnectionHolder(newCon), true); } // <2> 设置ConnectionHolder 连接持有者处于事务同步中 txObject.getConnectionHolder().setSynchronizedWithTransaction(true); // <3> 获取 Connection 数据库连接 con = txObject.getConnectionHolder().getConnection(); // <4> 设置 Connection 是否只读和事务隔离性 Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition); // <5> 保存之前的事务隔离级别(被挂起的事务) txObject.setPreviousIsolationLevel(previousIsolationLevel); // Switch to manual commit if necessary. This is very expensive in some JDBC drivers, // so we don't want to do it unnecessarily (for example if we've explicitly // configured the connection pool to set it already). // <6> 将自动提交关闭 if (con.getAutoCommit()) { txObject.setMustRestoreAutoCommit(true); if (logger.isDebugEnabled()) { logger.debug("Switching JDBC Connection [" + con + "] to manual commit"); } con.setAutoCommit(false); } // <7> 如果需要强制设置只读(默认不需要),且连接本身是只读的,则这里提前设置事务的只读性 prepareTransactionalConnection(con, definition); // <8> 当前 Connection 数据库连接正处于一个事务中 txObject.getConnectionHolder().setTransactionActive(true); // <9> 设置超时时间 int timeout = determineTimeout(definition); if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) { txObject.getConnectionHolder().setTimeoutInSeconds(timeout); } // Bind the connection holder to the thread. // <10> 如果是新的事务,则将 DataSource(数据源对象)和 ConnectionHolder(数据库连接持有者)保存至 ThreadLocal 中 if (txObject.isNewConnectionHolder()) { TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder()); } } catch (Throwable ex) { if (txObject.isNewConnectionHolder()) { DataSourceUtils.releaseConnection(con, obtainDataSource()); txObject.setConnectionHolder(null, false); } throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex); } }

过程如下:

如果没有 Connection 数据库连接,或者连接处于事务同步状态

通过 DataSource 创建一个 Connection 数据库连接

重置 ConnectionHolder 连接持有者,封装刚创建的数据库连接

设置 ConnectionHolder 连接持有者处于事务同步中

获取 Connection 数据库连接,设置是否只读、事务隔离性、超时时间,并将 autocommit 设置为 fasle,不自动提交

保存之前的事务隔离级别(被挂起的事务)

如果需要强制设置只读(默认不需要),且连接本身是只读的,则这里提前设置事务的只读性

设置当前 ConnectionHolder 数据库连接正处于一个事务中

如果是新的事务,则将 DataSource(数据源对象)和 ConnectionHolder(数据库连接持有者)保存至 ThreadLocal 中

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zygsff.html