jOOQ主要以其强大的类型安全、嵌入式、动态SQL功能而闻名,这些功能是通过代码生成来实现的。然而,代码生成的一个次要用例是将其用于存储过程(可能是专门用于存储过程)。
存储过程是将复杂的数据处理逻辑转移到服务器上的强大方式。由于性能的原因,这应该比大多数应用程序做得更频繁。例如,请看这篇关于节省服务器往返次数的文章。但它也可以作为一种实用的方法,向客户提供API,并向客户隐藏基于SQL的细节(如模式、表结构、事务脚本等),如果这对一个应用/团队来说是有用的。
在任何情况下,jOOQ都会通过生成所有函数、过程、包、UDT等的存根来大大帮助你。
一个程序的例子
在Oracle中,一个简单的示例程序是这样的:
CREATE OR REPLACE PROCEDURE my_proc (
i1 NUMBER,
io1 IN OUT NUMBER,
o1 OUT NUMBER,
o2 OUT NUMBER,
io2 IN OUT NUMBER,
i2 NUMBER
) IS
BEGIN
o1 := io1;
io1 := i1;
o2 := io2;
io2 := i2;
END my_proc;
它使用IN,OUT, 和IN OUT 参数。当用JDBC调用这个过程时,我们必须写一些类似的东西:
try (CallableStatement s = c.prepareCall(
"{ call my_proc(?, ?, ?, ?, ?, ?) }"
)) {
// Set all input values
s.setInt(1, 1); // i1
s.setInt(2, 2); // io1
s.setInt(5, 5); // io2
s.setInt(6, 6); // i2
// Register all output values with their types
s.registerOutParameter(2, Types.INTEGER); // io1
s.registerOutParameter(3, Types.INTEGER); // o1
s.registerOutParameter(4, Types.INTEGER); // o2
s.registerOutParameter(5, Types.INTEGER); // io2
s.executeUpdate();
System.out.println("io1 = " + s.getInt(2));
System.out.println("o1 = " + s.getInt(3));
System.out.println("o2 = " + s.getInt(4));
System.out.println("io2 = " + s.getInt(5));
}
这种方法存在着各种问题:
- 通常的参数索引是容易出错的。如果你再增加一个参数,索引就会转移,这就很难管理。你可以使用命名的参数,但这样你还是会有错别字,而且不是所有的JDBC驱动都支持这个。不过,它们都支持索引参数。
- 在API中,
IN,IN OUT, 和OUT参数之间没有明显的区别。你必须知道 哪个参数有哪个模式。JDBC的API在这里没有帮助你。 - 你还必须知道哪个参数是哪种类型的,并正确地掌握这个问题。
还有许多其他注意事项和细节,但这些是最重要的。
使用jOOQ生成的代码
jOOQ的代码生成器只是为这个程序生成了一个存根。或者说,2个存根。一个带参数的调用模型类,以及一个允许在单个方法调用中调用该过程的方便方法。这就是它的模样:
// Generated code
public class MyProc extends AbstractRoutine<java.lang.Void> {
// [...]
private static final long serialVersionUID = 1L;
public void setI1(Number value) {
setNumber(I1, value);
}
public void setIo1(Number value) {
setNumber(IO1, value);
}
public void setIo2(Number value) {
setNumber(IO2, value);
}
public void setI2(Number value) {
setNumber(I2, value);
}
public BigDecimal getIo1() {
return get(IO1);
}
public BigDecimal getO1() {
return get(O1);
}
public BigDecimal getO2() {
return get(O2);
}
public BigDecimal getIo2() {
return get(IO2);
}
}
Oracle生成的代码对输入值使用Number ,对输出值使用BigDecimal ,以绑定到NUMBER 类型。其他的RDBMS支持INTEGER 类型,以防你的代码更多的使用这种类型。你显然可以使用强制类型,就像使用表一样,重写jOOQ代码生成器中的数据类型定义。
所以,现在是调用程序的一种方式:
MyProc call = new MyProc();
call.setI1(1);
call.setIo1(2);
call.setIo2(5);
call.setI2(6);
// Use the usual jOOQ configuration, e.g. the one configured by
// Spring Boot, etc.
call.execute(configuration);
System.out.println("io1 = " + call.getIo1());
System.out.println("o1 = " + call.getO1());
System.out.println("o2 = " + call.getO2());
System.out.println("io2 = " + call.getIo2());
这已经很简单了,而且允许动态调用过程。现在,在大多数情况下,jOOQ也会生成一个方便方法,允许在1行中调用这个过程。生成的方便方法看起来像这样:
public class Routines {
// [...]
public static MyProc myProc(
Configuration configuration
, Number i1
, Number io1
, Number io2
, Number i2
) {
MyProc p = new MyProc();
p.setI1(i1);
p.setIo1(io1);
p.setIo2(io2);
p.setI2(i2);
p.execute(configuration);
return p;
}
}
所以,它为你做了输入参数的拼接,所以你可以像这样调用它:
MyProc result = Routines.myProc(configuration, 1, 2, 5, 6);
System.out.println("io1 = " + result.getIo1());
System.out.println("o1 = " + result.getO1());
System.out.println("o2 = " + result.getO2());
System.out.println("io2 = " + result.getIo2());
这两种调用过程的方法是等价的,不过,第一种方法也支持默认参数,以备你在过程定义中使用。
其他特点
前面的例子展示了这个jOOQ功能和存储过程的最常见的用法。还有更多,我将在后续的博文中讨论,很快,包括:
- 嵌入在jOOQ SQL语句中的标量函数
- 嵌入在jOOQ SQL语句中使用的表值函数(包括
PIPELINED函数) - 从存储过程返回的游标(包括声明为
REF CURSOR和未声明的)。 - Oracle PL/SQL包
- Oracle PL/SQL UDTs和它们的成员程序
- Oracle PL/SQL
TABLE,RECORD和关联数组类型 - 微软T-SQL的表值参数
- 微软T-SQL