尚学堂 老师好!

上海:15201841284

广州:020-2989 6995

深圳:0755-23061965

武汉:027-8798 9193

西安:029-8822 8155

在 Spring 框架中使用 jOOQ —— 配置部分

我已经分享过关于ORM框架所导致的性能问题。尽管我不得不承认这些问题大部分都是由你自己造成的, 但我也在开始思考在只读操作里使用ORM是不值得的。

更多精彩内容以及学习资料,尚学堂论坛bbs.bjsxt.com免费下载。

于是我开始寻找实现这些操作的替代方法。

接着我就遇到了 jOOQ ,它是这么介绍自己的:

jOOQ 可以从数据库生成Java代码,并允许你通过其流利的API构建类型安全的SQL查询。

这看起来相当有趣。这就是我为什么决定试一试 jOOQ ,并且把我的发现分享给大家。

这篇博文是我的 在Spring框架中使用 jOOQ 系列的第一部分。这里描述了我们将如何获取到所需的依赖并且配置好应用上下文。

现在开始!

使用 Maven 获取所需的依赖包

此应用依赖以下项目:

  • Spring Framework 4.1.2.RELEASE. 本例子使用了 aop, beans, core, context, context-support, jdbc, 和 tx 模块.
  • cglib 3.1.
  • BoneCP 0.8.0. 我们使用的是 BoneCP 连接池
  • jOOQ 3.4.4.
  • H2 1.3.174. H2 作为数据库

如果你想获得更多关于 Spring 框架的信息,请浏览 section 2.2 of the Spring Framework Reference Documentation.

相对应的 pom.xml 部分内容如下:

 <dependency>     <groupId>org.springframework</groupId>     <artifactId>spring-aop</artifactId>     <version>4.1.2.RELEASE</version> </dependency> <dependency>     <groupId>org.springframework</groupId>     <artifactId>spring-beans</artifactId>     <version>4.1.2.RELEASE</version> </dependency> <dependency>     <groupId>org.springframework</groupId>     <artifactId>spring-core</artifactId>     <version>4.1.2.RELEASE</version> </Dependency> <dependency>     <groupId>org.springframework</groupId>     <artifactId>spring-context</artifactId>     <version>4.1.2.RELEASE</version> </dependency> <dependency>     <groupId>org.springframework</groupId>     <artifactId>spring-context-support</artifactId>     <version>4.1.2.RELEASE</version> </dependency> <dependency>     <groupId>org.springframework</groupId>     <artifactId>spring-expression</artifactId>     <version>4.1.2.RELEASE</version> </dependency> <dependency>     <groupId>org.springframework</groupId>     <artifactId>spring-jdbc</artifactId>     <version>4.1.2.RELEASE</version> </dependency> <dependency>     <groupId>org.springframework</groupId>     <artifactId>spring-tx</artifactId>     <version>4.1.2.RELEASE</version> </dependency>           <dependency>     <groupId>cglib</groupId>     <artifactId>cglib</artifactId>     <version>3.1</version> </dependency>   <dependency>     <groupId>com.jolbox</groupId>     <artifactId>bonecp</artifactId>     <version>0.8.0.RELEASE</version> </dependency>   <dependency>     <groupId>org.jooq</groupId>     <artifactId>jooq</artifactId>     <version>3.4.4</version> </dependency>   <dependency>     <groupId>com.h2database</groupId>     <artifactId>h2</artifactId>     <version>1.3.174</version> </dependency>

这篇文章的示例应用程序还有其他依赖项。你可以通过查看pom.xml文件来看到完整的依赖项列表。

让我们继续探索如何将jOOQ抛出的异常转换为Spring 的 DataAccessException。

将jOOQ异常转换为Spring的DataAccessException

为什么我们必须将jOOQ抛出的异常转换为Spring的 DataAccessException?

这样做的一个原因是,我们想让我们的集成跟Spring框架的DAO支持一样的方式工作。这种支持的一个重要部分就是拥有一致的异常层次结构:

Spring 可以很方便的把技术相关的异常比如 SQLException 翻译成它自己的异常类,这种异常类的层次结构是以 DataAccessException 作为根异常的。 这些异常将原始的异常包装起来,这样就不会有任何可能丢失掉包含有出错原因的信息的风险。

换句话说,如果我们想要我们的应用做“一个好公民”的话,就很有必要确保我们的配置能够将jOOQ抛出的异常转换为Spring的DataAccessException。

我们可以通过以下步骤创建一个提供这种功能的组件:

JOOQToSpringExceptionTransformer 类的源代码如下所示:

 import org.jooq.ExecuteContext; import org.jooq.SQLDialect; import org.jooq.impl.DefaultExecuteListener; import org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator; import org.springframework.jdbc.support.SQLExceptionTranslator; import org.springframework.jdbc.support.SQLStateSQLExceptionTranslator;   public class JOOQToSpringExceptionTransformer extends DefaultExecuteListener {      @Override     public void exception(ExecuteContext ctx) {         SQLDialect dialect = ctx.configuration().dialect();         SQLExceptionTranslator translator = (dialect != null)                 ? new SQLErrorCodeSQLExceptionTranslator(dialect.name())                 : new SQLStateSQLExceptionTranslator();          ctx.exception(translator.translate("jOOQ", ctx.sql(), ctx.sqlException()));     } }

实际的应用程序是基于Maven的配置(profiles)进行构建的。这就保证了我们可以在不同环境下使用不同的配置。你可以通过阅读我之前写的题为 使用Maven创建基于特定profile的配置文件的博文来得到更多的信息。

创建属性文件

我们可以按以下步骤来创建属性文件:

  1. 配置数据库连接。我们需要配置JDBC驱动类(driver class),JDBC 地址(url),数据库用户的用户名及其密码。
  2. 配置使用的SQL方言的名称。
  3. 配置创建样例程序的数据库SQL脚本的名字(如果你的应用程序不使用嵌入式数据库,则该步骤就不是必需的了)。

application.properties 配置文件内容如下:

 #Database Configuration db.driver=org.h2.Driver db.url=jdbc:h2:target/jooq-example db.username=sa db.password=  #jOOQ Configuration jooq.sql.dialect=H2  #DB Schema db.schema.script=schema.sql 

这个页面 Javadoc of the SQLDialect Enum 说明了 jOOQ 支持的所有数据库列表。

接下来继续并找出如果通过使用 Java 配置来配置应用的上下文。

创建 Configuration 类

我们可以通过如下几步来配置应用的上下文:

PersistenceContext 类的源代码如下所示:

 import com.jolbox.bonecp.BoneCPDataSource; import org.jooq.SQLDialect; import org.jooq.impl.DataSourceConnectionProvider; import org.jooq.impl.DefaultConfiguration; import org.jooq.impl.DefaultDSLContext; import org.jooq.impl.DefaultExecuteListenerProvider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; import org.springframework.core.env.Environment; import org.springframework.core.io.ClassPathResource; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy; import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy; import org.springframework.jdbc.datasource.init.DataSourceInitializer; import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator; import org.springframework.transaction.annotation.EnableTransactionManagement;  import javax.sql.DataSource;  @Configuration @ComponentScan({"net.petrikainulainen.spring.jooq.todo"}) @EnableTransactionManagement @PropertySource("classpath:application.properties") public class PersistenceContext {      @Autowired     private Environment env;      @Bean(destroyMethod = "close")     public DataSource dataSource() {         BoneCPDataSource dataSource = new BoneCPDataSource();          dataSource.setDriverClass(env.getRequiredProperty("db.driver"));         dataSource.setJdbcUrl(env.getRequiredProperty("db.url"));         dataSource.setUsername(env.getRequiredProperty("db.username"));         dataSource.setPassword(env.getRequiredProperty("db.password"));          return dataSource;     }      @Bean     public LazyConnectionDataSourceProxy lazyConnectionDataSource() {         return new LazyConnectionDataSourceProxy(dataSource());     }      @Bean     public TransactionAwareDataSourceProxy transactionAwareDataSource() {         return new TransactionAwareDataSourceProxy(lazyConnectionDataSource());     }      @Bean     public DataSourceTransactionManager transactionManager() {         return new DataSourceTransactionManager(lazyConnectionDataSource());     }      @Bean     public DataSourceConnectionProvider connectionProvider() {         return new DataSourceConnectionProvider(transactionAwareDataSource());     }      @Bean     public JOOQToSpringExceptionTransformer jooqToSpringExceptionTransformer() {         return new JOOQToSpringExceptionTransformer();     }      @Bean     public DefaultConfiguration configuration() {         DefaultConfiguration jooqConfiguration = new DefaultConfiguration();          jooqConfiguration.set(connectionProvider());         jooqConfiguration.set(new DefaultExecuteListenerProvider(             jooqToSpringExceptionTransformer()         ));          String sqlDialectName = env.getRequiredProperty("jooq.sql.dialect");         SQLDialect dialect = SQLDialect.valueOf(sqlDialectName);         jooqConfiguration.set(dialect);          return jooqConfiguration;     }      @Bean     public DefaultDSLContext dsl() {         return new DefaultDSLContext(configuration());     }      @Bean     public DataSourceInitializer dataSourceInitializer() {         DataSourceInitializer initializer = new DataSourceInitializer();         initializer.setDataSource(dataSource());          ResourceDatabasePopulator populator = new ResourceDatabasePopulator();         populator.addScript(                 new ClassPathResource(env.getRequiredProperty("db.schema.script"))         );          initializer.setDatabasePopulator(populator);         return initializer;     } }

用于自动加入线程绑定事务的DataSourceUtils的代理, 例如在返回的Connections上执行DataSourceTransactionManager.getConnection 和 close 调用所管理的代理将会在同一个事务内正常起作用,比如总是在有事务的连接上执行操作。如果不是在一个事务内,正常的DataSource行为就会起作用。

换言之,如果没有使用事务去执行多个复杂的操作,jOOQ将会为每一个操作使用不同的连接,从而将会导致竞态条件下的bug。

我是在阅读Ben Manes写的这条评论时发现这个问题的。

2. TransactionAwareDataSourceProxy 在它的Javadoc里声明了不推荐使用它。

TransactionAwareDataSourceProxy类的Javadoc 里有这么一段话:

这个代理可以使数据访问代码能够和普通的JDBC API一起工作,并且能够加入由Spring管理的事务,就跟J2EE/JTA环境下的JDBC代码很类似。然而,如果可能,请尽量使用Spring的DataSourceUtilsJdbcTemplate或JDBC操作对象去获取事务的参与,即使没有使用对目标DataSource的代理,也尽量不要一开始就考虑定义如此一个代理。

这是一段非常模糊的评论,因为它并没有解释为什么我们不应该使用它。 Adam Zell则认为是由于这个类使用了反射,使用它可能会有性能问题。

如果你遇到了性能问题,你也许可以试试在Adam Zell的Gist里描述的方法。

总结

我们现在已经成功了配置好了我们的示例程序的应用上下文。这篇教程教会了我们四件事情:

  • 我们学会了如何使用Maven得到所需的依赖。
  • 我们学会了如何将由jOOQ抛出的异常转换为Spring的DataAccessExceptions。
  • 我们还学会了如何使用配置使用jOOQ和Spring的应用程序的应用上下文。
  • 我们还快速浏览了当我们使用这篇博文中提到的那些方法时那些我们需要留意的东西。

更多精彩内容以及学习资料,尚学堂论坛bbs.bjsxt.com免费下载。

  • 北京校区
  • 西安校区
  • 山西校区
  • 武汉校区
  • 深圳校区
  • 上海校区
  • 广州校区
  • 保定招生办
  • 黑龙江项目办

北京京南校区:北京亦庄经济开发区科创十四街6号院1号楼 赛蒂国际工业园
咨询电话:400-009-1906 / 010-56233821
面授课程: JavaEE+微服务+大数据     大数据+机器学习+平台架构     Python+数据分析+机器学习  人工智能+模式识别+强化学习   WEB前端+移动端+服务端渲染

地址:陕西省西安市高新区西安软件园西区创新信息大厦A座三层尚学堂

电话:029-88228155 / 18291433445

山西学区地址:山西省晋中市榆次区大学城大学生活广场万科商业A1座702

武汉学区地址:武汉市东湖高新区光谷金融港B22栋11楼
咨询电话:027-87989193

深圳校区地址:深圳市宝安区航城大道U8智造产业园U6栋3楼
咨询电话:0755-23061965 / 18898413781

上海尚学堂校区地址:上海市浦东新区城丰路650号
咨询电话:021-67690939

广州校区地址:广州市天河区车陂街道大岗路5号中侨广场2栋321室(四号线车陂站D出口,或brt车陂站)
咨询电话:18948349646

保定招生办公室

地址:河北省保定市竞秀区朝阳南大街777号鸿悦国际1101室

电话:15132423123

黑龙江项目办
地点:哈尔滨市松北区博文路青年部落孵化器1层
电话:15321415678
Copyright 2006-2021 北京尚学堂科技有限公司  京ICP备13018289号-19  京公网安备11010802015183   营业执照
网站维护:北京尚学堂科技有限公司昌平分公司