存储过程,一个在笔者看来“很难用”的东西(主要还是菜),工作中碰见的也很少,逻辑简单的还好,逻辑一旦复杂了,这个东西看起来并没有代码这么直观,一旦注释没写好,或者同事离职了,后面维护的人简直痛不欲生。

最近的接手的一个项目中,DAO层使用的是mybatis,各种功能的实现都是自己写sql,今天碰到一个统计功能,是由别人写好的存储过程,需要我用Mybatis来调用,本来以为很简单的东西,可硬是来来回回折腾了好几次,在此记录下调用过程中碰到的一些问题。

代码太长了,只展示一下入参出参

CREATE DEFINER=`root`@`%` PROCEDURE `query-new`(IN `v_id` int, IN `v_type` int, IN `v_qsrq` date, IN `v_zzrq` date, in v_page int, out v_all int, out v_allpage int)

结果除了上面的两个out出参,还返回了8个字段:n1、n2、n3、n4、n5、n6、n7、n8

1、存储过程名字不能用横杠“-”

同事给存储过程取了一个名字“query-new”,我在Navicat中执行的时候,没有任何问题,入参和返回都是OK的,可是当我用mybatis调用的时候,就在这里栽了个跟头

### Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '-new (

遂改之,查问题的时候,发现大家都喜欢用下划线“_”,所以我也改为了“query_new”,问题得到解决。

2、out类型参数,必须指定jdbcType

org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.executor.ExecutorException: The JDBC Type must be specified for output parameter.  Parameter: n1

因为我在写mapper的时候,传递参数一直都不习惯指定jdbcType的,而且平时的查询要么是list,要么是count,要么是自定义对象,这些返回值都可以使用resultType和resultMap来指定,所以我的mapper是这么写的:

<select id="query_new" statementType="CALLABLE" parameterType="java.util.Map">
    call query_new (
        #{v_id,mode=IN},
        #{v_type,mode=IN},
        #{v_qsrq,mode=IN},
        #{v_zzrq,mode=IN},
        #{v_page,mode=IN},

        #{v_all,mode=OUT},
        #{v_allpage,mode=OUT}
    )
</select>

存储过程中,不仅有返回值,还有out类型的出参,那这个时候再用我之前的逻辑,就出问题了,遂改之

<select id="query_new" statementType="CALLABLE" parameterType="java.util.Map">
    call query_new (
        #{v_id,mode=IN},
        #{v_type,mode=IN},
        #{v_qsrq,mode=IN},
        #{v_zzrq,mode=IN},
        #{v_page,mode=IN},

        #{v_all,mode=OUT,jdbcType=INTEGER},
        #{v_allpage,mode=OUT,jdbcType=INTEGER}
    )
</select>

此时,可以正常的执行了,赶紧写了一个mapper.java来测试

/**
 * 调用存储过程,传入参数、接收参数
 *
 * @param map 入参,没有封装query,偷个懒,直接用map
 */
void query_new(Map<String, Object> map);

debug发现代码没问题,map中的两个out出参也被赋上了值,这个有点儿类似于mybatis的insert上面的keyPropertyId,每次插入新数据之后,会将映射到的javabean的主键ID回写一下。

springboot mybatis 调用存储过程 Socket read timed out mybatis调用存储过程详解_存储过程

 

 

3、参数过多,超过指定数量

Caused by: java.sql.SQLException: Parameter index of 8 is out of range (1, 7)

这个错误,其实不应该犯,但是以前都没用过,第一次还真就踩了这个坑。

开始提到了,这个存储过程不仅返回了两个out出参,同时还返回了8个字段,也就是说,会返回一个List结果集,所以就想当然的以为多追加几个out就可以了

<select id="query_new" statementType="CALLABLE" parameterType="java.util.Map">
    call query_new (
        #{v_id,mode=IN,jdbcType=INTEGER},
        #{v_type,mode=IN,jdbcType=INTEGER},
        #{v_qsrq,mode=IN,jdbcType=TIMESTAMP},
        #{v_zzrq,mode=IN,jdbcType=TIMESTAMP},
        #{v_page,mode=IN,jdbcType=INTEGER},

        #{v_all,mode=OUT,jdbcType=INTEGER},
        #{v_allpage,mode=OUT,jdbcType=INTEGER}

        #{n1,mode=OUT,jdbcType=TIMESTAMP},
        #{n2,mode=OUT,jdbcType=VARCHAR}
        #{n3,mode=OUT,jdbcType=DOUBLE},
        #{n4,mode=OUT,jdbcType=VARCHAR}
        #{n5,mode=OUT,jdbcType=VARCHAR},
        #{n6,mode=OUT,jdbcType=VARCHAR}
        #{n7,mode=OUT,jdbcType=VARCHAR},
        #{n8,mode=OUT,jdbcType=INTEGER}
    )
</select>

上述代码中,为了以防类型报错,所以我在入参中也都一一指定了jdbcType,本想着一起在map中将值都取出来,但是很快就让我知道了这个想法多么的天真,直接提示out of range。

4、没有指定返回类型

接杯水,静坐,沉思,既然这个天马行空的想法不现实,那就直接按规矩办事,他不是需要返回结果集么,那我直接追加一个result可不可以呢?

<select id="query_new" statementType="CALLABLE" parameterType="java.util.Map" resultMap="itemMap">
        call query_new (
            #{v_id,mode=IN,jdbcType=INTEGER},
            #{v_type,mode=IN,jdbcType=INTEGER},
            #{v_qsrq,mode=IN,jdbcType=TIMESTAMP},
            #{v_zzrq,mode=IN,jdbcType=TIMESTAMP},
            #{v_page,mode=IN,jdbcType=INTEGER},
            #{v_all,mode=OUT,jdbcType=INTEGER},
            #{v_allpage,mode=OUT,jdbcType=INTEGER}
        )
</select>
<resultMap id="itemMap" type="com.st.money.wx.vo.wx.rake.RakeBackItemVO">
    <result column="n1" property="time"/>
    <result column="n2" property="source"/>
    <result column="n3" property="money"/>
    <result column="n4" property="goodsName"/>
    <result column="n5" property="userName"/>
    <result column="n6" property="status"/>
    <result column="n7" property="remark"/>
    <result column="n8" property="copperNumber"/>
</resultMap>

debug一波,直接报错:

Caused by: org.apache.ibatis.executor.ExecutorException: A query was run and no Result Maps were found for the Mapped Statement 'com.st.money.wx.dao.RakeBackDAO.query_new'.  It's likely that neither a Result Type nor a Result Map was specified.

失败了这么多次,咋一看又报错了,好紧张,但是仔细看一下,原来是没有javaDao中没有指定返回值,回过头去一看,还真的是么有,之前那以为所有的值都是out出参的形式返回,所以写的是void,失策失策,遂改之

/**
 * 调用存储过程,传入参数、接收参数
 *
 * @param map 入参,没有封装query,偷个懒,直接用map
 */
List<RakeBackItemVO> query_new(Map<String, Object> map);

有点儿小紧张,debug一看

springboot mybatis 调用存储过程 Socket read timed out mybatis调用存储过程详解_java_02

 

我尼玛,成功了!!!

java调用代码如下:

// in入参
Map<String, Object> map = new HashMap<>();
map.put("v_id", userId);
map.put("v_type", moneyType + 1);
map.put("v_qsrq", new Date(startTime));
map.put("v_zzrq", new Date(endTime));
map.put("v_page", page);
// 结果集
List<RakeBackItemVO> list = rakeBackDAO.query_new(map);
// out出参
Integer totalCount = (Integer) map.get("v_all");
Integer totalPage = (Integer) map.get("v_allpage");

好了,踩了这么多坑,总算是调用成功了~

最后提一点:查阅资料时,发现的案例中,会使用花括号将mapper.xml中的调用代码括起来,我测试过两种方式,并无太大差别,都能正确返回结果集和out出参。