Mybatis基于注解的结果映射

发布时间:2023年12月28日

专栏精选

引入Mybatis

Mybatis的快速入门

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

mapper映射的参数和结果

Mybatis复杂类型的结果映射

摘要

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

引言

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

正文

首图

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

注解xml等价形式描述
@Results<resultMap>映射一系列的字段和属性
@Result<result>,<id>映射一个字段和属性
@ConstructorArgs<constructor>将结果映射到对象的构造方法
@Arg<arg>,<idArg>构造方法的一个参数
@One<association>一对一关联
@Many<collection>一对多关联
@Insert<insert>指定新增的sql
@Delete<delete>指定删除的sql
@Update<update>指定更新的sql
@Select<select>指定查询的sql
@InsertProvider<insert>指定新增的sql provider
@DeleteProvider<delete>指定删除的sql provider
@UpdaateProvider<update>指定更新的sql provider
@SelectProvider<select>指定查询的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

<resultMap>标签一样,@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方法使其能够达成 <association> 标签的效果

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

<collection>的效果改动如下

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映射文件

<?xml version="1.0" encoding="UTF-8" ?>  
<!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文件中的<select id="" resultType=""> 属性的作用是完全不同的,@ResultType注解的作用是在mapper类中的方法中传入了 ResultHandler<?> 参数时,说明结果查询结果的类型。
这时,如果不使用@ResultType说明结果类型,ResultHandler也就无法使用了。它的用法如下:

@ResultType(ApplicationEntity.class)  
@Select("select id, app_name, app_code, auth_type, create_date, creator, app_status from app_test where id=#{id}")  
void selectAppEntityType(Long id, ResultHandler<ApplicationEntity> handler);

测试代码:

private void testAnnoType(){  
    this.mapper.selectAppEntityType(2L, new ResultHandler<ApplicationEntity>() {  
        @Override  
        public void handleResult(ResultContext<? extends ApplicationEntity> resultContext) {  
            ApplicationEntity entity = resultContext.getResultObject();  
            System.out.println(entity);  
        }  
    });  
}

打印结果如下:

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

关于 @MapKey的应用

基于注解可以实现一个xml映射文件中不支持的功能,即将列表返回值转换成一个Map,其中Map的key可以通过 @MapKey注解控制,实现方法如下:

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

测试代码

private void testMapKey(){  
    Map<Long, ApplicationEntity> map = this.mapper.selectAppListMap(2L);  
    map.entrySet().forEach(o->{  
        System.out.println(o.getKey()+":=> "+o.getValue());  
    });  
}

打印结果如下

2:=> ApplicationEntity{id=2, name='公共应用1', code='common', auth='2', birthday=2023-10-31, creator='admin2', status='0'}
5:=> ApplicationEntity{id=5, name='名称1', code='code-1', auth='2', birthday=2023-11-03, creator='admin3', status='0'}
6:=> ApplicationEntity{id=6, name='name1', code='code-2', auth='2', birthday=2023-11-04, creator='admin3', status='0'}
7:=> ApplicationEntity{id=7, name='jack liu', code='code-3', auth='2', birthday=2023-11-04, creator='admin3', status='0'}

总结

在这片文章中,我们介绍了Mybatis基于注解方式的结果映射,Mybatis的结果映射的注解主要有以下几个

  1. @Result和@Results
  2. @ConstructorArgs和@Arg
  3. @One和@Many
  4. @ResultMap和@ResultType
  5. @MapKey

其中,@MapKey还能将查询结果重新构建为一个HashMap,这在xml映射文件中是不支持的操作。


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

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

文章来源:https://blog.csdn.net/qq_43408971/article/details/135260756
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。