之前为了实现基于Hibernate+Oracle的存储过程调用,发现了一个又一个坑,然后一个一个的尝试解决。
需求:使用Hibernate调用Oracle的存储过程,需要支持的有动态表名
、存储过程变量定义
、数组传递
1. oracle变量定义
首先,需要解决的是如何在oracle存储过程中定义变量,否则连存储过程都没法写
变量赋值
- 在AS…BEGIN中间插入要定义的变量
foo
- 使用
select into foo
语法
-- http://stackoverflow.com/questions/16260068/assign-a-select-to-a-variable-in-a-stored-procedure
create or replace
FUNCTION PCD_COMBAT (identifier_perso NUMBER, identifier_advers NUMBER)
RETURN NUMBER
AS
ATT_PERSO NUMBER;
OFF_PERSO NUMBER;
DEF_ADVERS NUMBER;
BEGIN
SELECT OFFENSE_PERSO
INTO OFF_PERSO
FROM PERSONNAGE
WHERE ID_PERSO = identifier_perso;
SELECT DEFENSE_ADVERSAIRE
INTO DEF_ADVERS
FROM PERSONNAGE
WHERE ID_ADVERSAIRE = identifier_advers;
ATT_PERSO := OFF_PERSO - DEF_ADVERS;
IF ATT_PERSO <1 THEN
ATT_PERSO :=1;
END IF
RETURN ATT_PERSO;
END PCD_COMBAT;
复杂变量+package(未使用到)
Another reason that standalone procedures and functions, like the ones in “Creating and Using Standalone Procedures and Functions” , are limited to large-scale development is that they can only send and receive scalar parameters ( NUMBER , VARCHAR2, and DATE ), but cannot use a composite structure, RECORD , unless it is defined in a package specification.
https://docs.oracle.com/cd/B28359_01/appdev.111/b28843/tdddg_procedures.htm#CIHCHIDB
2. Hibernate存储过程调用
一个简单的存储过程写完了,得先测试能否使用hibernate对存储过程进行调用,所以接下来了解Hibernate是如何调用存储过程的。稍微查一下,发现调用还是相当简单的。但是,是有一些限制和条件的。
使用hibernate.query限制
For Oracle the following rules apply:
- A function must return a result set. The first parameter of a procedure must be an
OUT
that returns a result set. This is done by using aSYS_REFCURSOR
type in Oracle 9 or 10. In Oracle you need to define aREF CURSOR
type. See Oracle literature for further information.https://docs.jboss.org/hibernate/orm/4.2/manual/en-US/html/ch18.html#sp_query
不使用query
如果不能满足Hibernate对query对象的使用条件,就只能自己使用session进行相对原生一些的调用。虽然官方文档给出了session.connection()
,但是,明显这个文档也是一段时间没有更新了。在4.X版本中,这个接口已经没有了。因此,进一步找了一些替代方案
seesion.connection()
http://stackoverflow.com/questions/15217984/alternative-of-deprecated-hibernate-getsession-connection
connection异常
用了connection()跟着又有异常发生,看上去是安全策略和对象包装造成的,解决方案如下:
http://www.coderanch.com/t/415597/JDBC/databases/Apache-Commons-DBCP-connection-object
https://community.oracle.com/thread/2564233?start=0&tstart=0
accessToUnderlyingConnectionAllowed="true"
conn = ((DelegatingConnection) conn).getInnermostDelegate();
3. 变量类型
一个简单的存储过程的调用搞定了,就是后面的重头戏,表名做参数、传递数组参数。。。这是个大坑!大坑!!大坑!!!
表名做参数
表名做参数,有点像是Javascript的eval(),让一个字符串可执行
除了下面的例子显示的,还有使用DBMS.parse()
-- https://community.oracle.com/thread/1122692?tstart=0
CREATE OR REPLACE PROCEDURE "P_1"( TAB1 VARCHAR2)
AS
field1 CHAR(5);
field2 CHAR(5);
c1 sys_refcursor;
begin
l_str :='select a,b,c from '||tab1||' INNER JOIN tab2 ON a = .......';
open c1 from l_str;
/*OPEN C1;
CURSOR C1
IS
SELECT a, b , c
FROM TAB1
INNER JOIN
tab2
ON a = .......
OPEN C1;*/
LOOP
FETCH C1
INTO .....
EXIT WHEN C1%NOTFOUND;
.....
END;
END LOOP;
CLOSE C1;
EXCEPTION
WHEN OTHERS
.....
END;
数组做参数
这绝对是大坑,神坑!
http://stackoverflow.com/questions/23573303/send-retrieve-array-list-to-oracle-stored-procedure
http://www.codeproject.com/Articles/164705/Oracle-PL-SQL-collections
http://viralpatel.net/blogs/java-passing-array-to-oracle-stored-procedure/
-- http://xiaogui9317170.iteye.com/blog/286401
create table study_array_nick_tab
(
name varchar2(200)
);
create or replace type study_array_nick_list is VARRAY(1000) of varchar2(200);
create or replace procedure study_array_nick(in_array in study_array_nick_list)is
v_i number;
begin
for v_i in1.. in_array.count loop
insert into study_array_nick_tab(name) values(in_array(v_i));
end loop;
commit;
exception when others then
rollback;
raise_application_error('20999','测试错误');
end study_array_nick;
-- http://forum.spring.io/forum/spring-projects/data/24224-how-can-i-pass-arraylist-to-stored-procedure/page2
CREATE OR REPLACE TYPE TEST_EMP_OBJ AS OBJECT (empno number, empname varchar2(30));
create or replace type TEST_EMP_OBJ_ARRAY as table of TEST_EMP_OBJ;
create table test_emp (empno number, empname varchar2(30));
CREATE OR REPLACE PROCEDURE TEST_EMP_OBJ_ARRAY_PROC ( p_obj_array in TEST_EMP_OBJ_ARRAY ) AS
begin
for i in1..p_obj_array.count loop
insert into test_emp (empno, empname)
values(p_obj_array(i).empno, p_obj_array(i).empname);
end loop;
end;
ORACLE+JAVA
照着上面的那些例子,写了存储过程,然后通过Hibernate调用,实际上一个都没有成功。不传递数组就没问题,传递数组就会报异常,大意是参数无法转化为Oracle的表示。然后找到下面这个完整的例子,照着一模一样敲了一遍,除了那个IDS
的TYPE
我定义,而是直接用了varchar(32)
,结果就是不行。。。
https://oracle-base.com/articles/misc/using-ref-cursors-to-return-recordsets#11g-updates
SQL> CREATE OR REPLACE TYPE IDS AS OBJECT ( id1 NUMBER, id2 NUMBER, id3 NUMBER );
2/
Type created
SQL> CREATE OR REPLACE TYPE IDS_TABLE AS TABLE OF IDS;--这里我用的是OF varvchar(32)
2/
Type created
SQL> CREATE OR REPLACE PROCEDURE getInfo(p_ids IN IDS_TABLE) IS
2BEGIN
3 FOR i IN 1.. p_ids.COUNT LOOP
4 dbms_output.put_line(p_ids(i).id1
5||','|| p_ids(i).id2
6||','|| p_ids(i).id3);
7END LOOP;
8END getInfo;
9/
Procedure created
SQL> CREATE OR REPLACE
2 AND COMPILE JAVA SOURCE NAMED "ArrayDemo"
3as
4import java.io.*;
5import java.sql.*;
6import oracle.sql.*;
7import oracle.jdbc.driver.*;
8
9publicclassArrayDemo{
10
11publicstaticvoid passArray()throwsSQLException{
12
13Connection conn =
14newOracleDriver().defaultConnection();
15
16
17//这里没有定义IDS
18StructDescriptor itemDescriptor =StructDescriptor.createDescriptor("IDS",conn);
19
20Object[] itemAtributes =newObject[]{newInteger(1),
21newInteger(2),
22newInteger(3)};
23 STRUCT itemObject1 =new STRUCT(itemDescriptor,conn,itemAtributes);
24
25 itemAtributes =newObject[]{newInteger(4),
26newInteger(5),
27newInteger(6)};
28 STRUCT itemObject2 =new STRUCT(itemDescriptor,conn,itemAtributes);
29
30 STRUCT[] idsArray ={itemObject1,itemObject2};
31
32ArrayDescriptor descriptor =
33ArrayDescriptor.createDescriptor("IDS_TABLE", conn );
34//在这里直接穿了String的List。明明构造函数是有这种构造的
35 ARRAY array_to_pass =
36new ARRAY( descriptor, conn, idsArray );
37//然后调用就是出现异常
38OraclePreparedStatement ps =
39(OraclePreparedStatement)conn.prepareStatement
40("begin getInfo(:x); end;");
41
42 ps.setARRAY(1, array_to_pass );
43 ps.execute();
44
45}
46}
47/
Java created
SQL> CREATE OR REPLACE
2 PROCEDURE show_java_calling_plsql
3 AS LANGUAGE JAVA
4 NAME 'ArrayDemo.passArray()';
5/
Procedure created
SQL>exec show_java_calling_plsql ;
1,2,3
4,5,6
PL/SQL procedure successfully completed
4. 结论
关于使用Hibernate+Oracle存储过程,除了最后的数组参数没有搞定,其他的都尝试成功了。关于数组参数这个问题,我还是不知道怎么解决。难道一定要定义一个只包含一个字符串的OBJECT
(就像上面的那个IDS
一样)?这个没有进一步解决了。将这整个过程记录下来,权当整理。