此页内容
JavaMyBatis

利用MyBatis处理多结果集返回

Memory

1197字约4分钟

JavaMyBatisSQL多结果集

2024-04-19

平常开发中,这种返回并接收多结果集的场景并不常见,基本多在执行存储过程中遇到,所以在此将处理方式记录下来,以便日后碰到相同场景时,可以快速解决。

场景

提示

起因在于 SqlServer 中前人写的一条存储过程,原先是 .Net 调用的,现在需要移至 Java 程序调用执行并获取其结果。其返回的结果集有4个,因为之前没有碰到过这种情况,所以稍微有点麻烦。


多结果集展示(执行SqlServer中 sp_help 存储过程时的返回结果):

hero

如图所示,返回的结果集有多个,所以如果按照我们平常的代码来写是接收不到所有参数的。

List<Entity> exec(Entity entity);

解决

现在我们直接以 SqlServersp_help 这个存储过程为例,尝试执行它并获取其返回结果。

相信大家对JDBC的三种Statement一定有所耳闻,分别是 StatementPreparedStatementCallableStatement。其中 CallableStatement 是用于存储过程执行的,若你也是执行存储过程,那就设置这个Statement

并且由于是 多结果集 的,所以不能用 ResultType,必须用 ResultMap 接收。

提示

生成 ResultMap 的工作可以由我开发的插件 ZhiYouToolkit 使用文档 完成,能够将你从手敲的痛苦中解放出来。

Mapper.Xml
<!-- 两个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>



















 
 



Mapper.java
List<List<?>> exec1();

步骤

  • 定义两个 <ResultMap>
  • 将 statementType 设为 CALLABLE
  • 将 resultMap 设为两个 <ResultMap> 的 id,用逗号隔开
  • 将 Mapper方法 的返回值尽量设置为 List<List<?>>

将 resultMap 属性这样设置,这样MyBatis才会知道怎么给结果集做映射,并且这时IDEA会爆红,不用管他,如果介意,可以尝试用 @ResultMap 注解代替。


返回值 List<List<?>> 最外围的List指的是两个结果集的接收列表,而嵌套的List则是两个结果集分别拥有的数据(如果你的返回都是单独数据,那么可以不嵌套List)。


因为返回的不是单一类型的数据,所以在使用该方法时需要强转为对应类型。尽量定义为 ?,这样会方便强转
(其实定义成什么都无所谓,因为Java泛型管不了,只是你在编译时从一个类型转成另一个类型会比较麻烦。就比如你清楚这个套着 List<A> 泛型的壳的集合其实里面的真实类型是 List<B> ,但是编译器不会直接让你转换过去)。


这样大概已经完成了,执行的时候可以正常获取执行结果。

hero

hero


我的实现

正常来说这样就已经够了,但是我记得当时我做了相对更多的工作。由于真实场景中,我的返回字段实在太多,直接使用实体类映射出现了些问题,比如说有些字段无法被映射进来......

所以我使用 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那种方式解决。