在过去的几年里,我们投入了大量的精力来改善我们在jOOQ中的程序语言能力。最初是一个简单的内部API,支持像这样的DDL条款的模拟:
-- Some dialect that supports this
create table if not exists t (i varchar(10));
-- Db2
begin
declare continue handler for sqlstate '42710' begin end;
execute immediate 'create table T (I varchar(10))';
end
-- Oracle
begin
execute immediate 'create table T (I varchar2(10))';
exception
when others then
if sqlerrm like 'ORA-00955%' then null;
else raise;
end if;
end;
-- SQL Server
begin try
create table T (I varchar(10))
end try
begin catch
if error_number() != 2714 throw;
end catch
...发展成为一个成熟的API,用于在数据库服务器中执行各种过程性逻辑。
匿名块
上面的例子显示了大多数RDBMS称之为 "匿名块",类似于Java的匿名类,即没有名字的过程性逻辑元素。
根据数据库的情况,这些块会被即时解释,或者像普通SQL语句一样被编译和缓存。由于各种原因,它们可能非常有用:
- 创建原子式的临时代码单元,在一次服务器往返中执行,类似于上述带有集成异常处理的DDL脚本
- 创建动态程序性代码。这对许多人来说可能是深奥的,但对其他人来说却是正确的做法。所有的jOOQ都是关于动态SQL的,那么为什么不包括动态PL/SQL、T-SQL、pgplsql等?
- 为了绕过康威法则带来的限制,当你没有办法得到必要的GRANT或其他官僚主义的令牌,在生产中酌情部署你的程序。我的意思是,这在很多公司仍然是一件事。
- Conway's Law的一个较小的例子是,当你是一个产品供应商时,你不知道你是否可以在客户的生产系统上创建程序。就不要这样做。如果你不能,就以匿名块的形式运行你的过程性逻辑,如果你能,就以过程的形式运行。同样的jOOQ代码。
- 如果你的过程性代码变化非常频繁(甚至是动态的),存储它可能会引起棘手的问题。如果你曾经在Oracle工作过,遇到过可怕的无闩事件,你就知道我的意思。
我绝不是在提倡你应该使用匿名块而不是一般的存储过程。如果你可以的话,把你的代码存储在数据库中以获得更好的性能和重复使用。但有时你不能这样做,有时你也不应该这样做。
所以,jOOQ一如既往地支持--各种过程性逻辑元素的混合,包括:
- 带有变量声明的区块
IF
声明- 循环,包括
LOOP
,WHILE
,REPEAT
,FOR
循环 EXIT
(或 )和 (或 )用于循环控制流LEAVE
CONTINUE
ITERATE
RETURN
从程序或函数返回GOTO
(ghasp!)SIGNAL
或RAISE
- 标签
CALL
语句来调用其他存储程序EXECUTE
语句(用于从程序逻辑中运行动态SQL,这是哪一级的概念?)
而且我们一直在增加更多的支持。Java代码可能看起来是这样的:
Variable<Integer> i = var(name("i"), INTEGER);
ctx.begin(
for_(i).in(1, 10).loop(
insertInto(T).columns(T.COL).values(i)
)
).execute();
假设你因为某些原因不能运行批量插入语句,这可能是一个办法。它可以翻译成各种方言,如下所示。
Db2 和MySQL(它不支持匿名块,但支持语句批处理)
begin
declare I bigint;
set I = 1;
while I <= 10 do
insert into T (COL) values (I);
set I = (I + 1);
end while;
end;
PostgreSQL
do $$
begin
for I in 1 .. 10 loop
insert into T (COL) values (I);
end loop;
end;
$$
Oracle
begin
for I in 1 .. 10 loop
insert into T (COL) values (I);
end loop;
end;
SQL服务器
begin
declare @I bigint = 1;
while @I <= 10 begin
insert into T (COL) values (I);
set @I = (@I + 1);
end;
end;
与jOOQ一样,你不必一开始就编写基于jOOQ API的代码。当你的程序性(或SQL)逻辑是动态的,这是推荐的方法,但jOOQ也可以解析和翻译字符串形式的静态SQL。这就是SQL的babelfish。在这里玩一玩,了解更多:https://www.jooq.org/translate/
将代码存储为一个过程
如果你没有上述的使用情况,你会想把这些代码存储为一个过程(或函数):
- 为了更好地重复使用
- 为了获得更好的性能
在这种情况下,从jOOQ 3.15开始,你可以使用我们的 [CREATE PROCEDURE](https://www.jooq.org/doc/dev/manual/sql-building/ddl-statements/create-statement/create-procedure-statement/)
, [CREATE FUNCTION](https://www.jooq.org/doc/dev/manual/sql-building/ddl-statements/create-statement/create-function-statement/)
,甚至是 [CREATE TRIGGER](https://www.jooq.org/doc/dev/manual/sql-building/ddl-statements/create-statement/create-trigger-statement/)
支持。
注意:
CREATE PACKAGE
是我们的愿望清单上的重点,但可能不会再进入3.15。如果包只用于命名,它们可能会被其他方言的模式所模仿。其他的包级特征,例如包的状态可能更难翻译。
前面的匿名块可以很容易地被包裹在一个 [DSLContext.createProcedure()](https://www.jooq.org/javadoc/dev/org.jooq/org/jooq/DSLContext.html#createProcedure(org.jooq.Name))
调用
Variable<Integer> i = var("i", INTEGER);
Parameter<Integer> i1 = in("i1", INTEGER);
Parameter<Integer> i2 = in("i2", INTEGER);
ctx.createProcedure("insert_into_t")
.parameters(i1, i2)
// You may or may not wrap your block in BEGIN .. END.
// jOOQ will figure it out per dialect...
.as(for_(i).in(i1, i2).loop(
insertInto(T).columns(T.COL).values(i)
))
.execute();
这将产生以下程序:
Db2和MySQL
create procedure INSERT_INTO_T(
I1 integer,
I2 integer
)
begin
declare I bigint;
set I = I1;
while I <= I2 do
insert into T (COL) values (I);
set I = (I + 1);
end while;
end;
MariaDB
create procedure INSERT_INTO_T(
I1 int,
I2 int
)
begin
for I in I1 .. I2 do
insert into T (COL) values (I);
end for;
end;
Oracle
create procedure INSERT_INTO_T(
I1 number,
I2 number
)
as
begin
for I in I1 .. I2 loop
insert into T (COL) values (I);
end loop;
end;
PostgreSQL
create procedure INSERT_INTO_T(
I1 int,
I2 int
)
language plpgsql
as
$$
begin
for I in I1 .. I2 loop
insert into T (COL) values (I);
end loop;
end;
$$
SQL服务器
create procedure INSERT_INTO_T
@I1 int,
@I2 int
as
begin
declare @I bigint = @I1;
while @I <= @I2 begin
insert into T (COL) values (@I);
set @I = (@I + 1);
end;
end;
在这里玩一玩,了解更多:https://www.jooq.org/translate/。我们也期待着你的错误报告和/或功能请求:https://github.com/jOOQ/jOOQ/issues/new/choose。
棘手的转换
程序性语言通过ISO/IEC 9075-4标准进行标准化,一些RBDMS令人惊讶地在很大程度上同意该标准,包括:
- Db2
- HSQLDB
- MariaDB
- MySQL
其他的就不太一样了,但是所有的过程性语言都同意他们是非常简单的语言,没有任何像子类型或参数多态性这样的 "花哨 "的东西(好吧,PL/SQL有一些子类型多态性,但不是非常复杂的一种。我们暂时不支持它),lambda表达式,动态调度,代数数据类型,等等。
它们的共同点是与SQL语言紧密结合,这也是它们的闪光之处。
但是,还是有一些微妙的区别。例如,它们在你可以声明变量的地方有所不同。有些有块范围,有些没有。有的坚持标准,即LEAVE
,有的则不需要标签。
想象一下,你写了这样一段 "幻想中的 "jOOQ代码:
Name t = unquotedName("t");
Name a = unquotedName("a");
Variable<Integer> i = var(unquotedName("i"), INTEGER);
ctx.begin(
insertInto(t).columns(a).values(1),
declare(i).set(2),
loop(
insertInto(t).columns(a).values(i),
i.set(i.plus(1)),
if_(i.gt(10)).then(loop(exit()), exit())
)
)
.execute();
这只是原始循环的一个更复杂的版本,它将数值1-10插入到一个表中。除了展示loop(exit())
的嵌套的转换能力,以及用EXIT
的无限LOOP
,而不是用索引的FOR
循环外,没有别的原因。
在一些方言中,有一些事情并不总是完全像这样工作!
让我们看看Db2是如何处理这个问题的
begin
-- Variable declarations need to be "pulled up" to the beginning
-- of the block, i.e. before the INSERT statement
declare i integer;
insert into t (a) values (1);
-- While irrelevant to this example, the init value for the
-- variable declaration must remain at the original location
set i = 2;
-- We need a label to be able to leave this loop
alias_1:
loop
insert into t (a) values (i);
set i = (i + 1);
if i > 10 then
-- Same here, a label is required
alias_2:
loop
leave alias_2;
end loop;
leave alias_1;
end if;
end loop;
end
如果我们不在一个循环上使用EXIT
,那么就不会有一个标签。或者,你显然可以明确地给你的循环贴上标签,这总是被推荐的。但有时,你的原始源代码中并没有这个标签。
Oracle是如何处理这个问题的呢?
Oracle在这里有一个稍微不同的语法:
declare
i number(10);
begin
insert into t (a) values (1);
i := 2;
loop
insert into t (a) values (i);
i := (i + 1);
if i > 10 then
loop
exit;
end loop;
exit;
end if;
end loop;
end;
主要的区别是,声明也是拉起来的,但是需要一个单独的DECLARE
块来声明BEGIN .. END
以外的变量。无标签的EXIT
是原生支持的,所以这里不需要进行任何转换。
总结
无论你是要从一种方言迁移到另一种方言,还是要同时支持几种方言,还是要编写动态SQL和动态过程性逻辑,或者你只是喜欢用Java而不是本地SQL来写东西,或者你患有Conway's Law,不能轻易存储你的过程性代码,jOOQ都可以帮助你完成这些工作。
一段时间以来,jOOQ已经支持程序性语句作为最流行的方言的匿名块。从jOOQ 3.15开始,我们还将支持以一种与方言无关的方式将这种逻辑存储在数据库中,以及在我们的网站上解析/翻译程序代码,或作为一个库/CLI或JDBC代理,在传统的JDBC应用中临时取代你的SQL/程序代码。
请继续关注jOOQ发展的这一令人振奋的领域的更多信息!