侯体宗的博客
  • 首页
  • Hyperf版
  • beego仿版
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

Mybatis调用PostgreSQL存储过程实现数组入参传递

数据库  /  管理员 发布于 5年前   215

前言

项目中用到了Mybatis调用PostgreSQL存储过程(自定义函数)相关操作,由于PostgreSQL自带数组类型,所以有一个自定义函数的入参就是一个int数组,形如:

复制代码 代码如下:CREATE OR REPLACE FUNCTION "public"."func_arr_update"(ids _int4)...
如上所示,参数是一个int数组,Mybatis提供了对调用存储过程的支持,那么PostgreSQL独有的数组类型作为存储过程的参数又将如何处理呢?其实很简单,mybatis提供了typeHandlers可以创建一个数组类型的类型处理器,具体做法为:实现 org.apache.ibatis.type.TypeHandler 接口, 或继承一个很便利的类 org.apache.ibatis.type.BaseTypeHandler, 然后可以选择性地将它映射到一个 JDBC 类型,先稍作了解,后面再做详细说明,接下来依旧结合一个示例来看看。

创建自定义函数

如图,第一步首先是创建一个用于调用的自定义函数,功能也很简单,遍历参数数组的每一个元素和t_student表的stuid做比较,若一致,则修改那条记录的stuname(在其后拼接一段字符串),该自定义函数的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";

很简单,获取到参数数组的长度后开始循环,匹配stuid并更新stuname,直接在数据库调用一下看看结果:

如上图,可以看到成功修改了stuid为101,102和103的stuname,自定义函数已经没问题了,接下来就具体看一下如何通过mybatis调用。

调用自定义函数

mybatis中调用自定义函数很简单,Mapper XML文件中的select元素直接提供了属性支持――statementType,在官方文档中可以看到:

如上图,statementType的值默认是PREPARED,也就是说底层默认会使用jdbc的PreparedStatement,而我们都知道jdbc调用存储过程时需要用CallableStatement,所以在这里我们需要将statementType的值设置为CALLABLE。

mybatis默认的ArrayTypeHandler

调用存储过程很简单,那么接下来的问题是如何在mybatis中传一个数组参数到存储过程中呢?这里就要用到另外一个概念――TypeHandler,这是mybatis提供的自定义类型转换器,mybatis在预编译语句对象(PreparedStatement)设置参数时或是从结果集中取值时都会用类型处理器将获取的值以合适的方式转换成Java类型,mybatis默认实现了一部分TypeHandler供我们使用,当我们没有指定TypeHandler时(大多数情况都不会指定),mybatis会根据参数或者返回结果的不同,默认为我们选择合适的TypeHandler处理,下面可以通过查看源码大概看一下默认的TypeHandler,导入源码后可以在org.apache.ibatis.type包下找到一个TypeHandlerRegistry类,typeHandler正是通过这个类管理的,先看一下它的构造方法:

 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的数组类型并将它自动转换成Java数组类型呢?按官方的说法,既然这是默认的typeHandler,那么我们无需做任何配置mybatis会自动尝试适配,所以直接写测试代码看看:

@Testpublic 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>

如上所示,参数传的是一个Integer[],直接运行一下junit看看测试结果:

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如上所示,在调用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);    }  }

我们参数传进去的Integer[]数组是一个Object数组,而 setObject(int parameterIndex, Object x)方法的第二个参数是Object,所以这里这里自然无法匹配也就报错了,那么换成int[]可以吗?在上面的else if语句中明显没有x instanceof int[]这行代码,所以当然也不行,说到这里也就明确了mybatis默认提供的ArrayTypeHandler是无法自动识别PostgreSQL的数组类型,我们必须自定义一个参数为Object[]的ArrayTypeHandler才能实现匹配。

自定义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;  }}

如上所示,我们指定了参数类型为Object[],这样就可以接收Integer[]类型的参数了,关键是44~46行,postgresql的驱动类AbstractJdbc4Connection实现了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中方便的操作数组类型数据了,最后再测试一下,测试类代码不变,仅需在调用存储过程时指定mapper文件的typeHandler即可:

@Testpublic 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看一下测试结果:

如上所示,此时已经可以成功调用参数为Integer[]数组的pg自定义函数了。

总结

简单记录一下在mybatis中调用postgresql自定义函数时传递数组参数的解决方案,希望对遇到同样问题的朋友有所帮助,The End。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。


  • 上一条:
    PostgreSQL分区表(partitioning)应用实例详解
    下一条:
    Mac OS上安装PostgreSQL的教程
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • 分库分表的目的、优缺点及具体实现方式介绍(0个评论)
    • DevDB - 在 VS 代码中直接访问数据库(0个评论)
    • 在ubuntu系统中实现mysql数据存储目录迁移流程步骤(0个评论)
    • 在mysql中使用存储过程批量新增测试数据流程步骤(0个评论)
    • php+mysql数据库批量根据条件快速更新、连表更新sql实现(0个评论)
    • 近期文章
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 欧盟关于强迫劳动的规定的官方举报渠道及官方举报网站(0个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf文件功能(0个评论)
    • Laravel从Accel获得5700万美元A轮融资(0个评论)
    • 在go + gin中gorm实现指定搜索/区间搜索分页列表功能接口实例(0个评论)
    • 在go语言中实现IP/CIDR的ip和netmask互转及IP段形式互转及ip是否存在IP/CIDR(0个评论)
    • PHP 8.4 Alpha 1现已发布!(0个评论)
    • Laravel 11.15版本发布 - Eloquent Builder中添加的泛型(0个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2017-06
    • 2017-08
    • 2017-09
    • 2017-10
    • 2017-11
    • 2018-01
    • 2018-05
    • 2018-10
    • 2018-11
    • 2020-02
    • 2020-03
    • 2020-04
    • 2020-05
    • 2020-06
    • 2020-07
    • 2020-08
    • 2020-09
    • 2021-02
    • 2021-04
    • 2021-07
    • 2021-08
    • 2021-11
    • 2021-12
    • 2022-02
    • 2022-03
    • 2022-05
    • 2022-06
    • 2022-07
    • 2022-08
    • 2022-09
    • 2022-10
    • 2022-11
    • 2022-12
    • 2023-01
    • 2023-03
    • 2023-04
    • 2023-05
    • 2023-07
    • 2023-08
    • 2023-10
    • 2023-11
    • 2023-12
    • 2024-01
    • 2024-03
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客