转载

jOOQ 与 Spring 的一些注意事项

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/

原文  https://maoxian.de/2020/04/1606.html
正文到此结束
Loading...