声明性事务和TransactionAwareDataSourceProxy与JOOQ

时间:2018-03-07 09:30:25

标签: java spring spring-transactions jooq

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

我有以下问题:

  • 在测试期间(JUnit),@Transactional按预期工作。无论我使用DSLContext store()方法多少次,单个方法都是事务性的,而RuntimeException会触发整个事务的回滚。
  • 在实际生产运行时期间,@Transactional完全被忽略。方法不再是事务性的,TransactionSynchronizationManager.getResourceMap()包含两个单独的值:一个显示到我的连接池(事务性),另一个显示TransactionAwareDataSourceProxy)。 image

在这种情况下,我原本只期望一个TransactionAwareDataSourceProxy类型的资源包装我的数据库CP。

  • 经过多次尝试和错误后,我使用了第二组配置更改(下面注明" AFTER"),@Transactional即使在运行时也正常工作,TransactionSynchronizationManager.getResourceMap()拥有以下值: image

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

我的问题是:我的初始配置似乎是正确的,但没有用。建议修复'但是IMO根本不起作用(因为事务管理器似乎并不知道TransactionAwareDataSourceProxy)。

这里发生了什么?有没有更清洁的方法来解决这个问题?

BEFORE(在运行时不是事务性的)

@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(运行期间的事务处理,正如预期的那样,所有未列出的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
        }
    }
}

1 个答案:

答案 0 :(得分:1)

我会将我的评论转化为答案。

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

  

请注意事务管理器   DataSourceTransactionManager仍然需要使用底层   DataSource,不是这个代理。

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

  

此代理允许数据访问代码与纯JDBC API一起使用   仍然参与Spring管理的事务,类似于JDBC代码   在J2EE / JTA环境中。但是,如果可能的话,请使用Spring   获取DataSourceUtils,JdbcTemplate或JDBC操作对象   即使没有代表目标,也可以参与交易   DataSource,无需在第一个中定义这样的代理   的地方。

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