Mybatis复杂类型的结果映射

专栏精选

Mybatis的快速入门

引入Mybatis

Mybatis的增删改查扩展功能说明

mapper映射的参数和结果

摘要

在MyBatis框架中,Association标签和Collection标签是处理对象关系映射的关键组件。它们能够以简洁明了的方式映射实体类与数据库表之间的关系。通过这两个标签,我们可以实现一对一(One-to-One)、一对多(One-to-Many)和多对多(Many-to-Many)的关系映射,从而简化了数据库操作和对象关系之间的复杂转换。

引言

你是否曾经遇到过在Mybatis中处理复杂的对象关系映射时感到困扰?是否曾经遇到过一个查询功能的SQL过长而感到无从下手?是否希望找到一种简单而高效的方式来映射实体类与数据库表之间的关系?如果你对这些话题感兴趣,那么本文将为你提供一种简单有效的解决方案。

在阅读本文之前,我们希望你能积极参与并与我们一起探讨。请随时在评论区分享你的见解、疑问或经验。你的参与不仅能帮助我们更好地理解你的需求,还能为其他读者提供宝贵的参考意见。做好准备,Let’s go🚎🚀

正文

首图

针对以下业务逻辑, 编码/状态/类型 属性一般通过字典值保存, 业务表中存储code, 字典表中存储对应的名称, 如何在一次查询中直接获取到对应的字典编码和名称呢

在这里插入图片描述

association标签的简单查询

复杂类型映射需要通过 <resultMap ...></resultMap> 标签实现,该标签支持通过数据关系构建复杂对象。

为了方便测试,在数据库中新增字典表

create table dict_test  
(  
    dict_name varchar(20)   not null comment '名称',  
    dict_code char          not null comment '编码',  
    dict_type varchar(50)   not null comment '类型',  
    dict_sort int default 0 not null comment '排序'  
)  
    comment '字典表-测试用';

新增对应的实体类

public class DictTest {  
      
    private String dictName;  
  
    private String dictCode;  
  
    private String dictType;  
  
    private Integer dictSort;
    //getter、setter、toString方法省略
}

修改AppTestEntity 类,构建为复杂对象

public class AppTestEntity {  
    private Long id;  
    private String appName;  
    private String appCode;  
    private String authType;  
    private LocalDate createDate;  
    private String creator;  
    private String appStatus;
  
    private DictTest authTypeDict;
	//省略getter、setter、toString方法
}

新增mapper接口方法

public interface SimpleQueryMapper {
	AppTestEntity queryAppDetail(@Param("id") Integer id);
}

映射文件

<resultMap id="appDetail" type="appTestEntity" autoMapping="true">  
	<!--表示要映射的关系-->
    <association property="authTypeDict" javaType="dictTest">  
        <id column="auth_type_dc" property="dictCode"/>  
        <id column="auth_type_dt" property="dictType"/>  
        <result column="auth_type_dn" property="dictName"/>  
        <result column="auth_type_ds" property="dictSort"/>  
    </association>
</resultMap>  
  
<select id="queryAppDetail" resultMap="appDetail">  
    select t1.*  
        ,t2.dict_code as auth_type_dc,t2.dict_name as auth_type_dn,t2.dict_type as auth_type_dt,t2.dict_sort as auth_type_ds  
    from (  
        select id,app_name,app_code,auth_type,create_date,creator,app_status from app_test where id=#{id}  
    ) t1 left join (  
        select dict_code,dict_name,dict_type,dict_sort from dict_test where dict_type='app_auth_type'  
    ) t2 on t1.auth_type=t2.dict_code  
</select>

测试代码

public class SimpleQueryService extends MybatisService<SimpleQueryMapper>{  
    @Override  
    public void doService() {  
        SimpleQueryMapper mapper = super.getMapper(SimpleQueryMapper.class);  
        this.testAppAssociation(mapper);  
    }
    
    private void testAppAssociation(SimpleQueryMapper mapper){  
        AppTestEntity detail = mapper.queryAppDetail(2);  
        System.out.println(detail);  
    }  
}

打印结果

AppTestEntity{id=2, appName='公共应用', appCode='common', authType='2', createDate=2023-10-31, creator='admin2', authTypeDict=DictTest{dictName='手机号', dictCode='2', dictType='app_auth_type', dictSort=2}}

association标签的嵌套查询

以上就是 <resultMap><association>标签的简单用法,该标签还支持在<select>标签中间嵌套 <select> 标签

<resultMap id="appDetail" type="appTestEntity" autoMapping="true">  
	<!--这里需要注意,如果app_status作为嵌套select查询的条件时,需要指定app_status和appStatus的映射关系,否则appStatus属性会为空-->
	<result column="app_status" property="appStatus"/>
    <association property="authTypeDict" javaType="dictTest">  
        <id column="auth_type_dc" property="dictCode"/>  
        <id column="auth_type_dt" property="dictType"/>  
        <result column="auth_type_dn" property="dictName"/>  
        <result column="auth_type_ds" property="dictSort"/>  
    </association>    
    <!--
    注意这里的写法
    colomn 属性表示在父sql中的字段名称,也就是上文提到的<select id="queryAppDetail">...</select>查询结果中的app_status,而app_status作为queryAppStatus这个查询sql的入参,也即#{code}
    -->
    <association property="appStatusDict" javaType="dictTest" select="queryAppStatus" column="app_status"></association>  
</resultMap>

<select id="queryAppStatus" resultType="dictTest">  
    select dict_code,dict_name,dict_type,dict_sort from dict_test  
    where dict_type='app_status' and dict_code=#{code}  
</select>

打印结果如下:

AppTestEntity{id=2, appName='公共应用', appCode='common', authType='2', createDate=2023-10-31, creator='admin2', appStatus='0', authTypeDict=DictTest{dictName='手机号', dictCode='2', dictType='app_auth_type', dictSort=2}, appStatusDict=DictTest{dictName='临时应用', dictCode='0', dictType='app_status', dictSort=0}}

针对以下一对多关系(应用明细 和 服务列表)的关联查询有需要如何实现呢?

在这里插入图片描述

collection集合类型的嵌套查询

以上这个业务是一个典型的详情展示页面,其中的“应用”和“服务”存在一对多关联,需要在AppTestEntity类中添加List<ServiceTestEntity>类型的属性,那么如何通过<resultMap>标签表示这种对应关系呢?

添加数据库表

create table service_test  
(  
    id           int auto_increment comment '主键ID'  
        primary key,  
    service_name varchar(50)  not null comment '服务名称',  
    service_code varchar(50)  not null comment '服务编码',  
    service_path varchar(100) not null comment '资源地址',  
    app_id       int          null comment '应用ID'  
)  
    comment '微服务表';

生成实体类

public class ServiceTestEntity {  
    private Long id;  
    private String serviceName;  
    private String serviceCode;  
    private String servicePath;  
    private Long appId;
	//getter, setter, toString方法省略
}

public class AppTestEntity {  
    private Long id;  
    private String appName;  
    private String appCode;  
    private String authType;  
    private LocalDate createDate;  
    private String creator;  
    private String appStatus;  
  
    private DictTest authTypeDict;  
    private DictTest appStatusDict;  
  
    private List<ServiceTestEntity> services;
    //getter, setter, toString
}

映射文件的写法

<resultMap id="appDetail" type="appTestEntity" autoMapping="true">  
    <result column="app_status" property="appStatus"/>  
    <association property="authTypeDict" javaType="dictTest">  
        <id column="auth_type_dc" property="dictCode"/>  
        <id column="auth_type_dt" property="dictType"/>  
        <result column="auth_type_dn" property="dictName"/>  
        <result column="auth_type_ds" property="dictSort"/>  
    </association>    
    <association property="appStatusDict" javaType="dictTest" select="queryAppStatus" column="app_status"/>
    <collection property="services" column="id" select="queryServices" ofType="serviceTestEntity"/>  
</resultMap>

<select id="queryServices" resultType="serviceTestEntity">  
    select * from service_test where app_id=#{appId}  
</select>

其他内容不变, 打印结果如下:

AppTestEntity{id=null, appName='公共应用', appCode='common', authType='2', createDate=2023-10-31, creator='admin2', appStatus='0', authTypeDict=DictTest{dictName='手机号', dictCode='2', dictType='app_auth_type', dictSort=2}, appStatusDict=null, services=[ServiceTestEntity{id=1, serviceName='配置中心', serviceCode='config-center-service', servicePath='/config', appId=2}, ServiceTestEntity{id=2, serviceName='注册中心', serviceCode='eureka-service', servicePath='/eureka', appId=2}]}

collection集合类型的简单查询

<association>标签一样, <collection>标签也支持嵌套select和单个sql映射, 以上为嵌套select映射, 下面介绍单个sql映射
collections标签的普通查询

  1. mapper接口添加新方法
  2. 映射文件添加sql
  3. 测试类
AppTestEntity queryAppServices(Integer id);
<resultMap id="appServices" type="appTestEntity" autoMapping="true">  
	<!--注意,这里的id一定要配置清楚,会通过这个id来判定app的唯一性(去重)-->
    <id column="id" property="id"/>  
    <collection property="services" ofType="serviceTestEntity" autoMapping="true">  
        <id column="service_id" property="id"/>  
    </collection>
</resultMap>  
<select id="queryAppServices" resultMap="appServices">  
    select t1.*,t2.id as service_id,t2.service_name,t2.service_code,t2.service_path,t2.app_id  
    from app_test t1 left join service_test t2 on t1.id=t2.app_id where t1.id=#{id}  
</select>
public class SimpleQueryService extends MybatisService<SimpleQueryMapper>{  
    @Override  
    public void doService() {  
        SimpleQueryMapper mapper = super.getMapper(SimpleQueryMapper.class);  
        this.testAppServices(mapper);  
    }
    
	private void testAppServices(SimpleQueryMapper mapper){  
	    AppTestEntity detail=mapper.queryAppServices(2);  
	    System.out.println(detail);  
	}
}

打印结果如下:

AppTestEntity{id=2, appName='公共应用', appCode='common', authType='2', createDate=2023-10-31, creator='admin2', appStatus='0', authTypeDict=null, appStatusDict=null, services=[ServiceTestEntity{id=1, serviceName='配置中心', serviceCode='config-center-service', servicePath='/config', appId=2}, ServiceTestEntity{id=2, serviceName='注册中心', serviceCode='eureka-service', servicePath='/eureka', appId=2}]}

collection集合类型的多结果集查询

除了以上方式之外, mybatis还提供基于存储过程的多结果集查询, 即在存储过程中通过执行两个sql获取到对应的查询结果。需要注意的是,这种将业务逻辑代码存储到数据库中的方式在开发过程中应严格控制使用

样例代码如下:

数据库新增存储过程

drop procedure if exists queryAppServices;  
create procedure queryAppServices(appId int) begin  
    select * from app_test where id=appId;  
    select id as service_id,service_code,service_name,service_path,app_id from service_test where app_id=appId;  
end;

mapper层添加新方法

//注意:只存在一个基本类型包装类的参数时,可以不用@Param注解说明参数名称,可直接使用形参的名称
AppTestEntity queryAppServicesProcedure(Integer id);
<resultMap id="appServices" type="appTestEntity" autoMapping="true">  
    <id column="id" property="id"/>  
    <!--这里表明resulteSet名称,和select中标注的resultSets内的名称对应-->
    <collection property="services" ofType="serviceTestEntity" resultSet="services" autoMapping="true">  
        <id column="service_id" property="id"/>  
    </collection>
</resultMap>

<!--重点是标注resultSet名称,和存储过程中的两个查询语句对应-->
<select id="queryAppServicesProcedure" resultSets="app,services" resultMap="appServices" >  
    {call queryAppServices(#{id, jdbcType=INTEGER, mode=IN})}  
</select>

测试类代码

private void testAppServicesProcedure(SimpleQueryMapper mapper){  
    AppTestEntity detail=mapper.queryAppServicesProcedure(2);  
    System.out.println(detail);  
}

打印结果

AppTestEntity{id=2, appName='公共应用', appCode='common', authType='2', createDate=2023-10-31, creator='admin2', appStatus='0', authTypeDict=null, appStatusDict=null, services=[ServiceTestEntity{id=1, serviceName='配置中心', serviceCode='config-center-service', servicePath='/config', appId=2}, ServiceTestEntity{id=2, serviceName='注册中心', serviceCode='eureka-service', servicePath='/eureka', appId=2}]}

以上的resultMap标签可以等价写成如下两个,效果相同, 注意select标签的resultMap属性需要同步修改

<resultMap id="appServiceCall" type="appTestEntity" autoMapping="true">  
    <id column="id" property="id"/>  
    <collection property="services" resultSet="services" resultMap="serviceCall"/>  
</resultMap>  
<resultMap id="serviceCall" type="serviceTestEntity" autoMapping="true">  
    <id column="service_id" property="id"/>  
</resultMap>

总结

在这篇文章中,我们认识了Mybatis中 <association>标签和 <collection>标签的基本用法,这两个标签是mybatis复杂类型结果映射的基础。灵活使用它们可以使我们开发的代码更加清晰易懂,能够在一定程度上减少业务SQL过长的现象。

经过今天的学习,在这篇文章中提出的疑问3也得到了解决


📩 联系方式
邮箱:qijilaoli@foxmail.com

❗版权声明
本文为原创文章,版权归作者所有。未经许可,禁止转载。更多内容请访问奇迹老李的博客首页

  • 9
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
MyBatis提供了多种方式来处理复杂的结果集映射。以下是一些常见的技术和方法: 1. 嵌套查询(Nested Queries:可以使用嵌套来处理一对一、一对多和多对多的关联关系。通过定义嵌套的resultMap,可以将结果集映射到包含嵌套对象的复杂数据结构中。 2. 关联查询(Association):通过使用association标签,可以将关联对象映射到主对象中。这适用于一对一和多对一的关联关系。 3. 集合查询(Collection):通过使用collection标签,可以将集合对象映射到主对象中。这适用于一对多和多对多的关联关系。 4. resultMap继承(ResultMap Inheritance):可以通过定义一个基础的resultMap,并在子resultMap中使用继承关系来重用已定义的映射规则。这样可以减少重复的配置。 5. 枚举类型映射(Enum Mapping):如果结果集中包含枚举类型的字段,可以使用类型处理器或者自定义的类型处理器来将其映射为对应的Java枚举类型。 6. 自定义映射器(Custom Mappers):如果默认的映射方式无法满足需求,可以通过自定义映射器来实现复杂的结果集映射。自定义映射器可以通过实现ResultMapResolver接口来定义自己的映射规则。 这些只是一些常见的技术和方法,MyBatis在结果集映射方面提供了很多灵活的功能和选项,可以根据具体的需求选择适合的方式来处理复杂的结果集映射

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

李奇技

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值