Mybatis調(diào)用PostgreSQL存儲過程實現(xiàn)數(shù)組入?yún)鬟f
前言
項目中用到了Mybatis調(diào)用PostgreSQL存儲過程(自定義函數(shù))相關(guān)操作,由于PostgreSQL自帶數(shù)組類型,所以有一個自定義函數(shù)的入?yún)⒕褪且粋€int數(shù)組,形如:
如上所示,參數(shù)是一個int數(shù)組,Mybatis提供了對調(diào)用存儲過程的支持,那么PostgreSQL獨有的數(shù)組類型作為存儲過程的參數(shù)又將如何處理呢?其實很簡單,mybatis提供了typeHandlers可以創(chuàng)建一個數(shù)組類型的類型處理器,具體做法為:實現(xiàn) org.apache.ibatis.type.TypeHandler 接口, 或繼承一個很便利的類 org.apache.ibatis.type.BaseTypeHandler, 然后可以選擇性地將它映射到一個 JDBC 類型,先稍作了解,后面再做詳細說明,接下來依舊結(jié)合一個示例來看看。
創(chuàng)建自定義函數(shù)
如圖,第一步首先是創(chuàng)建一個用于調(diào)用的自定義函數(shù),功能也很簡單,遍歷參數(shù)數(shù)組的每一個元素和t_student表的stuid做比較,若一致,則修改那條記錄的stuname(在其后拼接一段字符串),該自定義函數(shù)的DLL語句如下:
CREATE OR REPLACE FUNCTION "public"."func_arr_update"(ids _int4) RETURNS "pg_catalog"."void" AS $BODY$ DECLARE scount INTEGER; rownum integer := 1; BEGIN scount:=array_length(ids,1); while rownum <= scount LOOP update t_student set stuname = stuname || ' has been modified. ' where stuid = ids[rownum]; rownum := rownum + 1; END LOOP; RETURN; END $BODY$ LANGUAGE 'plpgsql' VOLATILE COST 100 ; ALTER FUNCTION "public"."func_arr_update"(ids _int4) OWNER TO "postgres";
很簡單,獲取到參數(shù)數(shù)組的長度后開始循環(huán),匹配stuid并更新stuname,直接在數(shù)據(jù)庫調(diào)用一下看看結(jié)果:
如上圖,可以看到成功修改了stuid為101,102和103的stuname,自定義函數(shù)已經(jīng)沒問題了,接下來就具體看一下如何通過mybatis調(diào)用。
調(diào)用自定義函數(shù)
mybatis中調(diào)用自定義函數(shù)很簡單,Mapper XML文件中的select元素直接提供了屬性支持——statementType,在官方文檔中可以看到:
如上圖,statementType的值默認是PREPARED,也就是說底層默認會使用jdbc的PreparedStatement,而我們都知道jdbc調(diào)用存儲過程時需要用CallableStatement,所以在這里我們需要將statementType的值設(shè)置為CALLABLE。
mybatis默認的ArrayTypeHandler
調(diào)用存儲過程很簡單,那么接下來的問題是如何在mybatis中傳一個數(shù)組參數(shù)到存儲過程中呢?這里就要用到另外一個概念——TypeHandler,這是mybatis提供的自定義類型轉(zhuǎn)換器,mybatis在預編譯語句對象(PreparedStatement)設(shè)置參數(shù)時或是從結(jié)果集中取值時都會用類型處理器將獲取的值以合適的方式轉(zhuǎn)換成Java類型,mybatis默認實現(xiàn)了一部分TypeHandler供我們使用,當我們沒有指定TypeHandler時(大多數(shù)情況都不會指定),mybatis會根據(jù)參數(shù)或者返回結(jié)果的不同,默認為我們選擇合適的TypeHandler處理,下面可以通過查看源碼大概看一下默認的TypeHandler,導入源碼后可以在org.apache.ibatis.type包下找到一個TypeHandlerRegistry類,typeHandler正是通過這個類管理的,先看一下它的構(gòu)造方法:
public TypeHandlerRegistry() { register(Boolean.class, new BooleanTypeHandler()); register(boolean.class, new BooleanTypeHandler()); register(JdbcType.BOOLEAN, new BooleanTypeHandler()); register(JdbcType.BIT, new BooleanTypeHandler()); register(Byte.class, new ByteTypeHandler()); register(byte.class, new ByteTypeHandler()); register(JdbcType.TINYINT, new ByteTypeHandler()); register(Short.class, new ShortTypeHandler()); register(short.class, new ShortTypeHandler()); register(JdbcType.SMALLINT, new ShortTypeHandler()); register(Integer.class, new IntegerTypeHandler()); register(int.class, new IntegerTypeHandler()); register(JdbcType.INTEGER, new IntegerTypeHandler()); register(Long.class, new LongTypeHandler()); register(long.class, new LongTypeHandler()); register(Float.class, new FloatTypeHandler()); register(float.class, new FloatTypeHandler()); register(JdbcType.FLOAT, new FloatTypeHandler()); register(Double.class, new DoubleTypeHandler()); register(double.class, new DoubleTypeHandler()); register(JdbcType.DOUBLE, new DoubleTypeHandler()); register(String.class, new StringTypeHandler()); register(String.class, JdbcType.CHAR, new StringTypeHandler()); register(String.class, JdbcType.CLOB, new ClobTypeHandler()); register(String.class, JdbcType.VARCHAR, new StringTypeHandler()); register(String.class, JdbcType.LONGVARCHAR, new ClobTypeHandler()); register(String.class, JdbcType.NVARCHAR, new NStringTypeHandler()); register(String.class, JdbcType.NCHAR, new NStringTypeHandler()); register(String.class, JdbcType.NCLOB, new NClobTypeHandler()); register(JdbcType.CHAR, new StringTypeHandler()); register(JdbcType.VARCHAR, new StringTypeHandler()); register(JdbcType.CLOB, new ClobTypeHandler()); register(JdbcType.LONGVARCHAR, new ClobTypeHandler()); register(JdbcType.NVARCHAR, new NStringTypeHandler()); register(JdbcType.NCHAR, new NStringTypeHandler()); register(JdbcType.NCLOB, new NClobTypeHandler()); register(Object.class, JdbcType.ARRAY, new ArrayTypeHandler()); register(JdbcType.ARRAY, new ArrayTypeHandler()); register(BigInteger.class, new BigIntegerTypeHandler()); register(JdbcType.BIGINT, new LongTypeHandler()); register(BigDecimal.class, new BigDecimalTypeHandler()); register(JdbcType.REAL, new BigDecimalTypeHandler()); register(JdbcType.DECIMAL, new BigDecimalTypeHandler()); register(JdbcType.NUMERIC, new BigDecimalTypeHandler()); register(Byte[].class, new ByteObjectArrayTypeHandler()); register(Byte[].class, JdbcType.BLOB, new BlobByteObjectArrayTypeHandler()); register(Byte[].class, JdbcType.LONGVARBINARY, new BlobByteObjectArrayTypeHandler()); register(byte[].class, new ByteArrayTypeHandler()); register(byte[].class, JdbcType.BLOB, new BlobTypeHandler()); register(byte[].class, JdbcType.LONGVARBINARY, new BlobTypeHandler()); register(JdbcType.LONGVARBINARY, new BlobTypeHandler()); register(JdbcType.BLOB, new BlobTypeHandler()); register(Object.class, UNKNOWN_TYPE_HANDLER); register(Object.class, JdbcType.OTHER, UNKNOWN_TYPE_HANDLER); register(JdbcType.OTHER, UNKNOWN_TYPE_HANDLER); register(Date.class, new DateTypeHandler()); register(Date.class, JdbcType.DATE, new DateOnlyTypeHandler()); register(Date.class, JdbcType.TIME, new TimeOnlyTypeHandler()); register(JdbcType.TIMESTAMP, new DateTypeHandler()); register(JdbcType.DATE, new DateOnlyTypeHandler()); register(JdbcType.TIME, new TimeOnlyTypeHandler()); register(java.sql.Date.class, new SqlDateTypeHandler()); register(java.sql.Time.class, new SqlTimeTypeHandler()); register(java.sql.Timestamp.class, new SqlTimestampTypeHandler()); // issue #273 register(Character.class, new CharacterTypeHandler()); register(char.class, new CharacterTypeHandler()); }
如上所示,這就是全部默認的typeHandler了,注意一下46,47行可以看到默認有一個ArrayTypeHandler,順便看一下它的源碼:
/* * Copyright 2009-2012 The MyBatis Team * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.ibatis.type; import java.sql.Array; import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; public class ArrayTypeHandler extends BaseTypeHandler<Object> { public ArrayTypeHandler() { super(); } @Override public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException { ps.setArray(i, (Array) parameter); } @Override public Object getNullableResult(ResultSet rs, String columnName) throws SQLException { Array array = rs.getArray(columnName); return array == null ? null : array.getArray(); } @Override public Object getNullableResult(ResultSet rs, int columnIndex) throws SQLException { Array array = rs.getArray(columnIndex); return array == null ? null : array.getArray(); } @Override public Object getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { Array array = cs.getArray(columnIndex); return array == null ? null : array.getArray(); } }
那它能否識別PostgreSQL的數(shù)組類型并將它自動轉(zhuǎn)換成Java數(shù)組類型呢?按官方的說法,既然這是默認的typeHandler,那么我們無需做任何配置mybatis會自動嘗試適配,所以直接寫測試代碼看看:
@Test public void testFunc1() { SqlSession session = sqlSessionFactory.openSession(); try { Map<String, Object> map = new HashMap<String, Object>(); map.put("ids", new Integer[] { 101, 102, 103 }); session.update("com.wl.entity.StudentMapper.testFuncUpdate2", map); session.commit(); } catch (Exception e) { e.printStackTrace(); } finally { session.close(); } }
<update id="testFuncUpdate2" statementType="CALLABLE"> {call func_arr_update (#{ids,mode=IN})} </update>
如上所示,參數(shù)傳的是一個Integer[],直接運行一下junit看看測試結(jié)果:
Can't infer the SQL type to use for an instance of [Ljava.lang.Integer;. Use setObject() with an explicit Types value to specify the type to use.
異常log如上所示,在調(diào)用AbstractJdbc2Statement類的setObject方法時拋出異常,那么再看看這個方法的源碼:
/* * This stores an Object into a parameter. */ public void setObject(int parameterIndex, Object x) throws SQLException { checkClosed(); if (x == null) setNull(parameterIndex, Types.OTHER); else if (x instanceof String) setString(parameterIndex, (String)x); else if (x instanceof BigDecimal) setBigDecimal(parameterIndex, (BigDecimal)x); else if (x instanceof Short) setShort(parameterIndex, ((Short)x).shortValue()); else if (x instanceof Integer) setInt(parameterIndex, ((Integer)x).intValue()); else if (x instanceof Long) setLong(parameterIndex, ((Long)x).longValue()); else if (x instanceof Float) setFloat(parameterIndex, ((Float)x).floatValue()); else if (x instanceof Double) setDouble(parameterIndex, ((Double)x).doubleValue()); else if (x instanceof byte[]) setBytes(parameterIndex, (byte[])x); else if (x instanceof java.sql.Date) setDate(parameterIndex, (java.sql.Date)x); else if (x instanceof Time) setTime(parameterIndex, (Time)x); else if (x instanceof Timestamp) setTimestamp(parameterIndex, (Timestamp)x); else if (x instanceof Boolean) setBoolean(parameterIndex, ((Boolean)x).booleanValue()); else if (x instanceof Byte) setByte(parameterIndex, ((Byte)x).byteValue()); else if (x instanceof Blob) setBlob(parameterIndex, (Blob)x); else if (x instanceof Clob) setClob(parameterIndex, (Clob)x); else if (x instanceof Array) setArray(parameterIndex, (Array)x); else if (x instanceof PGobject) setPGobject(parameterIndex, (PGobject)x); else if (x instanceof Character) setString(parameterIndex, ((Character)x).toString()); else if (x instanceof Map) setMap(parameterIndex, (Map)x); else { // Can't infer a type. throw new PSQLException(GT.tr("Can''t infer the SQL type to use for an instance of {0}. Use setObject() with an explicit Types value to specify the type to use.", x.getClass().getName()), PSQLState.INVALID_PARAMETER_TYPE); } }
我們參數(shù)傳進去的Integer[]數(shù)組是一個Object數(shù)組,而 setObject(int parameterIndex, Object x)方法的第二個參數(shù)是Object,所以這里這里自然無法匹配也就報錯了,那么換成int[]可以嗎?在上面的else if語句中明顯沒有x instanceof int[]這行代碼,所以當然也不行,說到這里也就明確了mybatis默認提供的ArrayTypeHandler是無法自動識別PostgreSQL的數(shù)組類型,我們必須自定義一個參數(shù)為Object[]的ArrayTypeHandler才能實現(xiàn)匹配。
自定義ArrayTypeHandler
如題,先貼上代碼:
package com.wl.util; import java.sql.Array; import java.sql.CallableStatement; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import org.apache.ibatis.type.BaseTypeHandler; import org.apache.ibatis.type.JdbcType; import org.apache.ibatis.type.MappedJdbcTypes; import org.apache.ibatis.type.TypeException; @MappedJdbcTypes(JdbcType.ARRAY) public class ArrayTypeHandler extends BaseTypeHandler<Object[]> { private static final String TYPE_NAME_VARCHAR = "varchar"; private static final String TYPE_NAME_INTEGER = "integer"; private static final String TYPE_NAME_BOOLEAN = "boolean"; private static final String TYPE_NAME_NUMERIC = "numeric"; @Override public void setNonNullParameter(PreparedStatement ps, int i, Object[] parameter, JdbcType jdbcType) throws SQLException { String typeName = null; if (parameter instanceof Integer[]) { typeName = TYPE_NAME_INTEGER; } else if (parameter instanceof String[]) { typeName = TYPE_NAME_VARCHAR; } else if (parameter instanceof Boolean[]) { typeName = TYPE_NAME_BOOLEAN; } else if (parameter instanceof Double[]) { typeName = TYPE_NAME_NUMERIC; } if (typeName == null) { throw new TypeException( "ArrayTypeHandler parameter typeName error, your type is " + parameter.getClass().getName()); } Connection conn = ps.getConnection(); Array array = conn.createArrayOf(typeName, parameter); ps.setArray(i, array); } @Override public Object[] getNullableResult(ResultSet rs, String columnName) throws SQLException { return getArray(rs.getArray(columnName)); } @Override public Object[] getNullableResult(ResultSet rs, int columnIndex) throws SQLException { return getArray(rs.getArray(columnIndex)); } @Override public Object[] getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { return getArray(cs.getArray(columnIndex)); } private Object[] getArray(Array array) { if (array == null) { return null; } try { return (Object[]) array.getArray(); } catch (Exception e) { } return null; } }
如上所示,我們指定了參數(shù)類型為Object[],這樣就可以接收Integer[]類型的參數(shù)了,關(guān)鍵是44~46行,postgresql的驅(qū)動類AbstractJdbc4Connection實現(xiàn)了Connect接口的createArrayOf方法,源碼如下:
public Array createArrayOf(String typeName, Object[] elements) throws SQLException { checkClosed(); int oid = getTypeInfo().getPGArrayType(typeName); if (oid == Oid.UNSPECIFIED) throw new PSQLException(GT.tr("Unable to find server array type for provided name {0}.", typeName), PSQLState.INVALID_NAME); char delim = getTypeInfo().getArrayDelimiter(oid); StringBuffer sb = new StringBuffer(); appendArray(sb, elements, delim); // This will not work once we have a JDBC 5, // but it'll do for now. return new Jdbc4Array(this, oid, sb.toString()); }
這樣通過自定義的ArrayTypeHandler就可以在Mybatis中方便的操作數(shù)組類型數(shù)據(jù)了,最后再測試一下,測試類代碼不變,僅需在調(diào)用存儲過程時指定mapper文件的typeHandler即可:
@Test public void testFunc1() { SqlSession session = sqlSessionFactory.openSession(); try { Map<String, Object> map = new HashMap<String, Object>(); map.put("ids", new Integer[] { 101, 102, 103 }); session.update("com.wl.entity.StudentMapper.testFuncUpdate2", map); session.commit(); } catch (Exception e) { e.printStackTrace(); } finally { session.close(); } }
<update id="testFuncUpdate2" statementType="CALLABLE"> {call func_arr_update (#{ids,mode=IN,typeHandler=com.wl.util.ArrayTypeHandler})} </update>
再次運行junit看一下測試結(jié)果:
如上所示,此時已經(jīng)可以成功調(diào)用參數(shù)為Integer[]數(shù)組的pg自定義函數(shù)了。
總結(jié)
簡單記錄一下在mybatis中調(diào)用postgresql自定義函數(shù)時傳遞數(shù)組參數(shù)的解決方案,希望對遇到同樣問題的朋友有所幫助,The End。
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
PostgreSQL 數(shù)據(jù)庫跨版本升級常用方案解析
這篇文章主要介紹了PostgreSQL 數(shù)據(jù)庫跨版本升級常用方案解析,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-03-03在Ubuntu中安裝Postgresql數(shù)據(jù)庫的步驟詳解
PostgreSQL 是一款強大的,開源的,對象關(guān)系型數(shù)據(jù)庫系統(tǒng)。它支持所有的主流操作系統(tǒng),包括 Linux、Unix(AIX、BSD、HP-UX,SGI IRIX、Mac OS、Solaris、Tru64) 以及 Windows 操作系統(tǒng)。本文給大家介紹了在Ubuntu中安裝Postgresql數(shù)據(jù)庫的步驟,需要的朋友可以參考下。2017-09-09PostgreSQL數(shù)據(jù)庫管理系統(tǒng)快速入門
這篇文章主要介紹了PostgreSQL數(shù)據(jù)庫快速入門,PostgreSQL是一個功能強大的開源對象關(guān)系型數(shù)據(jù)庫系統(tǒng),他使用和擴展了SQL語言,并結(jié)合了許多安全存儲和擴展最復雜數(shù)據(jù)工作負載的功能,需要的朋友可以參考下2023-07-07如何將excel表格數(shù)據(jù)導入postgresql數(shù)據(jù)庫
這篇文章主要介紹了如何將excel表格數(shù)據(jù)導入postgresql數(shù)據(jù)庫,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-03-03postgresql 切換 log、xlog日志的實現(xiàn)
這篇文章主要介紹了postgresql 切換 log、xlog日志的實現(xiàn)方式,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-01-01PostgreSQL實時查看數(shù)據(jù)庫實例正在執(zhí)行的SQL語句實例詳解
在任何數(shù)據(jù)庫中,分析和優(yōu)化SQL的執(zhí)行,最重要的工作就是執(zhí)行計劃的解讀,而說到執(zhí)行計劃得先了解postgresql的查詢執(zhí)行過程,下面這篇文章主要給大家介紹了關(guān)于PostgreSQL實時查看數(shù)據(jù)庫實例正在執(zhí)行的SQL語句的相關(guān)資料,需要的朋友可以參考下2023-01-01