存储过程,一个在笔者看来“很难用”的东西(主要还是菜),工作中碰见的也很少,逻辑简单的还好,逻辑一旦复杂了,这个东西看起来并没有代码这么直观,一旦注释没写好,或者同事离职了,后面维护的人简直痛不欲生。
最近的接手的一个项目中,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回写一下。
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一看
我尼玛,成功了!!!
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出参。