ORM的小清新——JOOQ
在被传统行业的Hibernate和互联网行业的MyBatis垄断之下,后台CRUD开发越来越无聊。而Python等解释型语言快速、便捷、搭建速度快的特点,不断地冲击着Java后台之王的宝座。Hibernate上手成本高,MyBatis繁重的SQL代码,在我们的心里割下了一块阴影。
是时候引入一个小清新的ORM框架了。
JOOQ,Java Object Oriented Query,面向Java对象查询。当然现在应该称之为面向JVM查询,因为经过1.0、2.0、3.0版本的迭代,在最新的3.0版本已经可以支持Groovy、Scala、Kotlin的语法。
JOOQ,没有罗汉塔一样的封装,没有copy→paste的SQL语句,没有花哨的功能,我们只想写普通的SQL。
好了书归正传,JOOQ通过它本身的代码生成工具,自动生成数据库相关代码,并且对JDBC进行了简单的封装,让我们在新一代的体验之下,又不失对过去的回味。
本节是对JOOQ的开门见山。
官方给出了JOOQ的几大优势:
- 数据库优先。
- 类型安全的SQL。
- 代码自动生成。
- 动态Record。
- 多场景。
- 标准化。
- 查询的生命周期。
- 过程。
针对Java项目,要想使用JOOQ,首先需要引入相关工具包:
<!-- jOOQ -->
<dependency>
<groupId>org.jooq</groupId>
<artifactId>jooq</artifactId>
<version>x.y.z</version>
</dependency>
<dependency>
<groupId>org.jooq</groupId>
<artifactId>jooq-meta</artifactId>
<version>x.y.z</version>
</dependency>
<dependency>
<groupId>org.jooq</groupId>
<artifactId>jooq-codegen</artifactId>
<version>x.y.z</version>
</dependency>
我们来介绍下这几个基础包的含义:
- jooq。核心包,CRUD核心类所在大本营。
- jooq-meta。数据管理和操作的核心代码。
- jooq-codegen。负责数据库代码生成,主要负责JOOQ的代码生成功能。
接下来就将进入老生常谈部分——CRUD。
JOOQ这么小清新,我们肯定要使用一个能配的上它的基础组件——Spring Boot。
让我们享受一下快速搭建项目的过程,快速引入上面给出的Jar包,坐标请使用按需选择。
接下来我们配置下application.yml:
spring:
datasource:
url: jdbc:mysql://localhost:3306/jooq
username: root
password: 123456
server:
port: 10010
基础建设完毕,接下来就是主角建设了:
/**
* @author <sunshine> mysunshinedreams@163.com
* @date 2017/7/23.
*/
@Configuration
public class JooqConfig {
@Autowired
private Environment environment;
@Bean(name = "dslContext")
public DSLContext getDSLContext() {
// 配置DruidDataSource
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setUrl(environment.getProperty("spring.datasource.url"));
druidDataSource.setUsername(environment.getProperty("spring.datasource.username"));
druidDataSource.setPassword(environment.getProperty("spring.datasource.password"));
// 配置JOOQ
ConnectionProvider connectionProvider = new DataSourceConnectionProvider(druidDataSource);
org.jooq.Configuration configuration = new DefaultConfiguration();
configuration.set(connectionProvider).set(SQLDialect.MYSQL);
// 领域对象编程
return DSL.using(configuration);
}
}
我们需要创建一个配置文件,像配置SqlSessionFactory那样配置一个JOOQ(当然你可以选择不配置)。
配置了这么半天,代码自动生成呢?
别急,首先我们需要配置一个代码生成配置:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<configuration xmlns="http://www.jooq.org/xsd/jooq-codegen-3.9.2.xsd">
<!-- 配置jdbc驱动连接 -->
<jdbc>
<driver>com.mysql.jdbc.Driver</driver>
<url>jdbc:mysql://localhost:3306/jooq</url>
<user>root</user>
<password>yangsl</password>
</jdbc>
<generator>
<!-- 代码生成器 -->
<!-- 你也可以使用其他代码生成器,比如org.jooq.util.ScalaGenerator或者自定义代码生成器 -->
<!-- 默认org.jooq.util.JavaGenerator代码生成器 -->
<name>org.jooq.util.JavaGenerator</name>
<database>
<!-- 数据库类型 -->
<name>org.jooq.util.mysql.MySQLDatabase</name>
<!-- 数据库名 -->
<inputSchema>jooq</inputSchema>
<!-- 生成包含,*表示包含所有内容 -->
<includes>.*</includes>
<!-- 剔除,此处未剔除 -->
<excludes></excludes>
</database>
<target>
<!-- 生成的代码所在的包结构 -->
<packageName>com.sunshine.jooq</packageName>
<!-- 生成的代码存放路径,默认会以src同目录开始 -->
<directory>src/main/java/</directory>
</target>
</generator>
</configuration>
接着,打开我们的Terminal,cd到我们的项目目录(IDEA便捷性突出出来),执行以下下面的命令(具体路径自行配置):
java -classpath jooq-3.9.5.jar;jooq-meta-3.9.5.jar;jooq-codegen-3.9.5.jar;mysql-connector-java-5.1.18-in.jar;.
org.jooq.util.GenerationTool library.xml
什么?你准备x掉不看了?
老哥先别走,JOOQ也知道自己这么做很low,所以它实现了一个maven插件,真是美滋滋:
<dependency>
<groupId>org.jooq</groupId>
<artifactId>jooq-codegen-maven</artifactId>
<version>x.y.z</version>
</dependency>
对应的插件配置如下:
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8
</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<!--防止maven改动IDE的language level -->
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<!--数据库迁移所用的参数 -->
<db.url>jdbc:mysql://localhost:3306</db.url>
<db.username>root</db.username>
<db.password>yangsl</db.password>
<db.schema>jooq</db.schema>
</properties>
<build>
<plugins>
<!--数据库代码生成的插件 -->
<plugin>
<!-- 指定代码生成maven插件 -->
<groupId>org.jooq</groupId>
<artifactId>jooq-codegen-maven</artifactId>
<version>3.9.3</version>
<!-- 插件代码生成 -->
<executions>
<execution>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
<configuration>
<!-- JDBC连接配置 -->
<jdbc>
<driver>com.mysql.jdbc.Driver</driver>
<url>${db.url}</url>
<user>${db.username}</user>
<password>${db.password}</password>
</jdbc>
<!-- 代码生成配置参数 -->
<generator>
<database>
<name>org.jooq.util.mysql.MySQLDatabase</name>
<includes>.*</includes>
<inputSchema>${db.schema}</inputSchema>
<forcedTypes>
<forcedType>
<name>BOOLEAN</name>
<expression>.*\.HANDMADE</expression>
<types>.*</types>
</forcedType>
</forcedTypes>
</database>
<!-- 代码生成路径 -->
<target>
<packageName>com.sunshine.jooq</packageName>
<directory>src/main/java</directory>
</target>
</generator>
</configuration>
</plugin>
</plugins>
</build>
copy→paste总可以了吧,接下来无论是使用mvn jooq-codegen:generate命令还是IDE自带的Maven Plugins执行命令,都是一键发射,瞬间起飞(如果坠机,请自行检查JDBC配置)。
copy→paste之后不要忘记核心配置信息的修改。
起飞之后,我们可以看到,我们project的目录会多出这么一列东西:
由于本案例中只包含一个表,所以类的个数寥寥无几。在实际使用中,我们无需记住每个类的名字,仅需记住表名即可,对于带有_标识的表,JOOQ会优化为Java类的标准命名方式。
关于代码的分析,是在不久的未来。
数据源,连接,底层代码生成搞定,接下来就是我们的业务执行了,迅速摆上Service层代码(你不觉得JOOQ完成了的DAO层的任务吗?)。
/**
* @author <sunshine> yangsonglin@maoyan.com
* @date 2017/8/14.
*/
@Service("userService")
public class UserServiceImpl implements UserService {
@Resource(name = "dslContext")
private DSLContext dslContext;
@Override
public List<UserDO> getUserDOList() {
List<UserDO> userDOList = dslContext.select(USER.ID, USER.NAME, USER.ADDRESS, USER.SKILLS).from(USER).fetch().into(UserDO.class);
for (UserDO userDO : userDOList) {
System.out.println(userDO.getName());
}
return userDOList;
}
@Override
public int insertUser() {
UserRecord userRecord = dslContext.newRecord(USER);
userRecord.setName("Sundial丶Dreams");
userRecord.setAddress("Heilongjiang");
userRecord.setSkills("GO");
return userRecord.insert();
}
@Override
public int updateUser() {
return dslContext.update(USER).set(USER.NAME, "My Sunshine").set(USER.ADDRESS, "Heilongjiang").set(USER.SKILLS, "Full Stack").where(USER.ID.eq(3L)).execute();
}
@Override
public int deleteUser() {
return dslContext.deleteFrom(USER).where(USER.NAME.eq("Sundial丶Dreams")).execute();
}
}
为了抓住重点,我们将CRUD的SQL语句部分单独提炼出来:
dslContext.select(USER.ID, USER.NAME, USER.ADDRESS, USER.SKILLS).from(USER).fetch().into(UserDO.class);
dslContext.update(USER).set(USER.NAME, "My Sunshine").set(USER.ADDRESS, "Heilongjiang").set(USER.SKILLS, "Full Stack").where(USER.ID.eq(3L)).execute();
dslContext.delete(USER).where(USER.NAME.eq("Sundial丶Dreams")).execute();
这就是和Hibernate与MyBatis与众不同的Fluent API,方法链式调用。
MyBatis的Provider类型应该不属于链式调用,因为它的SELECT、WHERE等都相当于分别是一条语句。
既然说到Fluent API,那就简单说下Fluent API的优缺点吧。
优点:
- 写起来好爽,尤其是对于全栈工程师来说。
- 代码更为可读,比四维抽象编程很容易理解。
缺点:
- 空指针不好控制。
- debug调试想骂街。
- 通常搭配建造者模式,容易造成代码臃肿。
Fluent API维基百科官方指定Java示例——JOOQ。
好了,收回来。
针对上面的代码,我们将进行测试,为了测试逼真点,我们使用接口进行测试:
/**
* @author <sunshine> mysunshinedreams@163.com
* @date 2017/7/24.
*/
@RestController
@RequestMapping("/jooq")
public class JooqController {
@Autowired
private UserService userService;
@RequestMapping(value = {"/user.json"}, method = {RequestMethod.GET})
public List<UserDO> getUserDOList(HttpServletRequest request, HttpServletResponse response) {
return userService.getUserDOList();
}
@RequestMapping(value = {"/user/insert"}, method = {RequestMethod.POST})
public int insertUser(HttpServletRequest request, HttpServletResponse response) {
return userService.insertUser();
}
@RequestMapping(value = {"/user/update"}, method = {RequestMethod.PUT})
public int updateUser(HttpServletRequest request, HttpServletResponse response) {
return userService.updateUser();
}
@RequestMapping(value = {"/user/delete"}, method = {RequestMethod.DELETE})
public int deleteUser(HttpServletRequest request, HttpServletResponse response) {
return userService.deleteUser();
}
}
第一次执行JOOQ时,会给你一个大大的拥抱:
自行打开postman进行测试,展示下JOOQ大法:
和上古MyBatis一样,返回的是整型,各有利弊吧。
接下来我们进行一下查询:
这就是JOOQ的代码风格,让我们在Java类中直接把控SQL语句的同时,对核心操作进行简单的封装,代码一气呵成。
当然,仅仅是链式调用是不足以证明JOOQ的,笔者会一步一步带你引入这个小清新的世界。