簡體   English   中英

聲明式事務和TransactionAwareDataSourceProxy與JOOQ結合使用時的問題

[英]Issue with Declarative Transactions and TransactionAwareDataSourceProxy in combination with JOOQ

我有一個數據源配置類,如下所示,帶有單獨的DataSource Bean,用於使用JOOQ進行測試和非測試環境。 在我的代碼中,我不使用DSLContext.transaction(ctx -> {...} ,而是將方法標記為事務性的,以便JOOQ遵循 Spring的聲明性事務以實現事務性。我使用的是Spring 4.3.7.RELEASE

我有以下問題:

  • 在測試(JUnit)期間,@ @Transactional可以正常工作。 無論我使用DSLContextstore()方法有多少次,一個方法都是事務性的,並且RuntimeException觸發整個事務的回滾。
  • 在實際的生產運行期間,@ @Transactional被完全忽略。 一種方法不再是事務性的,而TransactionSynchronizationManager.getResourceMap()擁有兩個單獨的值:一個顯示給我的連接池( 不是事務性的),另一個顯示TransactionAwareDataSourceProxy )。 圖片

在這種情況下,我只希望包裝我的DB CP的TransactionAwareDataSourceProxy類型的單個資源。

  • 大量的試驗和錯誤使用第二套配置改變我做了之后(以下“之后”指出), @Transactional工作正常預期,即使在運行時,雖然TransactionSynchronizationManager.getResourceMap()持有以下值: 圖片

在這種情況下,我的DataSourceTransactionManager似乎甚至都不知道TransactionAwareDataSourceProxy (很可能是由於我將簡單的DataSource而不是代理對象傳遞給了它),無論如何它似乎完全“跳過了”代理。

我的問題是 :我似乎正確但沒有用的初始配置。 建議的“修補程序”有效,但是IMO根本不起作用(因為事務管理器似乎不知道TransactionAwareDataSourceProxy )。

這里發生了什么? 有沒有更清潔的方法來解決此問題?

之前(在運行時不進行事務處理)

@Configuration
@EnableTransactionManagement
@RefreshScope
@Slf4j
public class DataSourceConfig {

    @Bean
    @Primary
    public DSLContext dslContext(org.jooq.Configuration configuration) throws SQLException {
        return new DefaultDSLContext(configuration);
    }

    @Bean
    @Primary
    public org.jooq.Configuration defaultConfiguration(DataSourceConnectionProvider dataSourceConnectionProvider) {
        org.jooq.Configuration configuration = new DefaultConfiguration()
            .derive(dataSourceConnectionProvider)
            .derive(SQLDialect.POSTGRES_9_5);
        configuration.set(new DeleteOrUpdateWithoutWhereListener());
        return configuration;
    }

    @Bean
    public DataSourceTransactionManager transactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean
    public DataSourceConnectionProvider dataSourceConnectionProvider(DataSource dataSource) {
        return new DataSourceConnectionProvider(dataSource);
    }

    @Configuration
    @ConditionalOnClass(EmbeddedPostgres.class)
    static class EmbeddedDataSourceConfig {

        @Value("${spring.jdbc.port}")
        private int dbPort;

        @Bean(destroyMethod = "close")
        public EmbeddedPostgres embeddedPostgres() throws Exception {
            EmbeddedPostgres embeddedPostgres = EmbeddedPostgresHelper.startDatabase(dbPort);
            return embeddedPostgres;
        }

        @Bean
        @Primary
        public DataSource dataSource(EmbeddedPostgres embeddedPostgres) throws Exception {
            DataSource dataSource = embeddedPostgres.getPostgresDatabase();
            return new TransactionAwareDataSourceProxy(dataSource);
        }
    }

    @Configuration
    @ConditionalOnMissingClass("com.opentable.db.postgres.embedded.EmbeddedPostgres")
    @RefreshScope
    static class DefaultDataSourceConfig {

        @Value("${spring.jdbc.url}")
        private String url;

        @Value("${spring.jdbc.username}")
        private String username;

        @Value("${spring.jdbc.password}")
        private String password;

        @Value("${spring.jdbc.driverClass}")
        private String driverClass;

        @Value("${spring.jdbc.MaximumPoolSize}")
        private Integer maxPoolSize;

        @Bean
        @Primary
        @RefreshScope
        public DataSource dataSource() {
            log.debug("Connecting to datasource: {}", url);
            HikariConfig hikariConfig = buildPool();
            DataSource dataSource = new HikariDataSource(hikariConfig);
            return new TransactionAwareDataSourceProxy(dataSource);
        }

        private HikariConfig buildPool() {
            HikariConfig config = new HikariConfig();
            config.setJdbcUrl(url);
            config.setUsername(username);
            config.setPassword(password);
            config.setDriverClassName(driverClass);
            config.setConnectionTestQuery("SELECT 1");
            config.setMaximumPoolSize(maxPoolSize);

            return config;
        }
    }

之后(按預期在運行時進行事務處理,所有未列出的Bean與上面相同)

@Configuration
@EnableTransactionManagement
@RefreshScope
@Slf4j
public class DataSourceConfig {

    @Bean
    public DataSourceConnectionProvider dataSourceConnectionProvider(TransactionAwareDataSourceProxy dataSourceProxy) {
        return new DataSourceConnectionProvider(dataSourceProxy);
    }

    @Bean
    public TransactionAwareDataSourceProxy transactionAwareDataSourceProxy(DataSource dataSource) {
        return new TransactionAwareDataSourceProxy(dataSource);
    }

    @Configuration
    @ConditionalOnMissingClass("com.opentable.db.postgres.embedded.EmbeddedPostgres")
    @RefreshScope
    static class DefaultDataSourceConfig {

        @Value("${spring.jdbc.url}")
        private String url;

        @Value("${spring.jdbc.username}")
        private String username;

        @Value("${spring.jdbc.password}")
        private String password;

        @Value("${spring.jdbc.driverClass}")
        private String driverClass;

        @Value("${spring.jdbc.MaximumPoolSize}")
        private Integer maxPoolSize;

        @Bean
        @Primary
        @RefreshScope
        public DataSource dataSource() {
            log.debug("Connecting to datasource: {}", url);
            HikariConfig hikariConfig = buildPoolConfig();
            DataSource dataSource = new HikariDataSource(hikariConfig);
            return dataSource; // not returning the proxy here
        }
    }
}

我將把我的評論變成答案。

事務管理器不應該知道代理。 文檔中

請注意,事務管理器(例如DataSourceTransactionManager)仍需要使用基礎DataSource,而不是與此代理一起使用。

TransactionAwareDataSourceProxy類是一個特殊用途的類,在大多數情況下不需要。 通過Spring框架基礎結構與數據源接口的任何事物都不應在其訪問鏈中包含代理。 該代理用於無法與Spring基礎結構接口的代碼。 例如,一個第三方庫已經設置為可以使用JDBC,並且不接受任何Spring的JDBC模板。 與上述相同的文檔中對此進行了說明:

該代理允許數據訪問代碼與普通JDBC API一起使用,並且仍然參與Spring管理的事務,這與J2EE / JTA環境中的JDBC代碼類似。 但是,如果可能,即使沒有目標DataSource的代理,也可以使用Spring的DataSourceUtils,JdbcTemplate或JDBC操作對象來獲得事務參與,從而避免了首先定義此類代理的麻煩。

如果您沒有任何需要繞過Spring框架的代碼,則根本不要使用TransactionAwareDataSourceProxy 如果您確實有這樣的遺留代碼,那么您將需要執行第二步設置中已經配置的操作。 您將需要創建兩個bean,一個是數據源,另一個是代理。 然后,您應該為所有Spring托管類型提供數據源,並為舊類型提供代理。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM