Spring事务之如何保证同一个Connection对象

一、传统JDBC事务管理

  首先我们先看一下,jdbc的事务配置是在Connection对消里面有对应的方法,比如setAutoCommit,commit,rollback这些方法就是对事务的操作。

conn.setAutoCommit(false);//设置事务非自动提交
conn.commit();//提交事务
conn.rollback();//事务回滚

  这样必然存在一些问题,如:把业务逻辑代码和事务处理代码混合起来,同时存在代码重复性。如下是一段典型的控制事务代码:

 private DataSource dataSource = null;

  public void setDataSource(DataSource dataSource){
    this.dataSource = dataSource;
  }
  public void update() {
    Connection conn = null;
    PreparedStatement pstmt = null;
    try {
      conn = dataSource.getConnection();
      conn.setAutoCommit(false);//设置事务非自动提交
      String sql = "update testTable set name='测试数据' where id = '1'";
      pstmt = conn.prepareStatement(sql);
      pstmt.execute();
      conn.commit();//提交事务
    } catch (Exception e) {
      try {
        conn.rollback();//事务回滚
      } catch (Exception e1) {
        e1.printStackTrace();
      }
      e.printStackTrace();
    } finally {
      try {
        if(pstmt!=null)
          pstmt.close();
        if (conn != null)
          conn.close();
      } catch (SQLException e) {
        e.printStackTrace();
      }
    }
  }

二、Spring中的事务原理

  Spring容器的事务机制的实质是对传统JDBC的封装,也即是Spring事务管理无论是对单数据库实例还是分布式数据库实例,要实现事务管理,那么必须保证在一个事务过程获得Connetion对象是同一个,那么即使在同一个函数中调用其他多个的函数,通过Spring框架的AOP动态代理机制,使得Spring容器底层能够按传统JDBC的方式进行事务处理,从而保证对这个函数做事务控制。
  Spring框架具有支持多数据源的特性,在获得数据库Connection对象往往是通过DataSource中获得,DataSource这个接口往往由不同的厂商驱动实现,因此Spring框架往往是对DataSource进一步的封装保证每次获得的Connection为相同的,这就保证了一个业务方法里面进行多次dao操作,调用的都是一个connection对象,同时保证了多个dao都是在一个事务里面。

package javax.sql;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Wrapper;
/** 
 * <p>A factory for connections to the physical data source that this
 * <code>DataSource</code> object represents.  An alternative to the
 * <code>DriverManager</code> facility, a <code>DataSource</code> object
 * is the preferred means of getting a connection. An object that implements
 * the <code>DataSource</code> interface will typically be
 * registered with a naming service based on the 
 * Java<sup><font size=-2>TM</font></sup> Naming and Directory (JNDI) API.
 * <P>
 * The <code>DataSource</code> interface is implemented by a driver vendor.
 * There are three types of implementations:
 * <OL>
 *   <LI>Basic implementation -- produces a standard <code>Connection</code> 
 *       object
 *   <LI>Connection pooling implementation -- produces a <code>Connection</code>
 *       object that will automatically participate in connection pooling.  This
 *       implementation works with a middle-tier connection pooling manager.
 *   <LI>Distributed transaction implementation -- produces a
 *       <code>Connection</code> object that may be used for distributed
 *       transactions and almost always participates in connection pooling. 
 *       This implementation works with a middle-tier 
 *       transaction manager and almost always with a connection 
 *       pooling manager.
 * </OL>
 * <P>
 * A <code>DataSource</code> object has properties that can be modified
 * when necessary.  For example, if the data source is moved to a different
 * server, the property for the server can be changed.  The benefit is that
 * because the data source's properties can be changed, any code accessing
 * that data source does not need to be changed.
 * <P>
 * A driver that is accessed via a <code>DataSource</code> object does not 
 * register itself with the <code>DriverManager</code>.  Rather, a
 * <code>DataSource</code> object is retrieved though a lookup operation
 * and then used to create a <code>Connection</code> object.  With a basic
 * implementation, the connection obtained through a <code>DataSource</code>
 * object is identical to a connection obtained through the
 * <code>DriverManager</code> facility.
 *
 * @since 1.4
 */
public interface DataSource  extends CommonDataSource,Wrapper {
  /**
   * <p>Attempts to establish a connection with the data source that
   * this <code>DataSource</code> object represents.
   *
   * @return  a connection to the data source
   * @exception SQLException if a database access error occurs
   */
  Connection getConnection() throws SQLException;

  /**
   * <p>Attempts to establish a connection with the data source that
   * this <code>DataSource</code> object represents.
   *
   * @param username the database user on whose behalf the connection is 
   *  being made
   * @param password the user's password
   * @return  a connection to the data source
   * @exception SQLException if a database access error occurs
   * @since 1.4
   */
  Connection getConnection(String username, String password) 
    throws SQLException;
}

三、Spring中事务处理过程

  首先我们先看JdbcTemplate类数据访问类

public Object execute(ConnectionCallback action)
    throws DataAccessException
  {
    Assert.notNull(action, "Callback object must not be null");
    Connection con = DataSourceUtils.getConnection(getDataSource());
    try {
      Connection conToUse = con;
      if (this.nativeJdbcExtractor != null)
      {
        conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
      }
      else
      {
        conToUse = createConnectionProxy(con);
      }
      localObject1 = action.doInConnection(conToUse);
    }
    catch (SQLException ex)
    {
      Object localObject1;
      DataSourceUtils.releaseConnection(con, getDataSource());
      con = null;
      throw getExceptionTranslator().translate("ConnectionCallback", getSql(action), ex);
    }
    finally {
      DataSourceUtils.releaseConnection(con, getDataSource());
    }
  }

  由上述源码中Connection con = DataSourceUtils.getConnection(getDataSource());这个可以看出,DataSourceUtils类保证当前线程获得的是同一个Connection对象。下面我们主要分析DataSourceUtils类:

public static Connection getConnection(DataSource dataSource)
    throws CannotGetJdbcConnectionException
  {
    try
    {
      return doGetConnection(dataSource);
    } catch (SQLException ex) {
    }
    throw new CannotGetJdbcConnectionException("Could not get JDBC Connection", ex);
  }
  public static Connection doGetConnection(DataSource dataSource)
    throws SQLException
  {
    Assert.notNull(dataSource, "No DataSource specified");
    ConnectionHolder conHolder = (ConnectionHolder)TransactionSynchronizationManager.getResource(dataSource);
    if ((conHolder != null) && ((conHolder.hasConnection()) || (conHolder.isSynchronizedWithTransaction()))) {
      conHolder.requested();
      if (!conHolder.hasConnection()) {
        logger.debug("Fetching resumed JDBC Connection from DataSource");
        conHolder.setConnection(dataSource.getConnection());
      }
      return conHolder.getConnection();
    }
    logger.debug("Fetching JDBC Connection from DataSource");
    Connection con = dataSource.getConnection();
    if (TransactionSynchronizationManager.isSynchronizationActive()) {
      logger.debug("Registering transaction synchronization for JDBC Connection");
      ConnectionHolder holderToUse = conHolder;
      if (holderToUse == null) {
        holderToUse = new ConnectionHolder(con);
      }
      else {
        holderToUse.setConnection(con);
      }
      holderToUse.requested();
      TransactionSynchronizationManager.registerSynchronization(new ConnectionSynchronization(holderToUse, dataSource));
      holderToUse.setSynchronizedWithTransaction(true);
      if (holderToUse != conHolder) {
        TransactionSynchronizationManager.bindResource(dataSource, holderToUse);
      }
    }
    return con;
  }

  由以上源码可以知道,数据库连接从TransactionSynchronizationManager中获得,如果已经存在则获得,否则重新从DataSource创建一个连接,并把这个连接封装为ConnectionHolder,然后注册绑定到TransactionSynchronizationManager中,并返回Connection对象。同时,可以看出DataSource和ConnectionHolder的存储管理在TransactionSynchronizationManager中,继续分析TransactionSynchronizationManager中的关键代码:

private static final ThreadLocal resources = new NamedThreadLocal("Transactional resources");
  public static Object getResource(Object key)
  {
    Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
    Object value = doGetResource(actualKey);
    if ((value != null) && (logger.isTraceEnabled())) {
      logger.trace("Retrieved value [" + value + "] for key [" + actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");
    }
    return value;
  }
  private static Object doGetResource(Object actualKey)
  {
    Map map = (Map)resources.get();
    if (map == null) {
      return null;
    }
    Object value = map.get(actualKey);
    if (((value instanceof ResourceHolder)) && (((ResourceHolder)value).isVoid())) {
      map.remove(actualKey);
      value = null;
    }
    return 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 map = (Map)resources.get();
    if (map == null) {
      map = new HashMap();
      resources.set(map);
    }
    if (map.put(actualKey, value) != null) {
      throw new IllegalStateException("Already value [" + map.get(actualKey) + "] 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() + "]");
  }

分析源码可以得出,
(1)TransactionSynchronizationManager内部用ThreadLocal对象存储资源,ThreadLocal存储的为DataSource生成的actualKey为key值和ConnectionHolder作为value值封装成的Map。
(2)结合DataSourceUtils的doGetConnection函数和TransactionSynchronizationManager的bindResource函数可知:在某个线程第一次调用时候,封装Map资源为:key值为DataSource生成actualKey【Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);】value值为DataSource获得的Connection对象封装后的ConnectionHolder。等这个线程下一次再次访问中就能保证使用的是第一次创建的ConnectionHolder中的Connection对象。

  当事务结束后,调用【DataSourceUtils.releaseConnection(con, getDataSource());】将ConnectionHolder从TransactionSynchronizationManager中解除。当谁都不用,这个连接被close。 

public static void releaseConnection(Connection con, DataSource dataSource)
  {
    try
    {
      doReleaseConnection(con, dataSource);
    }
    catch (SQLException ex) {
      logger.debug("Could not close JDBC Connection", ex);
    }
    catch (Throwable ex) {
      logger.debug("Unexpected exception on closing JDBC Connection", ex);
    }
  }
  public static void doReleaseConnection(Connection con, DataSource dataSource)
    throws SQLException
  {
    if (con == null) {
      return;
    }
    if (dataSource != null) {
      ConnectionHolder conHolder = (ConnectionHolder)TransactionSynchronizationManager.getResource(dataSource);
      if ((conHolder != null) && (connectionEquals(conHolder, con)))
      {
        conHolder.released();
        return;
      }
    }
    if ((!(dataSource instanceof SmartDataSource)) || (((SmartDataSource)dataSource).shouldClose(con))) {
      logger.debug("Returning JDBC Connection to DataSource");
      con.close();
    }
  }
  • 14
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
在项目中配置 MySQL 事务Spring 事务的方法如下: 1. MySQL 事务配置 对于 MySQL 事务,需要在代码中使用 JDBC API 来控制事务的开始、提交和回滚等操作。具体步骤如下: - 获取数据库连接:使用 DriverManager 或 DataSource 获取数据库连接。 - 开启事务:调用 Connection 对象的 setAutoCommit(false) 方法,将自动提交设置为 false,即开启事务。 - 执行数据库操作:在事务中执行一组数据库操作,可以使用 PreparedStatement 或 Statement 对象来执行 SQL 语句。 - 提交事务或回滚事务:如果所有的数据库操作都执行成功,调用 Connection 对象的 commit() 方法提交事务;如果任何一个数据库操作失败,调用 Connection 对象的 rollback() 方法回滚事务。 - 关闭数据库连接:无论事务执行成功或失败,都需要关闭数据库连接。 2. Spring 事务配置 对于 Spring 事务,可以使用声明式事务或编程式事务来实现事务的控制。具体步骤如下: - 声明式事务配置:在 Spring 配置文件中配置事务管理器(TransactionManager)和事务通知(TransactionAdvice),并在需要控制事务的方法上添加事务注解(@Transactional)。 - 编程式事务配置:在代码中使用 TransactionTemplate 或 PlatformTransactionManager 等 Spring 提供的事务 API 来手动控制事务的开始、提交和回滚等操作。 以上是 MySQL 事务Spring 事务的基本配置方法,具体实现方式还要根据实际的业务需求和技术栈来进行选择和优化。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值