這篇要來紀錄如何使用 Java EE Spring JDBC template 來執行
PostgreSQL 的 Function。
首先我們先建立一個測試用的 Table:
兩個 PostgreSQL Function,如以下 SQL 語法:
CREATE TABLE IF NOT EXISTS public.testTable
(
id integer NOT NULL,
title text NOT NULL,
PRIMARY KEY(id)
)
並且塞入一些資料:
INSERT INTO public.testTable(id, title) VALUES(1, '111');
INSERT INTO public.testTable(id, title) VALUES(2, '222');
INSERT INTO public.testTable(id, title) VALUES(3, '333');
然後建立三個 Function,以下三個 Function 的作用基本一樣,都是接受傳入的
param_id 變數,然後進行
SELECT * FROM testTable WHERE id = param_id
的查詢,不同的是傳回的 type 不同,呼叫 Functoin 的方式也有所不同,
可以注意到的是,有兩個 Function 的名稱一樣 (都取名為
queryUsingReturnedRefcursor),不過 Function 簽名不同
(傳入參數不同),這是為了演示 PostgreSQL 能夠支持同名 Function 故意取名的,類似
Java 的 Function Overload:
-
queryUsingReturnedSetof(param_id integer):
CREATE OR REPLACE FUNCTION queryUsingReturnedSetof(param_id integer) RETURNS SETOF testTable AS $$
SELECT * FROM testTable WHERE id = param_id;
$$ LANGUAGE sql;
說明:
此 Function 傳回值 type 為 SETOF,相當於傳回 Table
的資料行集合,呼叫 Function 的方式為如下 SQL 語法:
SELECT * FROM
queryUsingReturnedSetof(2);
-
queryUsingReturnedRefcursor(param_id integer):
CREATE OR REPLACE FUNCTION queryUsingReturnedRefcursor(param_id integer) RETURNS refcursor AS $$
DECLARE ref refcursor;
BEGIN
OPEN ref FOR SELECT * FROM testTable WHERE id = param_id;
RETURN ref;
END;
$$ LANGUAGE plpgsql;
說明:
此 Function 傳回值 type 為 refcursor,為一個 cursor ,不是 Table
的資料行集合,直接呼叫的話只會得到一個 cursor ,必須要配合 FETCH
指令才能遍歷每一行的資料,例如以下語法:
SELECT
queryUsingReturnedRefcursor(2); FETCH ALL IN "<unnamed portal 6>";
其中,<unnamed
portal 6> 是被回傳的 cursor 名稱,不過在實際上每次執行 Function 後回傳的
cursor name 是會變化的,每次都不一樣,
而執行完 Function 後, cursor
就消失了,導致執行 FETCH 時會有
ERROR: cursor "<unnamed portal
xxx>" does not exist
的錯誤,所以上面那樣的語法沒辦法正確地得到查詢結果資料行,
要解決上面的問題,可以看下一個
Functoin 的說明,
其想法是把想要的 cursor name 傳進 Function
以指定回傳的 cursor name。
不過如果是用 Java
去呼叫的話,是可以成功執行並得到資料行的,
之後可以看到我們可以用 Java
去指定 cursor name。
-
queryUsingReturnedRefcursor(ref refcursor, param_id integer):
CREATE OR REPLACE FUNCTION queryUsingReturnedRefcursor(ref refcursor, param_id integer) RETURNS refcursor AS $$
BEGIN
OPEN ref FOR SELECT * FROM testTable WHERE id = param_id;
RETURN ref;
END;
$$ LANGUAGE plpgsql;
說明:
此 Function 傳回值 type 為 refcursor,跟上一個 Fucntion
一樣,只差在多傳進一個指定的 cursor name,這樣我們就可以用已知的 cursor name
來取得資料行,呼叫方式如下:
SELECT
queryUsingReturnedRefcursor('xxx_cur', 2); FETCH ALL IN "xxx_cur";
其中
xxx_cur 可以隨意更換成想要的 cursor name。
接下來是 Java EE Spring 的部份了。
下圖為測試專案的檔案結構,紅框處的是主要專案檔案:
因為重要的地方主要是 DAO 使用 JdbcTemplate 的部份,
為了簡單起見,所以這裡只展示 DAO 的部份,也就是 com.dao.MyDAO.java,
其他 Java EE Spring、Data Source 、使用到的 lib (使用 Maven POM 檔設定)
等的設定可以參考文章最後的源碼下載分享
MyDAO.java :
package com.dao;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.SqlParameter;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.simple.SimpleJdbcCall;
import org.springframework.stereotype.Repository;
@Repository
public class MyDAO {
@Autowired
@Qualifier("postgresql_JdbcTemplate")
private JdbcTemplate postgresql_JdbcTemplate;
public List<Map> queryUsingReturnedSetof(int id) {
List<Map> dataList = postgresql_JdbcTemplate.query("SELECT * FROM queryUsingReturnedSetof(?);", new RowMapper<Map>() {
@Override
public Map mapRow(ResultSet rs, int rowNumber) throws SQLException {
Map<String, Object> map = new HashMap<>();
map.put("id", rs.getInt("id"));
map.put("title", rs.getString("title"));
return map;
}
}, id);
//for test
for (Map data : dataList) {
System.out.println(data.get("id") + ", " + data.get("title"));
}
return dataList;
}
public List<Map> queryUsingReturnedRefcursor(int id) {
String customCursorName = "xxx_cur";
SimpleJdbcCall simpleJdbcCall = new SimpleJdbcCall(postgresql_JdbcTemplate)
.withProcedureName("queryUsingReturnedRefcursor")
.declareParameters(new SqlParameter("id", Types.INTEGER))
.withoutProcedureColumnMetaDataAccess()
.returningResultSet(customCursorName, new RowMapper<Map>() {
@Override
public Map mapRow(ResultSet rs, int rowNumber) throws SQLException {
Map<String, Object> map = new HashMap<>();
map.put("id", rs.getInt("id"));
map.put("title", rs.getInt("title"));
return map;
}
});
Map returnedMapContainsCursorName = simpleJdbcCall.execute(new MapSqlParameterSource().addValue("id", id));
List<Map> dataList = (List) returnedMapContainsCursorName.get(customCursorName);
//for test
for (Map data : dataList) {
System.out.println(data.get("id") + ", " + data.get("title"));
}
return dataList;
}
}
說明:
queryUsingReturnedSetof(int id) 對應到 PostgreSQL 中的 queryUsingReturnedSetof(param_id integer) 這個 Function。
queryUsingReturnedRefcursor(int id) 對應到 PostgreSQL 中的 queryUsingReturnedRefcursor(param_id integer) 這個 Function,可以看到它可以讓我們指定一個回傳的 refCursor name,
simpleJdbcCall.execute() 並不直接回傳 data list 資料,而是一個 Map,
就像上面我們直接用 Sql 語法查詢那樣回傳的 refCursor 的資料,
我們需要再用指定的 refCursor name 當作 Key 從 Map 中才能獲取真正的 data list 資料。
源碼下載分享: