平常开发中,这种返回并接收多结果集的场景并不常见,基本多在执行存储过程中遇到,所以在此将处理方式记录下来,以便日后碰到相同场景时,可以快速解决。
提示
起因在于 SqlServer 中前人写的一条存储过程,原先是 .Net 调用的,现在需要移至 Java 程序调用执行并获取其结果。其返回的结果集有4个,因为之前没有碰到过这种情况,所以稍微有点麻烦。
多结果集展示(执行SqlServer中 sp_help
存储过程时的返回结果):
如图所示,返回的结果集有多个,所以如果按照我们平常的代码来写是接收不到所有参数的。
List<Entity> exec(Entity entity);
现在我们直接以 SqlServer 的 sp_help
这个存储过程为例,尝试执行它并获取其返回结果。
相信大家对JDBC的三种Statement一定有所耳闻,分别是 Statement
、PreparedStatement
、CallableStatement
。其中 CallableStatement
是用于存储过程执行的,若你也是执行存储过程,那就设置这个Statement。
并且由于是 多结果集 的,所以不能用 ResultType,必须用 ResultMap
接收。
提示
生成 ResultMap
的工作可以由我开发的插件 ZhiYouToolkit 使用文档 完成,能够将你从手敲的痛苦中解放出来。
<!-- 两个ResultMap,映射两个结果集 -->
<resultMap id="Info1ResultMap" type="org.example.entity.Info1">
<result column="Owner" jdbcType="VARCHAR" property="owner"/>
<result column="Object_type" jdbcType="VARCHAR" property="objectType"/>
<result column="Name" jdbcType="VARCHAR" property="name"/>
</resultMap>
<resultMap id="Info2ResultMap" type="org.example.entity.Info2">
<result column="User_type" jdbcType="VARCHAR" property="userType"/>
<result column="Storage_type" jdbcType="VARCHAR" property="storageType"/>
<result column="Length" jdbcType="VARCHAR" property="length"/>
<result column="Prec" jdbcType="VARCHAR" property="prec"/>
<result column="Scale" jdbcType="VARCHAR" property="scale"/>
<result column="Nullable" jdbcType="VARCHAR" property="nullable"/>
<result column="Default_name" jdbcType="VARCHAR" property="defaultName"/>
<result column="Rule_name" jdbcType="VARCHAR" property="ruleName"/>
<result column="Collation" jdbcType="VARCHAR" property="collation"/>
</resultMap>
<!-- resultMap 可用这个代替: @ResultMap({"Info1ResultMap", "Info2ResultMap"}) -->
<!-- statementType 可用这个代替: @Options(statementType = StatementType.CALLABLE) -->
<select id="exec1" statementType="CALLABLE" resultMap="Info1ResultMap,Info2ResultMap">
EXEC sp_help
</select>
List<List<?>> exec1();
List<List<?>>
注
将 resultMap 属性这样设置,这样MyBatis才会知道怎么给结果集做映射,并且这时IDEA会爆红,不用管他,如果介意,可以尝试用 @ResultMap
注解代替。
返回值 List<List<?>>
最外围的List指的是两个结果集的接收列表,而嵌套的List则是两个结果集分别拥有的数据(如果你的返回都是单独数据,那么可以不嵌套List)。
因为返回的不是单一类型的数据,所以在使用该方法时需要强转为对应类型。尽量定义为 ?
,这样会方便强转
(其实定义成什么都无所谓,因为Java泛型管不了,只是你在编译时从一个类型转成另一个类型会比较麻烦。就比如你清楚这个套着 List<A>
泛型的壳的集合其实里面的真实类型是 List<B>
,但是编译器不会直接让你转换过去)。
这样大概已经完成了,执行的时候可以正常获取执行结果。
正常来说这样就已经够了,但是我记得当时我做了相对更多的工作。由于真实场景中,我的返回字段实在太多,直接使用实体类映射出现了些问题,比如说有些字段无法被映射进来......
所以我使用 HashMap 接收,确保所有字段都能映射。我编写ResultMap,定义字段与类属性的映射关系,MyBatis启动时会自动扫描ResultMap标签并为其创建 org.apache.ibatis.mapping.ResultMap
对象,作为映射关系。我获取到这些对象,并用来作为 HashMap 与实体类的映射,将存储在HashMap中的数据转移到我的对象中。
@Options(statementType = StatementType.CALLABLE)
@ResultMap({"Info1ResultMap", "Info2ResultMap"})
@SelectProvider(type = BaseProvider.class, method = "exec")
List<List<?>> exec(Entity entity);
// ------------------------------------------ //
// 获取ResultMap对象,里面存储着字段与类属性的映射关系
ResultMap info1ResultMap = MyBatisUtil.getResultMap(ExecMapper.class, "Info1ResultMap");
ResultMap info2ResultMap = MyBatisUtil.getResultMap(ExecMapper.class, "Info2ResultMap");
// 获取字段映射集
List<ResultMapping> propertyResultMappings = resultMap.getPropertyResultMappings();
// 处理映射,Map中取对应列
for (Map<String, Object> resMap : resMapList) {
// 创建实体类对象,并填充对象...
}
提示
如果你项目中没有用到MyBatis,可以尝试使用原生JDBC那种方式解决。