H2数据库是一个非常受欢迎的内存数据库产品,大多被Java开发人员用于测试。如果你查看DB-Engines的排名,它排在第50位,这相当令人印象深刻,因为这个排名超过了以下产品:
- CockroachDB
- Ignite
- Single Store(以前是MemSQL)
- Interbase(它被分叉为Firebird)。
- Ingres(它的前身是超强的PostgreSQL)
- 谷歌BigTable
jOOQ也支持以上所有的内容。
SQL标准化
以下是一个试图在RDBMS之间编写标准SQL的简单例子,它使用了本地H2语法:
try (
Connection c = DriverManager.getConnection(
"jdbc:h2:mem:test", "sa", "");
Statement s = c.createStatement();
ResultSet rs = s.executeQuery("""
SELECT v
FROM VALUES (1), (2), (3) AS t (v)
ORDER BY v
FETCH FIRST 2 ROWS ONLY
"""
)
) {
while (rs.next())
System.out.println(rs.getInt(1));
}
该查询产生了,正如预期的那样。
1
2
如果我们把这个查询粘贴到一个SQL编辑器中,并针对SQL Server运行,那么就会出现2个语法错误。
SQL错误[156] [S0001]。关键字'VALUES'附近的语法不正确。
在SQL Server中,VALUES 表的构造函数需要加括号,如下所示:
SELECT v
FROM (VALUES (1), (2), (3)) AS t (v)
一旦解决了这个问题,我们就会遇到下一个错误。
SQL错误[153] [S0002]。在FETCH语句中选项FIRST的使用无效。
由于只有T-SQL之神才能想象的原因,OFFSET 子句在SQL Server的标准SQLOFFSET .. FETCH 子句的概念中是强制性的,所以我们必须写这个,而不是:
SELECT v
FROM (VALUES (1), (2), (3)) AS t (v)
ORDER BY v
OFFSET 0 ROWS
FETCH FIRST 2 ROWS ONLY
注意,如果你使用jOOQ,你(几乎)不用担心这些细节,因为jOOQ会在需要时为你生成正确的SQL。编写标准的SQL已经很困难了。编写与SQL方言无关的SQL是非常困难的!
幸运的是,这仍然是标准SQL,所以它在H2上也仍然有效。
H2的兼容模式
然而,有可能你的应用程序首先需要在SQL Server上运行,而你想过要在H2上测试你的应用程序。这就是H2的兼容模式试图帮助的地方。你的基于T-SQL的应用程序可能会运行这样的语句,而不是之前的标准SQL语句:
SELECT TOP 2 v
FROM (VALUES (1), (2), (3)) AS t (v)
ORDER BY v;
它是完全等价的,并且仍然产生这样的输出:
1
2
有趣的是,H2也支持TOP 2 子句,甚至不需要在JDBC URL中指定兼容模式,比如这样。
jdbc:h2:mem:test;MODE=MSSQLServer
但如果你想在H2上运行这样的T-SQL语句,最好启用兼容模式,这样可以处理一些边缘情况。历史表明,这些东西在H2的补丁版本之间会发生不兼容的变化,所以最好小心一点。
使用H2与jOOQ
一旦你使用jOOQ,情况就完全不同了。jOOQ不知道H2的兼容模式。这是一个需要知道的重要事情--当你在H2上运行jOOQ查询时,jOOQ会假设H2的本地方言,并直接为H2生成SQL。
通常,用户会莫名其妙地认为他们应该继续使用兼容模式,就像上面的JDBC用例一样。例如,在这个Stack Overflow问题中,一个用户遇到了一个问题,即jOOQ在H2上生成H2的SQL,在MODE=MSSQLServer 。jOOQ仍然为H2生成LIMIT ,而不是FETCH (见这里的未决功能请求),但两者在SQL Server或H2上都不像这样使用MODE=MSSQLServer
如果你想继续使用H2作为你的测试数据库产品来模拟SQL Server,实际上只有一种有效的配置:
- 使用jOOQ的
SQLDialect.H2 - 使用没有任何兼容模式的H2
因为jOOQ为你实现了兼容模式。你可以尝试在H2上使用SQLDialect.SQLSERVER ,但是jOOQ会假设一个实际的SQL Server数据库,它能理解所有的T-SQL,而你会遇到H2的无尽限制MODE=MSSQLServer
换句话说:
H2的兼容模式只适用于普通SQL的使用,不适合与jOOQ这样的SQL生成器一起使用。
一个更好的选择:Testcontainers
在这一点上,我想指出,也许,将H2作为一个测试数据库产品是过时的。虽然它在10年前增加了很多价值,但由于有了更新的替代品,这种方法已经不再可行。
如果你的应用程序只在SQL Server上运行,那么为什么要费尽心思维护供应商的中立性,而只是为了能够对你的应用程序进行集成测试?
如今,testcontainers是一种流行的选择,可以在Docker中快速启动一个实际的 SQL Server实例,以便对你的应用程序进行集成测试(甚至开发)。其好处真的很明显:
- 它简化了你的代码
- 你可以使用各种供应商的特定功能(如T-SQL强大的表估值函数等)。
- 你可以不再担心这些痛苦的兼容性问题
我们甚至建议使用测试容器来生成jOOQ代码,这样你就可以逆向工程你的实际 模式(包括存储过程、数据类型,以及其他什么)。
例外:你的应用程序是与RDBMS无关的
上述情况的一个例外是,你的应用是一个支持多种RDBMS的产品,在这种情况下,你更喜欢jOOQ对你的SQL方言进行抽象。
因为在这种情况下,你已经不得不担心痛苦的兼容性问题,所以添加H2对你的伤害并不大,而且在这种情况下,你仍然可以从H2比基于测试容器的数据库产品旋转速度快一些中获益。
例如,jOOQ自己的集成测试首先针对H2运行,作为一个 "烟雾测试"。如果H2的集成测试失败,我们可以得到早期的反馈,这些反馈可能在所有其他方言中也会失败,所以我们得到的反馈更快。
但即便如此,在使用jOOQ时,H2的兼容模式也是不必要的,所以只在其原生形式下使用H2。而且,大多数应用程序不是这样的,它们被绑在一个单一的RDBMS上,所以在堆栈中加入H2的成本要高得多。再考虑一下测试容器吧。