【问题标题】:How do I get the connection inside of a Spring transaction?如何在 Spring 事务中获取连接?
【发布时间】:2012-07-31 13:01:40
【问题描述】:

想象一下这段代码:

foo() {
     Connection conn = ...;
}

foo() 已从具有注释 @Transactional 的方法中调用。如何获取当前的 JDBC 连接?请注意,foo() 在 bean 中(所以它可以有 @Autowired 字段)但foo() 不能有参数(所以我不能从某个地方传递连接)。

[编辑] 我正在使用需要数据源或连接的 jOOQ。我的问题:我不知道配置了哪个事务管理器。可以是任何东西; Java EE,基于 DataSource,通过 JNDI 获取数据源。我的代码不是应用程序,而是库。我需要吞下别人放在我盘子里的东西。同样,我无法请求 Hibernate 会话工厂,因为使用我的应用程序可能不使用 Hibernate。

但我知道其他代码,例如 Spring Hibernate 集成,可以通过某种方式从事务管理器获取当前连接。我的意思是,Hibernate 不支持 Spring 的事务管理器,因此胶水代码必须使 Spring API 适应 Hibernate 的期望。我需要做同样的事情,但我不知道它是如何工作的。

[EDIT2] 我知道有一个活动事务(即 Spring 在某处有一个 Connection 实例,或者至少有一个可以创建一个的事务管理器),但我的方法不是 @Transactional。我需要调用一个以java.sql.Connection 作为参数的构造函数。我该怎么办?

【问题讨论】:

    标签: java spring jdbc connection spring-transactions


    【解决方案1】:

    您或许可以尝试DataSourceUtils.getConnection(dataSource),根据API,它应该会返回数据源的当前连接。

    更新: 基于您的 cmets 和 org.springframework.transaction.support.TransactionSynchronizationManager 的源代码:

    正如我所说,获取连接的关键是数据源名称,如果无法获取,可以通过查看源代码来尝试以下方法:

    TransactionSynchronizationManager.getResourceMap()会在当前线程返回一个datasource的map到ConnectionHolder,假设你只有1个资源参与事务,你大概可以做一个map.values().get(0)得到第一个ConnectionHolder,从中你可以得到一个通过调用.getConnection()进行连接

    所以本质上是这样调用的:

    TransactionSynchronizationManager.getResourceMap().values().get(0).getConnection()

    可能必须有更好的方法:-)

    【讨论】:

    • 由于一个事务中可以有多个数据源,数据源是从保存事务细节的线程本地检索连接的关键——您应该能够将相关数据源注入这个类吗?
    • 请查看我对问题的修改。我正在写一个库,而不是一个应用程序。我得到了一个 txManager,没有别的。
    • 确保您在 @Transactional 方法中运行 DataSourceUtils.getConnection(dataSource),否则 Spring 将为您提供一个需要手动关闭的新连接,在这种情况下 Spring 不会为您执行此操作.
    【解决方案2】:

    (根据评论线程完全重写;不知道为什么我最初的答案集中在 Hibernate 上,而不是我现在正在使用的)

    事务管理器与数据源完全正交。一些事务管理器直接与数据源交互,一些通过中间层(例如,Hibernate)进行交互,还有一些通过容器提供的服务(例如,JTA)进行交互。

    当您将方法标记为@Transactional 时,这意味着Spring 将在加载您的bean 时生成一个代理,并且该代理将被交给任何其他想要使用您的bean 的类。当调用代理的方法时,它(代理)要求事务管理器给它一个未完成的事务或创建一个新事务。然后它调用你的实际 bean 方法。当您的 bean 方法返回时,代理再次与事务管理器交互,以说“我可以提交”或“我必须回滚”。这个过程有曲折。例如,一个事务方法可以调用另一个事务方法并共享同一个事务。

    虽然事务管理器DataSource 交互,但它拥有DataSource。你不能要求事务管理器给你一个连接。相反,您必须注入一个特定于框架的对象,该对象将返回连接(例如 Hibernate SessionFactory)。或者,您可以使用静态事务感知实用程序类,但它们又与特定框架相关联。

    【讨论】:

    • 但是当我编写一个未知应用程序想要使用的库时,我该怎么办?我想编写自己的 SessionFactory。当你需要自己实现它时,它是如何工作的?
    • 在阅读了您的一些 cmets 之后,我将回应其他发帖者所说的话:尝试从事务管理器获取您的数据源是 错误 的做事方式,并且只会让其他阅读您的代码的人感到困惑。引用Spring docs 的话:“通常,应用程序将使用 TransactionTemplate 或通过 AOP 的声明性事务划分”。通过标记你的方法@Transactional,你正在做后者。
    • 如果您打算编写自己的 SessionFactory——这不是你的问题所问的——那么你需要仔细研究 Hibernate 和 Spring 源代码,并且了解他们如何互动。实际上,请从 Spring 参考手册中有关事务管理的部分开始,因为我认为您对 Spring 事务的工作方式有误解。
    • 嗯,我需要的很简单,但 Spring 不会让我这样做。我知道有一个活动事务,但我的方法不是@Transactional。我需要创建一个将 Connection 作为构造函数参数的对象。我该怎么办?
    • 阅读对您问题的编辑后,我确信您对 Spring 的事务如何工作存在误解。事务管理器——任何类型的——都数据源一起工作。它不拥有数据源。如果你没有使用 Hibernate(不知道我为什么认为你是),那么请按照 Nandkumar 的建议并注入 DataSource。
    【解决方案3】:

    我假设你使用的是普通 Jdbc,你需要做的是:

    BaseDao {
        @Autowired
        private DataSource dataSource;
    
        public void setDataSource(DataSource dataSource) {
            this.dataSource = dataSource;
        }
    
        public Connection getConnection() {
            // ....use dataSource to create connection
            return DataSourceUtils.getConnection(dataSource);
        }
    }
    
    FooDao extends BaseDao {
        // your foo() method
        void foo() {
           Connection conn = getConnection();
           //....
        }
    }
    

    【讨论】:

    • 不,我使用的是 Spring TransactionManager。某处可能有数据源,但我不能指望它。
    • 某处可能有一个数据源,但我不能指望它......那我们怎么能算它呢?请提供您的配置详细信息
    • 在事务管理器的深处,必须是一种获得连接的方法。例如,我不认为 Spring 的 Hibernate 会话工厂对每种类型的 TransactionManager 都有一个特殊的实现,但我不知道这两者是如何协同工作的 :-(
    • 您可以从事务管理器获取数据源,但无法连接检查 API static.springsource.org/spring/docs/3.0.x/javadoc-api/org/…。我认为完全错误的想法。
    • 即使您使用的是 hibernateTransactionManager。
    【解决方案4】:

    所有这一切有点令人恼火的是,Spring 文档主要是用营销语言编写的,隐藏了幕后的丑陋。

    言简意赅:

    您的数据源存储在线程本地上下文中,这是基于(大部分有效的)请求始终由唯一线程处理的假设。

    所以,Spring 以一种非常复杂的方式所做的就是将你的东西本地存储到你当前的执行线程中,这是一件微不足道的事情,但在整个 Spring 文档中没有足够清楚地重复。 Spring 基本上将您的东西放入“全局上下文”中,以避免将其拉过所有接口和方法定义。乍一看有点魔幻,其实只是化妆而已。

    因此,您最终会调用静态 DataSourceUtils 方法来检索您的资料。

    【讨论】:

    • 在线程局部变量中存储像数据源这样的对象似乎很难看,但这可能是在特定执行线程中传递信息的唯一方法。在涉及多个数据源的典型 2 阶段提交中,事务管理器(通常由应用程序服务器提供,但也可以是独立的)的工作方式是将 javax.transaction.Transaction 对象关联到线程,然后使用 enlistresource 登记每个数据源事务对象的方法。所以这种使用模式非常普遍。
    【解决方案5】:

    如果您使用带有 JDBC 的 Spring Transaction,请配置 JdbcTemplate,并使用 JdbcTemplate.execute(ConnectionCallback) 访问当前事务。这是访问 Spring 配置的连接的标准方式。

    【讨论】:

    • 如何从 Spring TransactionManager 获取 JdbcTemplate?
    • new JdbcTemplate(datasource); Spring 的事务管理器并不关心 JdbcTemplate。但是,JdbcTemplate 将挂钩到上面的 DataSourceUtils 以使用适合于它在构造函数中给出的数据源的连接。
    【解决方案6】:

    另外三种方式:

    @Component
    public class MyServiceNonTransactional {
    
        @Autowired
        private TransactionTemplate transactionTemplate;
        
        @Autowired
        private DataSource dataSource;
        
        public void doStuff() {
            transactionTemplate.executeWithoutResult(status -> {
                Connection connection = DataSourceUtils.getConnection(dataSource);
                // here we go...
            });
        }
    
    }
    
    @Service
    public class MyServiceTransactional {
    
        @Autowired
        private DataSource dataSource;
        
        @Transactional
        public void doStuff() {
            Connection connection = DataSourceUtils.getConnection(dataSource);
            // here we go...
        }
    
    }
    
    @Service
    public class MyServiceViaJdbc {
    
        @Autowired
        private JdbcTemplate jdbcTemplate;
    
        public void doStuff() {
            jdbcTemplate.execute((ConnectionCallback<Void>) conn -> {
                // "conn" here we go!
                return null;
            });
        }
            
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-02-09
      • 1970-01-01
      • 2012-02-20
      • 2015-12-02
      • 1970-01-01
      • 2014-03-13
      • 2020-09-06
      • 1970-01-01
      相关资源
      最近更新 更多