与 JOOQ 结合使用声明式事务和 TransactionAwareDataSourceProxy 的问题

Issue with Declarative Transactions and TransactionAwareDataSourceProxy in combination with JOOQ

我有一个数据源配置 class,如下所示,带有用于测试的单独 DataSource bean 和使用 JOOQ 的 non-testing 环境。在我的代码中,我不使用 DSLContext.transaction(ctx -> {...} 而是将该方法标记为事务性的,以便 JOOQ 遵从 Spring 的声明性事务来实现事务性。我正在使用 Spring 4.3.7.RELEASE.

我有以下问题:

在这种情况下,我希望只有一个 TransactionAwareDataSourceProxy 类型的资源来包装我的 DB CP。

在这种情况下,我的 DataSourceTransactionManager 似乎甚至不知道 TransactionAwareDataSourceProxy(很可能是因为我传递给它的是简单的 DataSource,而不是代理对象),这无论如何似乎完全 'skip' 代理。

我的问题是:我的初始配置似乎是正确的,但没有用。提议的 'fix' 有效,但 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;
        }
    }

AFTER(如预期的那样,在运行时是事务性的,所有 non-listed 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
        }
    }
}

我会将我的评论变成答案。

事务管理器不应该知道代理。来自 documentation:

Note that the transaction manager, for example DataSourceTransactionManager, still needs to work with the underlying DataSource, not with this proxy.

class TransactionAwareDataSourceProxy 是一个特殊用途 class 大多数情况下不需要。任何通过 Spring 框架基础设施与您的数据源交互的东西都不应在其访问链中包含代理。该代理适用于无法与 Spring 基础设施交互的代码。例如,已经设置为与 JDBC 一起使用并且不接受任何 Spring 的 JDBC 模板的第三方库。这在与上面相同的文档中有说明:

This proxy allows data access code to work with the plain JDBC API and still participate in Spring-managed transactions, similar to JDBC code in a J2EE/JTA environment. However, if possible, use Spring's DataSourceUtils, JdbcTemplate or JDBC operation objects to get transaction participation even without a proxy for the target DataSource, avoiding the need to define such a proxy in the first place.

如果您没有任何需要绕过 Spring 框架的代码,那么根本不要使用 TransactionAwareDataSourceProxy。如果您确实有这样的遗留代码,那么您将需要执行您在第二个设置中已经配置的操作。您将需要创建两个 bean,一个是数据源,一个是代理。然后,您应该将数据源提供给所有 Spring 托管类型,并将代理提供给遗留类型。