Spring事务,connection获取,用DataSourceUtils的原理

前几天解释了Spring的抽象事务机制。这次讲讲Spring中的DataSource 事务。
DataSource事务相关的类比较多,我们一步步来拨开其中的密团。

1 如何获得连接 看DataSourceUtils代码

Java代码

protected static Connection doGetConnection(DataSource dataSource, boolean allowSynchronization); 
throws SQLException { 

    ConnectionHolder conHolder = (ConnectionHolder); TransactionSynchronizationManager.getResource(dataSource);
    if (conHolder != null) { 
        conHolder.requested(); 
        return conHolder.getConnection();
    } 


    Connection con = dataSource.getConnection();; 
    if (allowSynchronization && TransactionSynchronizationManager.isSynchronizationActive()) { 
        conHolder = new ConnectionHolder(con);
        TransactionSynchronizationManager.bindResource(dataSource, conHolder);
        TransactionSynchronizationManager.registerSynchronization(new ConnectionSynchronization(conHolder, dataSource)); 
        conHolder.requested(); 
    } 

    return con; 
} 

原来连接是从TransactionSynchronizationManager中获取,如果TransactionSynchronizationManager中已经有了,那么拿过来然后调用conHolder.requested()。否则从原始的DataSource这创建一个连接,放到一个ConnectionHolder,然后再调用TransactionSynchronizationManager.bindResource绑定。
好,我们又遇到两个新的类TransactionSynchronizationManager和ConnectionHolder和。继续跟踪

2 TransactionSynchronizationManager 看其中的一些代码

Java代码

private static ThreadLocal resources = new ThreadLocal(); 
public static Object getResource(Object key){ 
    Map map = (Map) resources.get(); 
    if (map == null) { 
    return null; 
    } 
    Object value = map.get(key); 
    return value; 
} 
public static void bindResource(Object key, Object value); throws IllegalStateException { 
    Map map = (Map) resources.get();
    if (map == null){ 
        map = new HashMap();
        resources.set(map);
    } 
    map.put(key, value);
}

原来TransactionSynchronizationManager内部建立了一个ThreadLocal的resources,这个resources又是和一个map联系在一起的,这个map在某个线程第一次调用bindResource时生成。
联系前面的DataSourceUtils代码,我们可以总结出来。
某个线程使用DataSourceUtils,当第一次要求创建连接将在TransactionSynchronizationManager中创建出一个ThreadLocal的map。然后以DataSource作为键,ConnectionHolder为值放到map中。等这个线程下一次再请求的这个DataSource的时候,就从这个map中获取对应的ConnectionHolder。用map是为了解决同一个线程上多个DataSource。
然后我们来看看ConnectionHolder又是什么?

3 对连接进行引用计数

看ConnectionHolder代码,这个类很简单,看不出个所以然,只好再去看父类代码ResourceHolderSupport,我们感兴趣的是这两个方法

Java代码

public void requested(); { 
    this.referenceCount++; 
} 

public void released(); { 
    this.referenceCount--; 
} 

看得出这是一个引用计数的技巧。原来Spring中对Connection是竟量使用已创建的对象,而不是每次都创建一个新对象。这就是DataSourceUtils中 的原因
Java代码

if (conHolder != null); { 
    conHolder.requested();; 
    return conHolder.getConnection();; 
}

4 释放连接 完成事物后DataSourceTransactionManager有这样的代码

DataSourceUtils
Java代码

protected void doCleanupAfterCompletion(Object transaction){ 
    DataSourceTransactionObject txObject =(DataSourceTransactionObject) transaction; 

    // Remove the connection holder from the thread. 
    TransactionSynchronizationManager.unbindResource(this.dataSource);
    txObject.getConnectionHolder().clear(); 

    //...DataSourceUtils.closeConnectionIfNecessary(con,this.dataSource); 
} 

protected static void doCloseConnectionIfNecessary(Connection con, DataSource dataSource) throws SQLException { 
    if (con == null) { 
    return; 
    } 

    ConnectionHolder conHolder = (ConnectionHolder); TransactionSynchronizationManager.getResource(dataSource);
    if (conHolder != null && con == conHolder.getConnection()){ 
        // It's the transactional Connection: Don't close it. 
        conHolder.released();; 
        return; 
    } 

    // Leave the Connection open only if the DataSource is our 
    // special data source, and it wants the Connection left open. 
    if (!(dataSource instanceof SmartDataSource) || ((SmartDataSource); dataSource).shouldClose(con)) { 
        logger.debug("Closing JDBC connection"); 
        con.close();
    } 
} 

恍然大悟。如果事物完成,那么就
TransactionSynchronizationManager.unbindResource(this.dataSource);将当前的ConnectionHolder
从TransactionSynchronizationManager上脱离,然后doCloseConnectionIfNecessary。最后会把连接关闭掉。

5 两个辅助类JdbcTemplate和TransactionAwareDataSourceProxy

JdbcTemplate中的execute方法的第一句和最后一句

Java代码

public Object execute(PreparedStatementCreator psc,PreparedStatementCallback action); 
throws DataAccessException { 

    Connection con = DataSourceUtils.getConnection(getDataSource()); 
    //其他代码 
    DataSourceUtils.closeConnectionIfNecessary(con, getDataSource()); 
    } 
} 

作用不言自明了吧

从TransactionAwareDataSourceProxy中获取的连接是这个样子的

Java代码

public Connection getConnection() throws SQLException { 
    Connection con = DataSourceUtils.doGetConnection(getTargetDataSource(), true);
    return getTransactionAwareConnectionProxy(con, getTargetDataSource()); 
} 

万变不离其宗,不过我们还是看看getTransactionAwareConnectionProxy

Java代码

protected Connection getTransactionAwareConnectionProxy(Connection target, DataSource dataSource) { 
    return (Connection) Proxy.newProxyInstance( 
    ConnectionProxy.class.getClassLoader(), 
    new Class[] {ConnectionProxy.class}, 
    new TransactionAwareInvocationHandler(target, dataSource)); 
} 

原来返回的是jdk的动态代理。继续看TransactionAwareInvocationHandler

Java代码

public Object invoke(Object proxy, Method method, Object[] args); throws Throwable { 
//...
    if(method.getName();.equals(CONNECTION_CLOSE_METHOD_NAME)) { 
        if (this.dataSource != null); {
            DataSourceUtils.doCloseConnectionIfNecessary(this.target,this.dataSource);
        } 
    return null; 
    } 

} 

TransactionAwareDataSourceProxy会先从DataSourceUtils获取连接。然后将这个连接用jdk的动态代理包一下返回。外部代码如果调用的这个冒牌的Connection,就会先调用TransactionAwareInvocationHandler的invoke,在这个invoke 中,完成原来调用DataSourceUtils的功能。

总结上面的流程
Spring 对DataSource进行事务管理的关键在于ConnectionHolder和TransactionSynchronizationManager。

  1. 先从TransactionSynchronizationManager中尝试获取连接
    如果前一步失败则在每个线程上,对每个DataSouce只创建一个Connection
    这个Connection用ConnectionHolder包装起来,由TransactionSynchronizationManager管理

  2. 再次请求同一个连接的时候,从TransactionSynchronizationManager返回已经创建的ConnectionHolder,然后调用ConnectionHolder的request将引用计数+1释放连接时要调用ConnectionHolder的released,将引用计数-1

  3. 当事物完成后,将ConnectionHolder从TransactionSynchronizationManager中解除。当谁都不用,这个连接被close

    以上所有都是可以调用DataSourceUtils化简代码,而JdbcTemplate又是调用DataSourceUtils的。所以在Spring文档中要求尽量首先使用JdbcTemplate其次是用DataSourceUtils来获取和释放连接。至于TransactionAwareDataSourceProxy,那是下策的下策。不过可以将Spring事务管理和遗留代码无缝集成。

    所以如某位朋友说要使用Spring的事务管理,但是又不想用JdbcTemplate,那么可以考虑TransactionAwareDataSourceProxy。这个类是原来DataSource的代理。
    其次,想使用Spring事物,又不想对Spring进行依赖是不可能的。与其试图自己模拟DataSourceUtils,不如直接使用现成的。

转载地址 http://yangleiol.iteye.com/blog/731694

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
这个错误提示说明你正在使用 Spring 事务管理器,并且在使用 `SqlSession` 时没有使用 Spring 提供的 `DataSourceTransactionManager`。 你需要Spring 配置文件中配置一个 `DataSourceTransactionManager` bean,并使用它来实例化 `TransactionTemplate`。然后在执行 SQL 操作时,使用 Spring 提供的 `TransactionTemplate.execute()` 方法来执行数据库操作。 在执行数据库操作时,你需要使用 Spring 提供的 `DataSourceUtils.getConnection()` 方法来获取连接,而不是直接使用 `dataSource.getConnection()`。这是因为 Spring事务管理器需要控制事务的提交和回滚,如果你直接使用 `dataSource.getConnection()`,那么 Spring 将无法控制事务的提交和回滚。 请注意,如果你想要使用 MyBatis,你也需要将 `SqlSession` 实例化为 Spring Bean,并在 Spring 配置文件中配置。否则,你需要手动关闭 `SqlSession`,否则会导致连接池资源泄漏。 以下是一个示例配置文件: ```xml <!-- 配置数据源 --> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/test" /> <property name="username" value="root" /> <property name="password" value="root" /> </bean> <!-- 配置 MyBatis SqlSessionFactory --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="configLocation" value="classpath:mybatis-config.xml" /> <property name="mapperLocations" value="classpath*:com/example/mappers/**/*.xml" /> </bean> <!-- 配置 MyBatis SqlSessionTemplate --> <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"> <constructor-arg ref="sqlSessionFactory" /> </bean> <!-- 配置 Spring 事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <!-- 配置 Spring 事务模板 --> <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate"> <property name="transactionManager" ref="transactionManager" /> </bean> ``` 在使用 `TransactionTemplate` 执行数据库操作时,你可以这样写: ```java return transactionTemplate.execute(status -> { try (SqlSession sqlSession = sqlSessionTemplate.getSqlSessionFactory().openSession(true)) { // 执行数据库操作 return sqlSession.selectList("com.example.mappers.MyMapper.selectSomething"); } catch (Exception e) { // 操作失败,回滚事务 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); throw new RuntimeException(e); } }); ``` 在这个示例中,我们使用了 `SqlSessionTemplate` 来获取 `SqlSession`。这个 `SqlSession` 实例也是一个 Spring Bean,因此 Spring 将负责它的生命周期管理。在使用 `SqlSession` 时,我们使用了 try-with-resources 语句来确保它被正确关闭。如果数据库操作失败,我们通过 `TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()` 将事务标记为回滚状态,以确保事务回滚。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值