Data Geekery 发布了 Java ORM 工具 jOOQ 的 3.9.0 版,用于构建类型安全查询

阅读数:2409 2017 年 2 月 22 日 18:00

Data Geekery 公司发布了其Java 对象关系映射(ORM,Object-Relational Mapping)工具包 jOOQ 的 3.9.0 版。jOOQ 首次推出于 2010 年 8 月,实现从数据库生成代码,用于类型安全查询。该新版本给出的新特性包括:

  • 实验性的语法解析器:

    • 将字符串形式的 SQL 语句解析为 jOOQ 表达式树。
  • 集成了 Checker 框架:

  • 改进了与 Oracle 12c 和 PL/SQL 的集成:

  • JSR-310 Java Time API:

    • 支持传统的 JDBC 日期 / 时间类型和 JSR-310 的 Java Time API 的一个单独的 API。

开源版本的 jOOQ 支持下列数据库:

jOOQ 的许可页面给出了一系列的许可选项。

入门指南

为初步了解如何构建 jOOQ 应用,我们考虑使用如下实体关系图去建模一个称为 pubs 的出版数据库:

我们使用 Maven,依赖关系中应包括数据库驱动和 jOOQ 的代码生成器:

<dependencies>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>6.0.3</version>
    </dependency>
    <dependency>
        <groupId>org.jooq</groupId>
        <artifactId>jooq</artifactId>
        <version>3.9.0</version>
    </dependency>
    <dependency>
        <groupId>org.jooq</groupId>
        <artifactId>jooq-meta</artifactId>
        <version>3.9.0</version>
    </dependency>
    <dependency>
        <groupId>org.jooq</groupId>
        <artifactId>jooq-codegen</artifactId>
        <version>3.9.0</version>
    </dependency>
</dependencies>

在配置文件的<profile>部分,定义数据库的相关属性:

<profile>
    <id>default</id>
    <activation>
        <activeByDefault>true</activeByDefault>
    </activation>
    <properties>
        <jdbc.user>root</jdbc.user>
        <jdbc.password></jdbc.password>
        <jdbc.url>jdbc:mysql://localhost:3306/pubs</jdbc.url>
        <jdbc.driver>com.mysql.cj.jdbc.Driver</jdbc.driver>
    </properties>
</profile>

下面 plugin 部分的<generate></generate>标签对内,定义了 Maven 编译目标、生成和 jOOQ 代码生成器的属性,其中包括使用的数据库、数据库模式,并定义了 jOOQ 生成的 Java 代码包。

<plugin>
    <groupId>org.jooq</groupId>
    <artifactId>jooq-codegen-maven</artifactId>
    <version>3.9.0</version>
    <!-- The plugin should hook into the generate goal -->
    <executions>
        <execution>
            <goals>
                <goal>generate</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <jdbc>
            <driver>${jdbc.driver}</driver>
            <url>${jdbc.url}</url>
            <user>${jdbc.user}</user>
            <password>${jdbc.password}</password>
        </jdbc>
        <generator>
            <database>
                <name>org.jooq.util.mysql.MySQLDatabase</name>
                <includes>.*</includes>
                <excludes></excludes>
                <inputSchema>pubs</inputSchema>
            </database>
            <target>
                <packageName>org.redlich.pubs.model</packageName>
                <directory>src/main/java</directory>
            </target>
        </generator>
    </configuration>
</plugin>

以下 pubs 数据库 SQL 查询示例将被 jOOQ 建模:

SELECT title,publish_date,authors.last_name,types.type,publishers.publisher
FROM publications
INNER JOIN authors ON authors.id = publications.author_id
INNER JOIN types ON types.id = publications.type_id
INNER JOIN publishers ON publishers.id = publications.publisher_id;

使用 jOOQ 生成的代码,可以编写实现访问数据库和编写类型安全查询的应用:

public class Application {
    public static void main(String[] args) throws Exception {
        String user = System.getProperty("jdbc.user");
        String password = System.getProperty("jdbc.password");
        String url = System.getProperty("jdbc.url");
        String driver = System.getProperty("jdbc.driver");
        Class.forName(driver).newInstance();
        try(Connection connection = DriverManager.getConnection(url,user,password)) {
            DSLContext dslContext = DSL.using(connection,SQLDialect.MYSQL);
            Result result = dslContext.select()
                .from(PUBLICATIONS)
                .join(AUTHORS)
                .on(AUTHORS.ID.equal(PUBLICATIONS.AUTHOR_ID))
                .join(TYPES)
                .on(TYPES.ID.equal(PUBLICATIONS.TYPE_ID))
                .join(PUBLISHERS)
                .on(PUBLISHERS.ID.equal(PUBLICATIONS.PUBLISHER_ID))
                .fetch();
            for(Record record : result) {
                Long id = record.getValue(PUBLICATIONS.ID);
                String title = record.getValue(PUBLICATIONS.TITLE);
                Long authorID = record.getValue(PUBLICATIONS.AUTHOR_ID);
                String lastName = record.getValue(AUTHORS.LAST_NAME);
                String firstName = record.getValue(AUTHORS.FIRST_NAME);
                String type = record.getValue(TYPES.TYPE);
                String publisher = record.getValue(PUBLISHERS.PUBLISHER);
                Date publishDate = record.getValue(PUBLICATIONS.PUBLISH_DATE);
                }
            }
        catch(Exception exception) {
            exception.printStackTrace();
            }
        }
    }

注意 jOOQ 所生成的表名和表的属性名是大写的。完整的 jOOQ 项目代码可以在 GitHub 上找到。

Data Geekery GmbH 的创始人兼 CEO Lukas Eder 向 InfoQ 介绍了最新版的 jOOQ。

InfoQ:您当前在 Data Geekery 的担任什么职责?

Eder:我是推出 jOOQ 产品的 Data Geekery 公司的创立人和 CEO。我还担任 SQL 和 PL/SQL 的顾问,主要针对性能优化。我当前正在开展一些 SQL 培训项目,今年将写出一本关于 SQL 的书。

InfoQ:是什么使得 jOOQ 不同于 Hibernate、Speedment 和 Apache Torque 等其他的 Java ORM 框架的?

Eder:在我个人看来,对比各种框架间的哲学 / 方法是十分有意思的,将每一个具体的实现看成是一种,嗯……该怎么说呢,实现细节吧。

我认为 Java 操作 RDBMS 的方式至多有五类。下面我为每类方法给出几个例子,但实际肯定远远不止这些:

  • 将 SQL 逻辑从 Java 中分离处理:例如:MyBatis、厂商特定的存储过程、视图和 jOOQ 的存储过程等。
  • 将 SQL 逻辑作为 SQL 语句字符串嵌入 Java 中:例如:JDBC、jOOQ 的纯 SQL、Spring、JDBi、JPA 原生查询等。
  • 以 SQL 为中心的内部领域特定语言(DLS)方式,将 SQL 逻辑嵌入 Java:例如:jOOQ、Criteria API(用于 JPQL 而非 SQL)等。
  • 以“符合语言习惯”的 API 集合方式,将 SQL 逻辑嵌入 Java:例如:Speedment、JINQ、Slick(Scala 编写)、LINQ(.NET 编写)等。
  • 通过 ORM 或活动记录(Active Record),将 SQL 逻辑嵌入 Java:例如:JPA 及其实现,包括 Hibernate、jOOQ UpdatableRecords、ActiveJDBC 等。

上述各类技术方法各有其优劣之处。jOOQ 侧重于前三类方法,因为所有这些方法是以 SQL 为中心的,即在用户与数据库的交互方式中 SQL 语言是其重要的组成部分。这些用户不认可由“符合语言习惯”的 API 集合或 ORM 所强加的认知障碍,他们只是想编写 SQL。

提供支持代码生成的流畅 API 是 jOOQ 的主要特性。这些 API 从高层次上提供了编译时对开发人员的类型安全,无需对 SQL 语言做过多妥协。对此内部 DSL 技术,我写过一个博客文章:

https://blog.jooq.org/2012/01/05/the-java-fluent-api-designer-crash-course

同时,jOOQ 鼓励开发人员:

  • 通过视图和存储过程将业务逻辑推送到数据库。视图和存储过程在 jOOQ 中同样非常易用。
  • 使用高级标准和厂商特定的 SQL 特性,这在分析和报表时尤为有用。
  • 以 RDBMS 为中心的方法思考。当你的系统规模增长超过了微不足道的单客户或十数个表这样规模数据库时,这就是种非常好的工作方式了。

当然,有意思的是认识到各种方法之间并非必须要相互竞争。很多 jOOQ 用户也在同一应用中使用 Hibernate,例如:

  • 将 jOOQ 用于分析、ETL、报表和复杂查询。
  • 在一次事务中更新大量实体时,将 Hibernate 用于辅助 CRUD。

最后一点,作为一种实现方式,jOOQ 重视那些寻求精准实现的用户,对于这些用户而言,SQL 语言十分重要。并且我也敢于这样说,jOOQ 帮助了不少客户理解到,SQL 对很多问题而言常是最好的解答,比他们想象的还要好。

InfoQ: 您为我们介绍一下使用 jOOQ 的友商和客户的情况吧?

Eder: 一般而言,jOOQ 有助于友商和客户超越他人并成为更好的开发人员。让我解释一下。

我时常非常有兴趣去了解我们用户基础的多样性。一开始,我们按自己的做事和思考问题方式设计 jOOQ。在创建 Data Geekery 之前,我供职于一个大型瑞士银行的电子银行系统团队,时至今日我依然帮助他们解决 Oracle SQL 性能问题。很显然,他们也在 Oracle 上使用 jOOQ。

他们的系统中大约有 500 多张数据表,其中一些表有数十亿行的数据,数以千计的视图,数百个 PL/SQL 软件包,并伴以大量的 Java 代码。大量的业务逻辑是在深度嵌套的 SQL 视图和过程中实现的。当然,这个系统也可以使用一些其他的方式构建,但是我们的以 SQL 为中心的系统给出了一个非常优雅的设计,的确在 Oracle 数据库上运行得很好。

并非所有的公司都有他们那样的复杂数据库,区别 jOOQ 在这些公司中的应用的是 jOOQ 为 Java/SQL 开发所带来的简单性和乐趣。你也知道,SQL 是一门古老的语言,所有的关键字和有意思的语法让 SQL 看上去十分奇特(对此我近期有一篇博文,地址是: https://blog.jooq.org/2016/12/09/a-beginners-guide-to-the-true-order-of-sql-operations/ )。

声明式编程使用了受关系代数所启发的编程语言, 在声明式编程中存在着大美之处。使用 jOOQ 可以简单并直观地编写 SQL 语句,这对于开发人员而言确实是一种激励。我听到的最好的事情(从不少的客户那里!)是 jOOQ 是如何有助于满足他们的好奇心,去探索最宽广、最激动人心的 SQL 世界。

换句话说,对于分布在各行各业中才华横溢的客户,我们只是充当他们的催化剂。

InfoQ: jOOQ 中将会出现哪些特性?

Eder: 我们对与 SQL 有关的所有特性都有兴趣。当前,我们正对一个内建的 SQL 解析器进行实验(已经添加在 3.9 版本中)。这样 jOOQ“仅仅”是一个 SQL 表达式树模型库。使用 jOOQ 构建 SQL 语句的用户不再需要构建查询字符串,而是构建一个表达式树,这个树会生成 SQL 字符串。为什么不反其道而行之,将 SQL 字符串解析为 jOOQ 表达式树呢?我们这样做,使得用户可以将一种 SQL 字符串(例如 DB2 的)转换为另一种的 SQL 字符串(例如 PostgreSQL 的)。这是对于遗留数据库是一种多么好用的迁移工具呀!敬请期待!

jOOQ 已有内建的 SQL 转换 SPI,可对我们前面到的表达式树进行转换。这里可能给出包括类似于多租户、行级安全、性能优化以及更多的杀手级特性。这些特性在昂贵的商业数据库中是即可使用的,但是在不少开源数据库中却并非如此。我们已经提供了架构,未来我们将提供这样的即可使用特性。

SQL:2011 规范中,最令人激动的特性莫过于可以编写时态查询。假定对于一家保险企业,如果某个特定的政策发生了变化。不同于传统的 SQL,你不需要使用 UPDATE 语句去实际更改数据记录,只需为具有不同时效的同一逻辑策略创建一个新纪录。SQL:2011 允许清晰地编写一个时态 UPDATE 语句,同样也适用于时态 DELETE 语句的编写。我们正探索在所有的数据库中仿效该特性,包括不支持时态查询的数据库(包括除 Oracle 和 DB2 之外的大部分数据库)。

我们还有更多的想法。SQL 语言是一个庞大的生态系统,我们拥抱一切与 SQL 相关的事情。因此敬请期待更为激动人心的 jOOQ 版本!

资源

更多的 jOOQ 相关内容,可参考下列文章:

查看英文原文: Data Geekery Releases Version 3.9.0 of jOOQ, a Java ORM Tool for Building Type Safe Queries


感谢冬雨对本文的审校。

给 InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ @丁晓昀),微信(微信号: InfoQChina )关注我们。

评论

发布