Mybatis基于注解的结果映射

专栏精选

引入Mybatis

Mybatis的快速入门

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

mapper映射的参数和结果

Mybatis复杂类型的结果映射

文章目录

  • 专栏精选
  • 摘要
  • 引言
  • 正文
    • 关于 @Results和 @Result的应用
    • 关于 @ConstructorArgs 和 @Arg的应用
    • 关于@One和@Many的关系映射的应用
    • 关于 @ResultMap 和 @ResultType的应用
    • 关于 @MapKey的应用
  • 总结

摘要

在这篇文章中,我们将进入Mybatis注解的世界,了解实现基于注解的结果映射的基本方法,其中的很多观点或内容都能在一定程度上让我们的开发之旅更加轻松方便,这是一个菜鸟提升技术能力,老鸟巩固基础知识的好机会。准备好开启今天的神奇之旅了吗?

引言

大家好,我是奇迹老李,一个专注于分享开发经验和基础教程的博主。这里是我的其中一个技术分享平台,欢迎广大朋友们点赞评论提出意见,重要的是点击关注喔 。今天要和大家分享的内容是基于注解的结果映射。做好准备,Let’s go

正文

首图

在resultMap标签中常用的元素都有对应等价形式的java注解,这在Mybatis官方文档中有明确的说明

注解 xml等价形式 描述
@Results 映射一系列的字段和属性
@Result , 映射一个字段和属性
@ConstructorArgs 将结果映射到对象的构造方法
@Arg , 构造方法的一个参数
@One 一对一关联
@Many 一对多关联
@Insert 指定新增的sql
@Delete 指定删除的sql
@Update 指定更新的sql
@Select 指定查询的sql
@InsertProvider 指定新增的sql provider
@DeleteProvider 指定删除的sql provider
@UpdaateProvider 指定更新的sql provider
@SelectProvider 指定查询的sql provider
@ResultMap 没有 指定使用xml文件中的resultMap
@ResultType 没有 当方法返回值为空时,指定返回值类型
@MapKey 没有 将方法的List返回值,转化为一个Map,其中Map的key就是@MapKey(value="")中的内容

关于 @Results和 @Result的应用

注册一个新的mapper

<configuration>
	<mappers>  
        <mapper resource="mapper/ApplicationMapper.xml"/>  
        <mapper resource="mapper/SimpleQueryMapper.xml"/>  
        <mapper class="top.sunyog.mybatis.mapper.AppAnnoMapper"/>  
    mappers>
configuration>

一个简单的查询sql写法是这样的

public interface AppAnnoMapper {  
    @Select("select id, app_name, app_code, auth_type, create_date, creator, app_status from app_test where id=#{id}")
    Map<String,Object> selectAppEntityById(Long id);  
}

测试类代码:

public class AppAnnoService extends MybatisService<AppAnnoMapper> {  
    private AppAnnoMapper mapper;  
    @Override  
    public void doService() {  
        this.mapper = this.getMapper(AppAnnoMapper.class);  
        this.testSelectResult();  
    }  
  
    private void testSelectResult() {  
        Map<String, Object> res = this.mapper.selectAppEntityById(1L);  
        res.entrySet().forEach(e-> System.out.println(e.getKey()+":=> "+e.getValue()));  
    }  
}

打印输出查询结果如下:

app_name:=> 测试应用1
auth_type:=> 1
creator:=> admin
app_status:=> 8
id:=> 1
create_date:=> 2023-10-31
app_code:=> ceshi

可以通过 @Result注解转换查询结果的属性名称,代码如下:

@Result(column = "app_name",property = "APPLICATION_NAME")  
@Select("select id, app_name, app_code, auth_type, create_date, creator, app_status from app_test where id=#{id}")  
Map<String,Object> selectAppEntityById(Long id);

效果如下,app_name属性的名称已经修改

auth_type:=> 1
creator:=> admin
app_status:=> 8
id:=> 1
create_date:=> 2023-10-31
APPLICATION_NAME:=> 测试应用1
app_code:=> ceshi

如果多个属性的名称都需要重新映射,则使用 @Results

@Results({  
        @Result(id = true,column = "id",property = "APP_ID"),  
        @Result(column = "app_name",property = "APPLICATION_NAME"),  
        @Result(column = "app_code",property = "APPLICATION_CODE"),  
        @Result(column = "auth_type",property = "AUTHENTICATION_TYPE"),  
        @Result(column = "creator",property = "CREAT_BY"),  
        @Result(column = "app_status",property = "STATUS"),  
        @Result(column = "create_date",property = "BIRTHDAY")  
})  
@Select("select id, app_name, app_code, auth_type, create_date, creator, app_status from app_test where id=#{id}")  
Map<String,Object> selectAppEntityById(Long id);

效果如下:

APP_ID:=> 1
STATUS:=> 8
AUTHENTICATION_TYPE:=> 1
CREAT_BY:=> admin
APPLICATION_NAME:=> 测试应用1
BIRTHDAY:=> 2023-10-31
APPLICATION_CODE:=> ceshi

标签一样,@Result 注解也支持自定义类型转换器,写法如下

//mapper接口代码
@Result(column = "create_date",property = "BIRTHDAY",typeHandler = LocalDateTypeHandler.class)

//测试方法代码
private void testSelectResult() {  
    Map<String, Object> res = this.mapper.selectAppEntityById(1L);  
    System.out.println(res.get("BIRTHDAY").getClass());  
}

修改后的打印结果:class java.time.LocalDate
修改前的打印结果:class java.sql.Date

关于 @ConstructorArgs 和 @Arg的应用

这两个注解的作用也是映射结果属性,区别在于 @ConstructorArgs用于通过构造函数映射,而 @Resutls用于setter方法映射。构造函数映射需要注意定义的参数的顺序要和构造函数入参的顺序一致。

  1. 定义实体类
public class ApplicationEntity implements Serializable {  
    private Long id;  
    private String name;  
    private String code;  
    private String auth;  
    private LocalDate birthday;  
    private String creator;  
    private String status;  
  
    public ApplicationEntity( Long id, String name, String code, String auth, LocalDate birthday, String creator  
            , String status) {  
        this.id = id;  
        this.name = name;  
        this.code = code;  
        this.auth = auth;  
        this.birthday = birthday;  
        this.creator = creator;  
        this.status = status;  
    }  
  
    @Override  
    public String toString() {  
        final StringBuffer sb = new StringBuffer("ApplicationEntity{");  
        sb.append("id=").append(id);  
        sb.append(", name='").append(name).append('\'');  
        sb.append(", code='").append(code).append('\'');  
        sb.append(", auth='").append(auth).append('\'');  
        sb.append(", birthday=").append(birthday);  
        sb.append(", creator='").append(creator).append('\'');  
        sb.append(", status='").append(status).append('\'');  
        sb.append('}');  
        return sb.toString();  
    }  
}
  1. 定义映射接口方法
@ConstructorArgs({
//这里的顺序需要和实体类的构造函数入参顺序一致
        @Arg(id = true, column = "id", javaType = Long.class),  
        @Arg(column = "app_name", javaType = String.class),  
        @Arg(column = "app_code", javaType = String.class),  
        @Arg(column = "auth_type", javaType = String.class),  
        @Arg(column = "create_date", javaType = LocalDate.class),  
        @Arg(column = "creator", javaType = String.class),  
        @Arg(column = "app_status", javaType = String.class)  
})  
@Select("select id, app_name, app_code, auth_type, create_date, creator, app_status from app_test where id=#{id}")  
ApplicationEntity selectAppArgsById(Long id);
  1. 测试方法
private void testSelectConstructor(){  
    ApplicationEntity entity = this.mapper.selectAppArgsById(1L);  
    System.out.println(entity);  
}

打印结果如下:

ApplicationEntity{id=1, name='测试应用1', code='ceshi', auth='1', birthday=2023-10-31, creator='admin', status='8'}

这时,如果调换@Arg注解的先后位置,如下

@ConstructorArgs({
        @Arg(id = true, column = "id", javaType = Long.class),  
        @Arg(column = "app_code", javaType = String.class),  
        @Arg(column = "app_name", javaType = String.class),  
        @Arg(column = "auth_type", javaType = String.class),  
        @Arg(column = "create_date", javaType = LocalDate.class),  
        @Arg(column = "creator", javaType = String.class),  
        @Arg(column = "app_status", javaType = String.class)  
})  
@Select("select id, app_name, app_code, auth_type, create_date, creator, app_status from app_test where id=#{id}")  
ApplicationEntity selectAppArgsById(Long id);

就会产生这样的结果,name和code的映射结果是错误的

ApplicationEntity{id=1, name='ceshi', code='测试应用1', auth='1', birthday=2023-10-31, creator='admin', status='8'}

注意:@Arg注解有一个name属性,这个属性在 @ConstructorArgs注解中不能出现,如果出现,构造函数会映射失败导致报错。想要解决这个问题需要在实体类的构造函数中通过 @Param注解指定每个入参的名称和@Arg注解中的name属性一一对应,如下所示:

实体类:

public class ApplicationEntity implements Serializable {  
	//省略属性
    public ApplicationEntity(@Param("id") Long id,@Param("name") String name,@Param("code") String code  
            ,@Param("auth") String auth,@Param("birthday") LocalDate birthday,@Param("creator") String creator  
            ,@Param("status") String status) {
	    ...
    }
    //省略其他方法
}

mapper接口

@ConstructorArgs({  
        @Arg(id = true, column = "id", javaType = Long.class, name = "id"),  
        @Arg(column = "app_code", javaType = String.class, name = "code"),  
        @Arg(column = "app_name", javaType = String.class, name = "name"),  
        @Arg(column = "auth_type", javaType = String.class, name = "auth"),  
        @Arg(column = "create_date", javaType = LocalDate.class, name = "birthday"),  
        @Arg(column = "creator", javaType = String.class, name = "creator"),  
        @Arg(column = "app_status", javaType = String.class, name = "status")  
})  
@Select("select id, app_name, app_code, auth_type, create_date, creator, app_status from app_test where id=#{id}")  
ApplicationEntity selectAppArgsById(Long id);

这样就改变了 ResultMap的映射方式(从按顺序改为按名称),即便修改了@Arg的定义顺序,也能正确的映射到对应的属性上

关于@One和@Many的关系映射的应用

改造AppAnnoMapper#selectAppEntityById方法使其能够达成 标签的效果

public interface AppAnnoMapper {  
    @Results(id = "APP_MAP", value = {  
            @Result(id = true, column = "id", property = "APP_ID"),  
            @Result(column = "app_name", property = "APPLICATION_NAME"),  
            @Result(column = "app_code", property = "APPLICATION_CODE"),  
            @Result(column = "auth_type", property = "AUTHENTICATION_TYPE"),  
            @Result(column = "creator", property = "CREAT_BY"),  
            @Result(column = "app_status", property = "STATUS"),  
            @Result(column = "create_date", property = "BIRTHDAY", typeHandler = LocalDateTypeHandler.class),  
            //添加一个一对一关系,映射到selectAppStatus方法
            @Result(column = "app_status",property = "STATUS_DICT",one = @One(select = "selectAppStatus"))  
    })  
    @Select("select id, app_name, app_code, auth_type, create_date, creator, app_status from app_test where id=#{id}")  
    Map<String, Object> selectAppEntityById(Long id);  

	//添加状态字典查询
    @Select("select dict_name, dict_code, dict_type, dict_sort from dict_test where dict_type='app_status' and dict_code=#{code}")  
    Map<String,Object> selectAppStatus(@Param("code")String code);
}

原方法的打印结果如下,其中STATUS_DICT属性为 @One关联出的结果

APP_ID:=> 2
STATUS:=> 0
STATUS_DICT:=> [{dict_type=app_status, dict_name=临时应用, dict_sort=0, dict_code=0}]
AUTHENTICATION_TYPE:=> 2
CREAT_BY:=> admin2
APPLICATION_NAME:=> 公共应用1
BIRTHDAY:=> 2023-10-31
APPLICATION_CODE:=> common

的效果改动如下

public interface AppAnnoMapper {  
    @Results(id = "APP_MAP", value = {  
            @Result(id = true, column = "id", property = "APP_ID"),  
            @Result(column = "app_name", property = "APPLICATION_NAME"),  
            @Result(column = "app_code", property = "APPLICATION_CODE"),  
            @Result(column = "auth_type", property = "AUTHENTICATION_TYPE"),  
            @Result(column = "creator", property = "CREAT_BY"),  
            @Result(column = "app_status", property = "STATUS"),  
            @Result(column = "create_date", property = "BIRTHDAY", typeHandler = LocalDateTypeHandler.class),  
            @Result(column = "app_status",property = "STATUS_DICT",one = @One(select = "selectAppStatus")),  
            //添加一个一对多关系,映射到selectAppServices方法
            @Result(column = "id",property = "SERVICE_LIST",many = @Many(select = "selectAppServices"))  
    })  
    @Select("select id, app_name, app_code, auth_type, create_date, creator, app_status from app_test where id=#{id}")  
    Map<String, Object> selectAppEntityById(Long id);  
  
    @Select("select id, service_name, service_code, service_path, app_id from service_test where app_id=#{appId}")  
    List<ServiceTestEntity> selectAppServices(Long appId);  
  
    @Select("select dict_name, dict_code, dict_type, dict_sort from dict_test where dict_type='app_status' and dict_code=#{code}")  
    Map<String,Object> selectAppStatus(@Param("code")String code);
}

关于 @ResultMap 和 @ResultType的应用

可以在映射接口中使用xml文件中定义的resultMap,使用 @ResultMap注解实现

配置文件中新增mapper映射文件配置

<configuration>
...
	<mappers>  
        <mapper resource="mapper/ApplicationMapper.xml"/>  
        <mapper resource="mapper/SimpleQueryMapper.xml"/>  
        <mapper class="top.sunyog.mybatis.mapper.AppAnnoMapper"/>  
        <mapper resource="mapper/AppAnnoMapper.xml"/>  
    mappers>
configuration>

新增xml映射文件

  
DOCTYPE mapper  
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"  
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">  
<mapper namespace="top.sunyog.mybatis.mapper.AppAnnoMapper">  
    <resultMap id="applicationEntityResultMap" type="top.sunyog.common.entity.ApplicationEntity">  
        <constructor>            
            <idArg column="id" javaType="Long"/>  
            <arg column="app_name" javaType="String"/>  
            <arg column="app_code" javaType="String"/>  
            <arg column="auth_type" javaType="String"/>  
            <arg column="create_date" javaType="java.time.LocalDate"/>  
            <arg column="creator" javaType="String"/>  
            <arg column="app_status" javaType="String"/>  
        constructor>    
    resultMap>  

mapper>

mapper接口新增测试方法

@ResultMap(value = "applicationEntityResultMap")  
@Select("select id, app_name, app_code, auth_type, create_date, creator, app_status from app_test where id=#{id}")  
ApplicationEntity selectAppEntityMap(Long id);

测试代码

public class AppAnnoService extends MybatisService<AppAnnoMapper> {  
    private AppAnnoMapper mapper;
    ...
	private void testAnnoResultMap(){  
	    ApplicationEntity entity = this.mapper.selectAppEntityMap(2L);  
	    System.out.println(entity);  
	}
}

打印结果如下:

ApplicationEntity{id=2, name='公共应用1', code='common', auth='2', birthday=2023-10-31, creator='admin2', status='0'}

@ResultType注解和 xml文件中的