是否可以结合 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?的主要内容,如果未能解决你的问题,请参考以下文章