与 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.
我有以下问题:
- 在测试 (JUnit) 期间,
@Transactional
按预期工作。无论我使用DSLContext
的store()
方法多少次,单个方法都是事务性的,而RuntimeException
会触发整个事务的回滚。
- 在实际生产运行时,
@Transactional
被完全忽略。一个方法不再是事务性的,并且 TransactionSynchronizationManager.getResourceMap()
包含两个独立的值:一个向我的连接池显示( 非 事务),另一个显示 TransactionAwareDataSourceProxy
).
在这种情况下,我希望只有一个 TransactionAwareDataSourceProxy
类型的资源来包装我的 DB CP。
- 在使用我所做的第二组配置更改(在下面用“AFTER”标注)进行多次试验和错误之后,
@Transactional
即使在运行时也能按预期正常工作,尽管 TransactionSynchronizationManager.getResourceMap()
持有以下内容价值:
在这种情况下,我的 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 托管类型,并将代理提供给遗留类型。
我有一个数据源配置 class,如下所示,带有用于测试的单独 DataSource
bean 和使用 JOOQ 的 non-testing 环境。在我的代码中,我不使用 DSLContext.transaction(ctx -> {...}
而是将该方法标记为事务性的,以便 JOOQ 遵从 Spring 的声明性事务来实现事务性。我正在使用 Spring 4.3.7.RELEASE.
我有以下问题:
- 在测试 (JUnit) 期间,
@Transactional
按预期工作。无论我使用DSLContext
的store()
方法多少次,单个方法都是事务性的,而RuntimeException
会触发整个事务的回滚。 - 在实际生产运行时,
@Transactional
被完全忽略。一个方法不再是事务性的,并且TransactionSynchronizationManager.getResourceMap()
包含两个独立的值:一个向我的连接池显示( 非 事务),另一个显示TransactionAwareDataSourceProxy
).
在这种情况下,我希望只有一个 TransactionAwareDataSourceProxy
类型的资源来包装我的 DB CP。
- 在使用我所做的第二组配置更改(在下面用“AFTER”标注)进行多次试验和错误之后,
@Transactional
即使在运行时也能按预期正常工作,尽管TransactionSynchronizationManager.getResourceMap()
持有以下内容价值:
在这种情况下,我的 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 托管类型,并将代理提供给遗留类型。