标准 专业
多元 极客

ORM的小清新(1)——JOOQ

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代码生成

由于本案例中只包含一个表,所以类的个数寥寥无几。在实际使用中,我们无需记住每个类的名字,仅需记住表名即可,对于带有_标识的表,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时,会给你一个大大的拥抱:

Thank You for Using JOOQ

自行打开postman进行测试,展示下JOOQ大法:

JOOQ简单插入

和上古MyBatis一样,返回的是整型,各有利弊吧。

接下来我们进行一下查询:

JOOQ简单查询

 

这就是JOOQ的代码风格,让我们在Java类中直接把控SQL语句的同时,对核心操作进行简单的封装,代码一气呵成。

当然,仅仅是链式调用是不足以证明JOOQ的,笔者会一步一步带你引入这个小清新的世界。

赞(1) 投币

评论 抢沙发

慕勋的实验室慕勋的研究院

码字不容易,路过请投币

支付宝扫一扫

微信扫一扫