摘要:这篇文章主要是对之前一段时间学习Jooq的总结。
前言:相比较Hibernate、Mybatis等大家早已熟知的ORM框架来说,JOOQ只能算是一个弟弟。但它之所以能够脱颖而出,是因为它找到了正确的设计思想。
Jooq简介
JOOQ 是基于Java访问关系型数据库的工具包,轻量,简单,并且足够灵活,可以轻松的使用Java面向对象语法来实现各种复杂的sql。不管是Hibernate或者Mybatis,都能简单的使用实体映射来访问数据库,但有时候这些“高级”的对象关系映射,没有直接使用原生sql来的灵活和简单,而且对于一些如:joins,union, nested selects等复杂的操作支持的不友好。JOOQ 既吸取了传统ORM操作数据的简单性和安全性,又保留了原生sql的灵活性,它更像是介于ORM和JDBC的中间层。
相比其它ORM,JOOQ更注重于对数据库模型的建模。它拥有以下特点:
- 面向SQL
- 构建类型安全的SQL
- 强大的代码生成工具
- 方便的乐观锁等高级特性
详细的可以看看Jooq官网介绍。
如果说它有什么缺点,那就是它对一些商用数据库,如Oracle、SQL Server是收费的。
快速上手Jooq
接下来,让我们通过官网的《JOOQ7步走》中的例子快速上手JOOQ吧。
第一步 下载JOOQ
首先需要在JOOQ的下载页面下载它。如果使用的是开源版本,可以直接通过Maven等工具下载。
第二步 创建数据库
1 | CREATE DATABASE `test`; |
第三步 生成代码
JOOQ生成代码方式有多种,可以按照官网的方法生成代码。然后就可以在自己的代码中使用这些生成的代码了。
或者参考下面配置,修改你的pom.xml,然后在Terminal运行:mvn jooq-codegen:generate
1 | <properties> |
第四、五、六步 完成增删改操作
具体代码如下:
1 | import org.jooq.DSLContext; |
探索 :略
特性
1、生成POJO
首先在生成配置中添加生成POJO的选项:
1 | <generate> |
重新运行生成工具,一个 student 类就生成了:
1 | import java.io.Serializable; |
然后可以使用JOOQ的API方便的将查询结果和POJO相互转化了:
1 | // Record to POJO |
需要注意的是:
from方法提供了多个重载,可以从Map或Array获取数据from方法从Object获取数据使用了反射的方式,因此效率会比较低。【后面会有解决办法】
2、生成接口
Java提倡面向接口编程,JOOQ也提供了生成接口的配置:
1 | <generate> |
生成的接口是这样的:
1 | import java.io.Serializable; |
如果选择生成“接口”,则生成的Record和POJO都将继承该接口。
而之前提到的from方法也将会增加一个IStudent的重载,并使用其中的Getters, Setters,从而具有更好的性能。
如果只生成接口,而不生成Record或POJO,则JOOQ会使用proxy动态代理对象的方法来达到相同的效果。
3、生成Annotation
JOOQ可以选择生成JPA和JSR-303的注解,从而能够更好的和JPA框架配合,并提供更加强大的校验能力:
1 | <jpaAnnotations>true</jpaAnnotations> |
选择之后,以IStudent为例,生成的代码将变成这样:
1 | import java.io.Serializable; |
4、Builder模式
到了这一步,也许你已经在问自己,为什么没有早点认识JOOQ了,能省不少事呢。不过你知道吗,JOOQ还可以做得更多——它能生成具有Builder模式的Bean:
添加以下配置:
1 | <generate> |
配置fluentSetters为true时,Setter将返回自身:
1 | public Student setId(Integer id) { |
生成的代码将可以这样使用:
1 | IStudent student = dslContext.newRecord(STUDENT); |
这样代码字段设置和之前相比简洁了很多。
5、DAO和spring
如果你是DAO模式和Spring的忠实粉丝,还可以增加以下两个配置,无缝对接Spring的集成:
1 | <daos>true</daos> |
生成的代码类似这样:
1 | import java.util.List; |
6、无符号数字类型
SQL支持无符号数字,但是java没有内建无符号数字的类型,为此JOOQ的作者提供了JOOU来处理无符号数。
例如对上面的age字段,类型可以设置成UInteger,可以使用age.intValue()获取对应的int数值。尽管UInteger更加准确,但是如果你觉得增加一个intValue()的调用会比较繁琐。可以通过下面这个配置使POJO直接使用int作为字段类型:
1 | <database> |
7、乐观锁支持
一个修改的常见场景是:用户读取了记录,作了修改之后再保存入数据库。如果这个时间里,有人修改了数据库的同一个字段,则上一个人的修改就被覆盖了。
乐观锁就是再出现这种情况时,通过抛出异常或其他方式,来提醒用户改动不安全,需要刷新并重新修改记录。
jOOQ允许您使用乐观锁定执行CRUD操作。您可以通过激活相关的executeWithOptimisticLocking设置立即利用此功能。如果不进一步了解基础数据语义,这将对store()和delete()方法产生以下影响:
- INSERT语句不受此Setting标志的影响。
- 在UPDATE或DELETE语句之前,jOOQ将运行SELECT .. FOR UPDATE语句,悲观地锁定记录以用于后续的UPDATE(DELETE)。
- 使用先前的SELECT提取的数据将与存储或删除的记录中的数据进行比较。
- 一个org.jooq.exception.DataChangedException如果记录已经在平均时间被修改,则被抛出。
- 如果同时未修改记录,则记录成功UPDATE(DELETE)。
JOOQ提供了一个配置能方便地实现乐观锁,而不需要修改业务代码【详见文档】:
1 | // Properly configure the DSLContext |
JOOQ的乐观锁模式虽然对业务逻辑代码没有侵入性,但是需要数据表提供numeric VERSION或TIMESTAMP字段,比如下表的last_modified_time字段:
1 | CREATE TABLE `student` ( |
然后在代码生成中添加下面的配置:
1 | <database> |
最后生成的Table类中将增加一个getRecordTimestamp方法供乐观锁使用:
1 | public TableField<StudentRecord, Timestamp> getRecordTimestamp() { |
应用集成
JOOQ与hikaricp集成管理数据源
JOOQ唯一的外部依赖就是一个JDBC连接或JDBC资源池DataSource。JOOQ只会使用这个连接构建PreparedStatement和执行SQL,并不会管理这个连接的生命周期。这样做的好处是模块功能划分做的很彻底,数据源的事情由专门的数据源库来管理DataSource是一个独立的模块,我们可以灵活地配置它。
应用开发需要引入一个第三方的DatatSource数据源来管理数据库连接;这里介绍一下hikaricp的集成方式:
Hikari提供了多种方式创建数据源,例如可以程序直接创建:
1 | HikariConfig config = new HikariConfig(); |
观察者
ExecuteListener 可以看作是一个JOOQ执行的观察者,它可以监控SQL执行的整个生命周期。并且可以通过执行上下文,做一些个性化的操作。下面SlowQueryListener类的作用就是收集sql执行过程的慢查询日志。
1 | class SlowQueryListener extends DefaultExecuteListener { |
然后在初始化 DSLContext 的时候把SlowQueryListener配置进去,代码如下:
1 | dslContext.configuration() |
分库
数据库分库是生产环境中经常使用的方式,例如我们提供了一个新的class的库,复用了student表。如果我们要查询class库,还需要重新生成class的代码,那就太麻烦了。JOOQ提供了运行时替换库名的能力,使得在生产环境中使用多个分库变得很轻松。还以student的查询为例:
1 | DSLContext dsl = DSL.using(conn, SQLDialect.MYSQL); |
对应的默认查询语句是这样的:
1 | select |
如果我们配置了动态替换
1 | Settings settings = |
则生成的语句就变为class下的SQL了:
1 | select |
JOOQ还支持多个Schema的动态替换,以及表名的动态替换,详见Runtime schema mapping
复用数据库实例
因为JOOQ的SQL是带有Schema名称的,所以对同一个IP实例中的不同数据库,JOOQ可以方便的支持查询。
这在当多个数据库共用一个数据库实例的时候,很有用,可以复用数据源配置,从而不需要为每一个库创建一个连接池,减少数据库连接资源的占用。
1 | dataSource = ImmutableMap.<String, HikariDataSource>builder() |
依赖注入
Spring: http://www.jooq.org/doc/3.8/manual-single-page/#jooq-with-spring
JOOQ:https://github.com/jOOQ/jOOQ/tree/master/jOOQ-examples/jOOQ-spring-guice-example
其他常用操作:Batch、Curser、Transaction
- Batch: http://www.jooq.org/doc/3.8/manual/sql-execution/batch-execution/
- 游标 Curser: http://www.jooq.org/doc/3.8/manual/sql-execution/fetching/lazy-fetching/
- 事务: http://www.jooq.org/doc/3.8/manual-single-page/#transaction-management
其它运行时配置
JOOQ将一些不常用的配置,放在Settings对象类管理,例如
- 是否使用乐观锁
- 是否打印JOOQ的SQL日志
具体可以参考JOOQ文档
- 本文作者: th3ee9ine
- 本文链接: https://www.blog.ajie39.top/2021/05/05/ORM之他暂时可能只是个弟弟——Jooq/
- 版权声明: 本博客所有文章除特别声明外,均采用 LICENSE 下的许可协议。转载请注明出处!