jOOQ 全称 Java Object Oriented Querying,即面向 Java 对象查询。它是 Data Geekery 公司研发的 DA 方案 (Data Access Layer),是一个 ORM 框架。
使用 jOOQ,既不像 Hibernate 等框架封装过高,无法触及 SQL 底层;也不像 MyBatis 等,配置太过繁琐。同时还是 Type Safe 的框架,编译时即可最大程度的发现问题。
不过在 jOOQ 配合 String Cloud 使用的时候,还是踩了几个小坑,特别说明一下。随时补充新遇到的问题。
一、事物问题
jOOQ 默认有一套自己的流式 API,来支持事物。不过,在 Spring 里面,我们使用的最多的还是 @EnableTransactionManagement
和 @Transactional
注解。使用这二者可以开启 Spring内置的基于注解和 Proxy 的事物处理机制,相对更灵活,更优雅。使用起来也更简单。
但是在跟 jOOQ 联动的时候,实际使用却发现事物始终不生效。但是奇怪的是,不论是打断点调试还是加日志,都能发现异常正常抛出了,也被 Spring 正常捕获了,Transaction 的 Rollback 也调用了,但是实际上事物就是没有撤销。
在多次排查 Spring 本身的配置问题后,突然想到问题可能处在 jOOQ 上。经过查找相关文档发现,由于我们的 SQL 都是通过 jOOQ 的 DSLContent
构建并执行的,所以默认情况下并不会受 Spring Transaction Manager 的管理。这里我们需要在配置 jOOQ 的时候,特别配置一下,才能让 @Transactional
注解生效。参考官网的样例,XML 配置如下:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd"> <!-- This is needed if you want to use the @Transactional annotation --> <tx:annotation-driven transaction-manager="transactionManager"/> <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close" > <!-- These properties are replaced by Maven "resources" --> <property name="url" value="${db.url}" /> <property name="driverClassName" value="${db.driver}" /> <property name="username" value="${db.username}" /> <property name="password" value="${db.password}" /> </bean> <!-- Configure Spring's transaction manager to use a DataSource --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <!-- Configure jOOQ's ConnectionProvider to use Spring's TransactionAwareDataSourceProxy, which can dynamically discover the transaction context --> <bean id="transactionAwareDataSource" class="org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy"> <constructor-arg ref="dataSource" /> </bean> <bean class="org.jooq.impl.DataSourceConnectionProvider" name="connectionProvider"> <constructor-arg ref="transactionAwareDataSource" /> </bean> <!-- Configure the DSL object, optionally overriding jOOQ Exceptions with Spring Exceptions --> <bean id="dsl" class="org.jooq.impl.DefaultDSLContext"> <constructor-arg ref="config" /> </bean> <bean id="exceptionTranslator" class="org.jooq.example.spring.exception.ExceptionTranslator" /> <!-- Invoking an internal, package-private constructor for the example Implement your own Configuration for more reliable behaviour --> <bean class="org.jooq.impl.DefaultConfiguration" name="config"> <property name="SQLDialect"><value type="org.jooq.SQLDialect">H2</value></property> <property name="connectionProvider" ref="connectionProvider" /> <property name="executeListenerProvider"> <array> <bean class="org.jooq.impl.DefaultExecuteListenerProvider"> <constructor-arg index="0" ref="exceptionTranslator"/> </bean> </array> </property> </bean> <!-- This is the "business-logic" --> <bean id="books" class="org.jooq.example.spring.impl.DefaultBookService"/> </beans>
核心要点在这段:
<!-- Configure jOOQ's ConnectionProvider to use Spring's TransactionAwareDataSourceProxy, which can dynamically discover the transaction context --> <bean id="transactionAwareDataSource" class="org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy"> <constructor-arg ref="dataSource" /> </bean> <bean class="org.jooq.impl.DataSourceConnectionProvider" name="connectionProvider"> <constructor-arg ref="transactionAwareDataSource" /> </bean>
这里要注意,一定要用 Spring 的 TransactionAwareDataSourceProxy
包装一层前面配置的 DataSource
对象。否则,jOOQ 拿到的就是一个没有被托管的原始 DataSource,那么就不会被 @Transactional
注解所管控。
对应的 Java 方式配置要点如下:
package de.maoxian.config; import javax.sql.DataSource; import org.jooq.ConnectionProvider; import org.jooq.DSLContext; import org.jooq.SQLDialect; import org.jooq.impl.DataSourceConnectionProvider; import org.jooq.impl.DefaultConfiguration; import org.jooq.impl.DefaultDSLContext; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy; import org.springframework.transaction.annotation.EnableTransactionManagement; @Configuration @EnableTransactionManagement public class DbConfig { @Bean @Primary @ConfigurationProperties(prefix = "spring.datasource.maoxian") public DataSource dataSource() { return DataSourceBuilder.create().build(); } @Primary @Bean public DataSourceTransactionManager transactionManager(DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } @Bean public DSLContext dslContext(@Qualifier("maoxian-jooq-conf") org.jooq.Configuration configuration) { return new DefaultDSLContext(configuration); } @Bean("maoxian-jooq-conf") public org.jooq.Configuration jooqConf(ConnectionProvider connectionProvider) { return new DefaultConfiguration().derive(connectionProvider).derive(SQLDialect.MYSQL); } @Bean public ConnectionProvider connectionProvider(TransactionAwareDataSourceProxy transactionAwareDataSource) { return new DataSourceConnectionProvider(transactionAwareDataSource); } @Bean public TransactionAwareDataSourceProxy transactionAwareDataSourceProxy(DataSource dataSource) { return new TransactionAwareDataSourceProxy(dataSource); } }
重点在 ConnectionProvider 的配置。此处的 ConnectionProvider 在创建时,必须使用被 Spring 包过的 DataSource。如果直接使用 DataSource 而不是 TransactionAwareDataSourceProxy 则注解失效。
参考文档:
https://www.jooq.org/doc/latest/manual/getting-started/tutorials/jooq-with-spring/