是否可以结合 MyBatis 和 QueryDSL/jOOQ?

Posted

技术标签:

【中文标题】是否可以结合 MyBatis 和 QueryDSL/jOOQ?【英文标题】:Is it possible to combine MyBatis and QueryDSL/jOOQ? 【发布时间】:2014-12-25 16:06:39 【问题描述】:

MyBatis 提供开箱即用的映射、本地缓存和注销。 QueryDSL / jOOQ 提供 SQL 语句的编译时检查和 IDE 自动完成功能。 可以合并吗?

换句话说,我想用 QueryDSL 或 jOOQ 创建一个查询,然后用 MyBatis 的一些胶水代码/适配器执行它

我已经检查过的内容:

我考虑用 QueryDSL 生成 SQL 查询字符串,并在 MyBatis 中使用它的 '@SelectProvider' 注释,但这似乎是一个死胡同:MyBatis 在它的 SQL 字符串中需要 "$xxx" 的东西,但是 QueryDSL仅根据实际的 Java 类型生成查询,因此即使是 ID 也不起作用。 MyBatis Generator 作为 QueryDSL/jOOQ 的替代品:相当糟糕的替代品,因为它实际上会生成一个样板代码,您以后必须维护和扩展它 MyBatis SQL Builder 作为 QueryDSL/jOOQ 的替代品:比 QueryDSL 或 jOOQ 弱得多,例如它不提供列名的编译时检查,更麻烦,并且依赖于使代码复杂化的“@SelectProvider”

【问题讨论】:

MyBatis 也支持类型安全的 DSL。您可以在 XML 或 Annotations 中使用它来代替旧的普通 SQL。 【参考方案1】:

我将从高层次回答——所以我不会深入研究 QueryDSL 和 jOOQ 之间的实际区别,在讨论的这一点上,它们“只是”都在 Java 中提供了类型安全的嵌入式 SQL。不过,我会从 jOOQ 的角度给出答案,因为我知道那个 API 要好得多。

免责声明:我是从为 jOOQ 背后的公司工作的人的角度给出这个答案的。

是的,你可以:

是的,您可以将 jOOQ 与 MyBatis 结合使用。例如,您可以像这样从 jOOQ 查询中提取查询字符串:

Select<?> query =
DSL.using(configuration)
   .select(TABLE.A, TABLE.B)
   .from(TABLE)
   .where(TABLE.C.eq(1));

String sql = query.getSQL();
List<Object> bindvalues = query.getBindValues();

许多人实际上使用这种技术然后使用 Spring JDBC 而不是 jOOQ 运行查询。他们这样做的主要原因是他们已经广泛使用 Spring JDBC,并且他们不希望在他们的堆栈上执行两种类型的查询。

但你为什么会:

您添加到堆栈中的每个 API 也会增加堆栈行为方式的复杂性和一组规则。另一方面,您有一组想要通过这些 API 实现的功能。让我们确定这些特征:

    类型安全的嵌入式 SQL 缓存 日志记录 将非规范化 SQL 结果映射到您的域

1) 类型安全的嵌入式 SQL

这很简单。你可能不想为此使用 MyBatis。该实现(如您所见)更像是概念证明。所以你来到了jOOQ的选择

2) 缓存

我个人认为您过早下结论。 MyBatis 有很多非常简单的实现,但是在缓存的情况下,我很确定你想要实现更通用的缓存策略,例如使用新的 JSR-107 caching support, like the one from Spring 仅仅是因为缓存与 SQL 的耦合并不是那么紧密。

3) 日志记录

jOOQ for instance implements easy to hook into logging hooks,但您也可以在 JDBC 级别使用日志记录 using a JDBC trace logging library or your JDBC drivers' capabilities

4) 映射

我所说的缓存在这里也是如此。 MyBatis 为您的映射算法提供了一个简单的默认实现,当您转向更复杂的映射场景时,这可能很快就不够了。 jOOQ 也是如此,顺便说一句,它也实现了default mapping for POJOs、which you can override any way you want。但就像缓存一样,映射并不是真正应该在 SQL 级别上解决的问题。您会发现更好的工具用于映射本身 - 例如Model Mapper(有built-in jOOQ support,顺便说一句)。或者,如果您在 Java 8 环境中,您可以使用 regular functional programming techniques to map stuff,例如像这样:

DSL.using(configuration)
   .select(
       COLUMNS.TABLE_NAME,
       COLUMNS.COLUMN_NAME,
       COLUMNS.TYPE_NAME
   )
   .from(COLUMNS)
   .orderBy(
       COLUMNS.TABLE_CATALOG,
       COLUMNS.TABLE_SCHEMA,
       COLUMNS.TABLE_NAME,
       COLUMNS.ORDINAL_POSITION
   )
   .fetch()

上图:jOOQ 代码。下图:Java 8 映射代码

   .stream()
   .collect(groupingBy(
       r -> r.getValue(COLUMNS.TABLE_NAME),
       LinkedHashMap::new,
       mapping(
           r -> new Column(
               r.getValue(COLUMNS.COLUMN_NAME),
               r.getValue(COLUMNS.TYPE_NAME)
           ),
           toList()
       )
   ))
   .forEach(
       (table, columns) -> 
            System.out.println(
                "CREATE TABLE " + table + " (");

            System.out.println(
                columns.stream()
                       .map(col -> "  " + col.name +
                                    " " + col.type)
                       .collect(Collectors.joining(",\n"))
            );

           System.out.println(");");
       
  );

这是this article底部的示例,它显示了如何查询H2 INFORMATION_SCHEMA的所有表,并将结果映射到CREATE TABLE语句

结论:

许多 API 倾向于诱使您使用它们,因为它们的非核心“功能”,例如缓存或映射,这些对于 SQL API 来说确实是非核心功能。您将使用简单的用例和宠物/猫/狗或作者/书籍应用程序快速启动并运行,但您会被更复杂的应用程序卡住。在您的用例中,类型安全的嵌入式 SQL 功能是您想要使用类似 jOOQ 的原因。您正在寻找的其他功能都不是使用 MyBatis 的非常有说服力的理由(其核心功能是外部 SQL 文件,这与嵌入 SQL 的 jOOQ 完全相反)。

因此,我对您的建议是:为您希望堆栈中的每个功能确定最佳工具,并查看这些工具是否易于组合。我们在实现 jOOQ 时非常小心,允许任何第三方应用程序非常容易地插入到 jOOQ 中(例如缓存、日志记录、映射),因为这些功能不是 jOOQ 的核心功能。

【讨论】:

【参考方案2】:

就像在卢卡斯非常完整的答案中加点一样。

从我的基准测试来看,MyBatis 在映射上有一些严重的性能问题,Jooq 的性能曲线要好得多。地图上的 Jooq 就是这样。

https://github.com/arnaudroger/SimpleFlatMapper#local-mysql

还有 SimpleFlatMapper 作为 querydsl 和 spring 数据的集成点。我不知道您可以将自己的映射与 Jooq 集成,并将其添加到积压中。

【讨论】:

“我不知道您可以将自己的映射与 Jooq 集成,并将其添加到待办事项中。” - 那会很有趣!感谢这个基准 #user3996996:好东西,但对我来说,它缺乏对可以覆盖列名的注释的支持。我确实有一个用例,我的对象必须从 CSV 文件中读取并转储到数据库中,但 CSV 中的列名与数据库中的列名不匹配。在我看来,使用元信息(只会生成一次)添加“映射上下文”的概念将大大增加使用 SimpleFlatMapper 的选项 目前处理不匹配名称的方法是使用别名。我现在一直在推迟引入注释。我仍然认为别名会比注释更好。用元信息映射上下文是什么意思?用例是什么? 我还首次尝试实施 RecordFieldMapperFactory 9.15,有一些性能问题链接到应该很快得到修复的转换过程。我应该在周末完成这一切。

以上是关于是否可以结合 MyBatis 和 QueryDSL/jOOQ?的主要内容,如果未能解决你的问题,请参考以下文章

如何从querydsl获得完全物化的查询

使用 Redshift 设置的 QueryDSL

使 JPQL/QueryDSL 不会产生可怕的查询

Querydsl喜欢惊喜

mybatis结合redis实战二级缓存

如何使用 QueryDSL 在 Spring Data JPA 中使用 order by 和 Limit