声明性事务和 TransactionAwareDataSourceProxy 与 JOOQ 结合的问题
Posted
技术标签:
【中文标题】声明性事务和 TransactionAwareDataSourceProxy 与 JOOQ 结合的问题【英文标题】:Issue with Declarative Transactions and TransactionAwareDataSourceProxy in combination with JOOQ 【发布时间】:2018-08-15 08:20:07 【问题描述】:我有一个如下所示的数据源配置类,带有单独的 DataSource
bean,用于使用 JOOQ 的测试和非测试环境。在我的代码中,我不使用DSLContext.transaction(ctx -> ...
,而是将该方法标记为事务性的,以便JOOQ 遵循Spring 的事务性声明性事务。我正在使用 Spring 4.3.7.RELEASE。
我有以下问题:
在测试 (JUnit) 期间,@Transactional
按预期工作。无论我使用多少次DSLContext
的store()
方法,单个方法都是事务性的,而RuntimeException
会触发整个事务的回滚。
在实际生产运行时,@Transactional
被完全忽略。方法不再是事务性的,TransactionSynchronizationManager.getResourceMap()
拥有两个单独的值:一个显示给我的连接池(不是事务性的),一个显示TransactionAwareDataSourceProxy
)。
在这种情况下,我希望只有一个 TransactionAwareDataSourceProxy
类型的资源来包装我的 DB CP。
@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;
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
【问题讨论】:
作为记录,这个问题似乎也被问到了 spring boot 问题跟踪器:github.com/spring-projects/spring-boot/issues/12377 嗨@LukasEder,是的,我已经单独发布了这个问题,尽管它似乎放错了地方。我把它移到了SO。对于没有在此处提及上述内容而造成的混乱,我深表歉意。感谢您更新此问题和跟踪器。 您的课程是否直接使用DataSource
(例如JDBC)?如果没有,我认为您根本不应该使用TransactionAwareDataSourceProxy
。正如the docs 中所述,“请注意,事务管理器,例如 DataSourceTransactionManager,仍然需要使用底层 DataSource,而不是使用此代理。”
如果您的代码确实使用了 JDBC 以及 Spring 的抽象,那么您将需要创建两个 bean。首先是普通数据源,您将其传递给 Spring 的抽象(例如 DataSourceTransactionManager
)和第二个 bean,即代理,它包装第一个 bean 并将其传递给您的类。
【参考方案1】:
我会把我的 cmets 变成答案。
事务管理器不应该知道代理。来自documentation:
注意事务管理器,例如 DataSourceTransactionManager,仍然需要与底层合作 数据源,不使用此代理。
TransactionAwareDataSourceProxy
类是一个特殊用途的类,在大多数情况下不需要。任何通过 Spring 框架基础设施与您的数据源交互的东西都不应在其访问链中包含代理。代理适用于无法与 Spring 基础架构交互的代码。例如,已经设置为使用 JDBC 并且不接受任何 Spring 的 JDBC 模板的第三方库。这在与上述相同的文档中有所说明:
此代理允许数据访问代码使用纯 JDBC API 和 仍然参与 Spring 管理的事务,类似于 JDBC 代码 在 J2EE/JTA 环境中。但是,如果可能,请使用 Spring 的 DataSourceUtils、JdbcTemplate 或 JDBC 操作对象获取 即使没有目标的代理也参与交易 DataSource,避免一开始就定义这样的代理 地点。
如果您没有任何需要绕过 Spring 框架的代码,则根本不要使用 TransactionAwareDataSourceProxy
。如果您确实有这样的遗留代码,那么您将需要执行您在第二次设置中已经配置的内容。您将需要创建两个 bean,一个是数据源,一个是代理。然后,您应该将数据源提供给所有 Spring 托管类型,并将代理提供给旧类型。
【讨论】:
啊,现在一切都说得通了。在这种情况下,JOOQ 在 Spring 眼中是“遗留代码”,因此它的 DB 访问必须通过连接提供程序进行,该连接提供程序从TransactionAwareDataSourceProxy
检索连接。无论何时使用DSLContext
,第二个配置都会隐式授予对代理对象的访问权限。非常感谢您的宝贵时间。以上是关于声明性事务和 TransactionAwareDataSourceProxy 与 JOOQ 结合的问题的主要内容,如果未能解决你的问题,请参考以下文章