暂无图片
暂无图片
1
暂无图片
暂无图片
1
暂无图片

日常工作分享: PGJDBC 调用存储过程案例2则

大家好,今天和大家分享一下日常工作中的JAVA的JDBC 调用PG中的store procedure 2 则案例。

最近公司开发同学正在尝试把oracle的PLSQL,改写成PLPGSQL, 应用程序是JAVA的springboot 框架。

案例一: 自定义类型存储过程中的传参问题

我们先来看ORACLE 的中的存储过程是什么样子的

1.自定义类型的type TYPE APP_MSG_GW.“TYP_BLACK_LIST_OBJ”
2.基于自定义类型的type APP_MSG_GW.“TYP_BLACK_LIST_OBJ” 的集合 collection
3.基于自定义type APP_MSG_GW.“TYP_BLACK_LIST_OBJ” 的集合 collection 传参的存储过程: 简单的说就是根据入参的数据,对数据库表中的数据进行merge操作。

TYP_BLACK_LIST_OBJ:

Image.png

TYP_BLACK_LIST_TAB:
Image.png

PROCEDURE PROC_BLACK_LIST_BATCH_INSERT 传参类型是 自定义对象TYP_BLACK_LIST_OBJ的集合

Image.png
开发小伙伴的问题是 PG中是否存在 TABLE(PI_BLACK_LIST_BATCH) 这种写法? 显然是不存在的,但是PG中相应的workaround 关键字 unnest :

postgres=# select unnest(Array[1,2,3]) as a; a --- 1 2 3 (3 rows)
复制

于是为开发小伙伴写了一个PLPGSQL的从测试小demo 版本:

创建表:t_person_list CREATE table t_person_list ( id bigint primary key , name VARCHAR(100) ); PG中创建type CREATE TYPE typ_perosn_obj as ( id bigint, name VARCHAR(100) ); typ_black_list_obj[] 进行传参转换 create or replace procedure proc_merge_test(person_list in typ_perosn_obj[]) as $$ begin merge into t_person_list t1 using (SELECT tmp.id, tmp.name from "unnest"(person_list) tmp) t2 on t1.id = t2.id WHEN NOT MATCHED THEN INSERT (id, name) VALUES (t2.id, t2.name); end; $$ language plpgsql; 测试程序 do $$ declare data typ_perosn_obj[]; begin data[1] := (1, 'jason'); data[2] := (2, 'tom'); call proc_merge_test(data); end; $$; 返回结果: postgres=# select * from t_person_list; id | name ----+------- 1 | jason 2 | tom (2 rows)
复制

自己和开发同学在PSQL的client下测试就无任何问题,本以为事情至此结束。。。

但是开发小伙伴第二天找到我说,JAVA调用出现了异常。。。无论代码怎默写都不管用???

开发同学大致试了3,4调用方式吧,无论如何都不行。。。

Image.png

Image.png

同样我也写了个小测试程序试了一下,也是不行的(各种调试了一整晚)

@RequestMapping(value="/plpgsql", method=RequestMethod.GET) public String callProc() throws SQLException { Connection conn = jdbcTemplate.getDataSource().getConnection(); Person[] personArray = new Person[1]; personArray[0] = new Person(100,"jason100"); final Array sqlArray = conn.createArrayOf("typ_perosn_obj", personArray); PreparedStatement stmt = conn.prepareStatement("call proc_merge_test( ? )"); // CallableStatement proc = con.prepareCall("{call commitproc( ? )}"); stmt.setArray(1,sqlArray); stmt.execute(); conn.commit(); return "SUCCESS!!"; }
复制

报错如下: 感觉是PG的自定义类型和JAVA中没有mapping 上

2024-03-03 19:46:57,451 ERROR (DirectJDKLog.java:175)- Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.postgresql.util.PSQLException: ERROR: malformed record literal: "Person(id=100, name=jason100)" Detail: Missing left parenthesis. Where: unnamed portal parameter $1 = '...'] with root cause org.postgresql.util.PSQLException: ERROR: malformed record literal: "Person(id=100, name=jason100)" Detail: Missing left parenthesis. Where: unnamed portal parameter $1 = '...'
复制

下一步去问问GOOGLE 吧(国内PLPGSQL 这块 基本上是空白…)

果然发现了一个2008年的帖子: 貌似是PGJDBC驱动确实不支持自定义type类型的传参

Image.png

由于这个帖子时间太久远了,我决定在github 给 PGJDBC 开一个issue: 来确实一下目前版本是否支持?或者未来是否有支持的打算?

https://github.com/pgjdbc/pgjdbc/issues/3143

Image.png

大致过了一个星期,收到了官方大佬 Davecramer 的回复: 确实当前版本还是不支持自定义type的传参,未来在google summer 有希望实现这个功能。

Image.png

既然目前驱动不支持,我们就需要改写一下PG中的实现方式,我们可以考虑换成text[] 数组类型(PGJDBC对于这种基本类型的参数是支持的),每条记录是json串的格式:

测试demo 如下:

创建表: t_person_list CREATE table t_person_list ( id bigint primary key , name VARCHAR(100) ); create or replace procedure proc_merge_test_new(person_list in text[]) as $$ begin merge into t_person_list t1 using ( select (per::json->>'id')::bigint as id, (per::json->>'name')::text as name from "unnest" (person_list) as per ) t2 on t1.id = t2.id WHEN NOT MATCHED THEN INSERT (id, name) VALUES (t2.id, t2.name); end; $$ language plpgsql; 测试程序: do $$ declare data text[]; begin data[1] := '{"id":100,"name":"jason1"}'; data[2] := '{"id":200,"name":"jason2"}'; call proc_merge_test_new(data); end; $$;
复制

开发小伙伴同样在JAVA端测试成功了!

Image.png

案例二: PLPGSQL 函数返回游标问题

开发小伙伴遇到的另一个问题也是JAVA调用存储过程返回out 类型的参数 refcursor

JAVA错误: org.postgresql.util.PSQLException: ERROR: cursor “<unnamed portal 1>” does not exist

Image.png

Google上给出的答案是 手动设置一下 conn.setAutoCommit(false);

我们来测试一下

PLPGSQL

CREATE OR REPLACE FUNCTION TEST_QUERY_CURSOR () RETURNS refcursor AS $$ DECLARE ref refcursor; BEGIN OPEN ref FOR SELECT 1, 'jason'; RETURN ref; END; $$ LANGUAGE 'plpgsql';
复制

JAVA code:

@RequestMapping(value="/queryCursor", method=RequestMethod.GET) public String queryCursor() throws SQLException { Connection conn = jdbcTemplate.getDataSource().getConnection(); conn.setAutoCommit(false); CallableStatement proc = conn.prepareCall("{? = call TEST_QUERY_CURSOR()}"); proc.registerOutParameter(1, Types.REF_CURSOR); proc.execute(); ResultSet results = (ResultSet) proc.getObject(1); while (results.next()) { System.out.println(results.getInt(1) + "-->" +results.getString(2)); } results.close(); proc.close(); return "SUCCESS!!"; }
复制

果然在代码中设置了 conn.setAutoCommit(false); 之后返回结果正常了

1-->jason
复制

但是有的公司明确规定代码里不可以自己手动设置改变事务的默认提交方式, 我们其实针对这个case相应的workaround有很多:
1.JAVA中直接写SQL QUERY
2.改写function 返回table的方式

我们测试一下 改写function 返回table的方式

PLPGSQL:

CREATE OR REPLACE FUNCTION PROC_QUERY() RETURNS table(id bigint,name varchar(100)) AS $$ begin return query select t.id, t.name from t_person_list t; end; $$ LANGUAGE plpgsql;
复制

JAVA code:

@RequestMapping(value="/queryCursor2", method=RequestMethod.GET) public String queryCurso2r() throws SQLException { Connection conn = jdbcTemplate.getDataSource().getConnection(); //conn.setAutoCommit(false); -- function return table 方式无需手动设置事务提交方式 PreparedStatement pstmt = conn.prepareStatement("select * from PROC_QUERY()"); ResultSet rs = pstmt.executeQuery(); while (rs.next()) { System.out.println(rs.getBigDecimal(1) + rs.getString(2)); } rs.close(); pstmt.close(); return "SUCCESS!!"; }
复制

测试结果:

100jason1 200jason2
复制

最后我们总结一下:

1)目前PG的JDBC驱动不支持自定义类型的传参,我们可以考虑用 json 字符串的形式带替代
2)PLLGSQL中的函数返回refcursor 需要手动设置事务自动提交为false => “conn.setAutoCommit(false)”, 可以考虑用JAVA代码直接 SQL query 或者 PLLGSQL函数返回table => RETURNS table(col1 type1, col2 type2) 来替代

Have a fun 🙂 !

「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论

11 0
暂无图片
1年前
评论
暂无图片 0
👍
1年前
暂无图片 点赞
评论