jOOQ的主要价值主张是显而易见的:Java中的类型安全的嵌入式SQL。
当然,积极寻找这样一个SQL构建者的人将不可避免地偶然发现jOOQ并喜欢它。但是很多人并不真正需要SQL构建器 - 但是,jOOQ在其他情况下通过其鲜为人知的功能仍然非常有用。
这是前五个“隐藏”的jOOQ功能列表。
即使你不是直接使用jOOQ而是直接使用JDBC(或Spring JdbcTemplate等),最令人讨厌的事情之一就是使用ResultSet
。JDBC ResultSet
模拟数据库游标,它本质上是指向服务器上的集合的指针,可以定位在任何地方,即通过ResultSet.absolute(50)
(记住从1开始计数)到第50个记录。
JDBC ResultSet
针对延迟数据处理进行了优化。这意味着我们不必实现客户端中服务器生成的整个数据集。对于大型(甚至是大型)数据集来说,这是一个很好的功能,但在很多情况下,这是一个痛苦。当我们知道我们只获取十行并且我们知道我们在内存中将需要它们时,List<Record>
类型会更方便。
jOOQ的org.jooq.Result
是这样的List
,幸运的是,您可以使用DSLContext.fetch(ResultSet)
轻松导入任何JDBCResultSet
:
try (ResultSet rs = stmt.executeQuery()) {
Result<Record> result = DSL.using(connection).fetch(rs);
System.out.println(result);
}
考虑到这一点,您现在可以访问所有不错的jOOQ实用程序,例如格式化结果,即作为TEXT(有关详细信息,请参阅第二个功能):
+---+---------+-----------+
| ID|AUTHOR_ID|TITLE |
+---+---------+-----------+
| 1| 1|1984 |
| 2| 1|Animal Farm|
+---+---------+-----------+
当然,逆也是可能的。需要来自jOOQResult
的JDBC ResultSet
吗?调用Result.intoResultSet()
并且您可以将伪结果注入到在JDBCResultSet
上运行的任何应用程序:
DSLContext ctx = DSL.using(connection);
// Get ready for Java 10 with var!
var result = ctx.newResult(FIRST_NAME, LAST_NAME);
result.add(ctx.newRecord(FIRST_NAME, LAST_NAME)
.values("John", "Doe"));
// Pretend this is a real ResultSet
try (ResultSet rs = result.intoResultSet()) {
while (rs.next())
System.out.println(rs.getString(1) + " " + rs.getString(2));
}
正如我们在上一节中看到的,jOOQ Result
类型具有很好的格式化功能。您也可以格式化为XML,CSV,JSON,HTML和TEXT,而不仅仅是文本。
格式通常可以根据您的需要进行调整。
例如,这种文本格式也是可能的:
ID AUTHOR_ID TITLE
------------------------
1 1 1984
2 1 Animal Farm
格式化为CSV时,您将获得:
ID,AUTHOR_ID,TITLE
1,1,1984
2,1,Animal Farm
格式化为JSON时,您可能会得到:
[{"ID":1,"AUTHOR_ID":1,"TITLE":"1984"},
{"ID":2,"AUTHOR_ID":1,"TITLE":"Animal Farm"}]
或者,根据您指定的格式选项,您可能更喜欢更紧凑的数组样式数组?
[[1,1,"1984"],[2,1,"Animal Farm"]]
或者XML,再次使用各种常见的格式样式,其中包括:
<result>
<record>
<ID>1</ID>
<AUTHOR_ID>1</AUTHOR_ID>
<TITLE>1984</TITLE>
</record>
<record>
<ID>2</ID>
<AUTHOR_ID>1</AUTHOR_ID>
<TITLE>Animal Farm</TITLE>
</record>
</result>
HTML似乎很明显。你会得到:
ID | AUTHOR_ID | TITLE |
---|---|---|
1 | 1 | 1984 |
2 | 1 | Animal Farm |
或者,在代码中:
<table>
<tr><th>ID</th><th>AUTHOR_ID</th><th>TITLE</th></tr>
<tr><td>1</td><td>1</td><td>1984</td></tr>
<tr><td>2</td><td>1</td><td>Animal Farm</td></tr>
</table>
作为奖励,您甚至可以将结果导出为ASCII图表:
这些功能是普通jOOQ查询的明显补充,但正如我在第1节中所示,您也可以从JDBC结果中获得免费导出!
在上一节的导出功能之后,考虑如何再次将这些数据导回到更有用的格式是很自然的。例如,当您编写集成测试时,您可能希望数据库查询返回如下结果:
ID AUTHOR_ID TITLE
-- --------- -----------
1 1 1984
2 1 Animal Farm
只需将结果集的上述文本表示用Result.fetchFromTXT(String)
导入到实际的jOOQ Result
中,您就可以继续在jOOQ上运行Result
(或者如第1节所示,使用JDBC ResultSet
!)。
大多数其他导出格式(当然除了图表)也可以导入。
现在,你不希望Java有多行字符串(在这种情况下,这将非常好看):
Result<?> result = ctx.fetchFromTXT(
"ID AUTHOR_ID TITLE \n" +
"-- --------- -----------\n" +
" 1 1 1984 \n" +
" 2 1 Animal Farm\n"
);
ResultSet rs = result.intoResultSet();
现在可以将这些类型注入到服务或DAO生成jOOQ Result
或JDBCResultSet
的任何位置。最明显的应用是嘲弄。第二个最明显的应用是测试。您可以轻松地测试服务是否产生上述表单的预期结果。
我们来谈谈mocking.......
有时,mocking很酷。使用上述工具,jOOQ自然而然地提供了一个完整的,基于JDBC的模拟SPI。我在之前写过这个功能,并且在这里再一次提到了。
从本质上讲,您可以实现一个名为MockDataProvider
的FunctionalInterface
。创建一个的最简单方法是使用方法,即:
MockDataProvider provider = Mock.of(ctx.fetchFromTXT(
"ID AUTHOR_ID TITLE \n" +
"-- --------- -----------\n" +
" 1 1 1984 \n" +
" 2 1 Animal Farm\n"
));
此提供程序只是忽略所有输入(查询,绑定变量等),并始终返回相同的简单结果集。您现在可以将此提供程序插入MockConnection
并使用它,就像任何普通的JDBC连接一样:
try (Connection c = new MockConnection(provider);
PreparedStatement s = c.prepareStatement("SELECT foo");
ResultSet rs = s.executeQuery()) {
while (rs.next()) {
System.out.println("ID : " + rs.getInt(1));
System.out.println("First name: " + rs.getString(2));
System.out.println("Last name : " + rs.getString(3));
}
}
输出是(完全忽略SELECT foo
语句):
ID : 1
First name: 1
Last name : 1984
ID : 2
First name: 1
Last name : Animal Farm
这个客户端代码甚至不使用jOOQ(虽然它可以)!这意味着您可以在任何基于JDBC的应用程序(包括基于Hibernate的应用程序)上使用jOOQ作为JDBC模拟框架。
当然,您并不总是希望返回完全相同的结果。这就是为什么MockDataProvider
为您提供包含所有查询信息的参数:
try (Connection c = new MockConnection(ctx -> {
if (ctx.sql().toLowerCase().startsWith("select")) {
// ...
}
})) {
// Do stuff with this connection
}
您几乎可以使用单个lambda表达式实现整个JDBC驱动程序。在这里阅读更多。很酷,对吧?
旁注:不要误会我的意思:我认为你不应该因为可以而mock整个数据库层。我的想法可以在这个推特风暴中找到:
说到合成JDBC连接......
jOOQ 3.9引入了一个SQL解析器,其主要用例是为代码生成器解析和反向工程DDL脚本。
另一个尚未被讨论的功能(因为还有点实验)是解析连接,可通过DSLContext.parsingConnection()
。同样,这是一个JDBC Connection
实现,它包装物理JDBC连接,但在再次生成它们之前通过jOOQ解析器运行所有SQL查询。
重点是什么?
我们假设我们正在使用SQL Server,它支持以下SQL标准语法:
SELECT * FROM(VALUES(1),(2),(3))t(a)
结果是:
a
---
1
2
3
现在,我们假设我们计划将应用程序迁移到Oracle。我们有以下不能在Oracle上运行的JDBC代码,因为Oracle不支持上述语法:
try (Connection c = DriverManager.getConnection("...");
Statement s = c.createStatement();
ResultSet rs = s.executeQuery(
"SELECT * FROM (VALUES (1), (2), (3)) t(a)")) {
while (rs.next())
System.out.println(rs.getInt(1));
}
现在,我们有三个选项(提示#1很糟糕;#2和#3很酷):
try (DSLContext ctx = DSL.using("...");
Connection c = ctx.parsingConnection(); // Magic here
Statement s = c.createStatement();
ResultSet rs = s.executeQuery(
"SELECT * FROM (VALUES (1), (2), (3)) t(a)")) {
while (rs.next())
System.out.println(rs.getInt(1));
}
我们没有触及任何基于JDBC的客户端逻辑。我们只介绍了一个代理JDBC连接,它在重新生成包装的物理JDBC连接上的语句之前通过jOOQ解析器运行每个语句。
在Oracle上真正执行的是这里的仿真:
select t.a from (
(select null a from dual where 1 = 0) union all
(select * from (
(select 1 from dual) union all
(select 2 from dual) union all
(select 3 from dual)
) t)
) t
看起来很棒,是吗?此处描述了此仿真的基本原理。
jOOQ可以使用其API表示的每个SQL功能以及它可以在数据库之间进行模拟的功能都将受到支持!这包括更多琐碎的事情,比如解析这个查询:
SELECT substring('abcdefg', 2, 4)
...而在Oracle上运行这个:
select substr('abcdefg', 2, 4) from dual
你们都在想:
jOOQ API中有许多这样的好东西可以帮助您提高效率。
原文标题《Top 5 Hidden jOOQ Features》
作者:Lukas Eder
译者:February
不代表云加社区观点,更多详情请查看原文链接
本文系外文翻译,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文系外文翻译,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。