在Mybatis
中,有一个强大的功能元素resultMap
。当咱们但愿将JDBC ResultSets
中的数据,转化为合理的Java对象时,你就能感觉到它的非凡之处。正如其官方所述的那样:前端
resultMap
元素是MyBatis
中最重要最强大的元素。它可让你从 90% 的JDBC ResultSets
数据提取代码中解放出来,并在一些情形下容许你进行一些 JDBC 不支持的操做。实际上,在为一些好比链接的复杂语句编写映射代码的时候,一份resultMap
可以代替实现同等功能的长达数千行的代码。ResultMap
的设计思想是,对于简单的语句根本不须要配置显式的结果映射,而对于复杂一点的语句只须要描述它们的关系就好了。java
在Mybatis
中,最简单的结果映射方式,就是经过类型别名typeAliases
来处理。数据库
若是要这样作,那么第一步须要配置实体类包的路径:bash
mybatis.type-aliases-package=com.xxx.entity
mybatis
该路径下的全部类,就会被注册到TYPE_ALIASES容器
。咱们在指定返回值类型的时候,就直接用别名便可。ui
好比,咱们有一个User
类:this
@Data
public class User {
private String id;
private String username;
private String password;
private String address;
private String email;
}
复制代码
若是数据库中表的字段与User
类的属性名称一致,咱们就可使用resultType
来返回。url
<select id="getUsers" resultType="User">
SELECT
u.id,
u.username,
u.password,
u.address,
u.email
FROM
USER u
</select>
复制代码
固然,这是理想状态下,属性和字段名都彻底一致的状况。但事实上,不一致的状况是有的,这时候咱们的resultMap
就要登场了。spa
若是User
类保持不变,但SQL
语句发生了变化,将id
改为了uid
。设计
<select id="getUsers" resultType="User">
SELECT
u.id as uid,
u.username,
u.password,
u.address,
u.email
FROM
USER u
</select>
复制代码
那么,在结果集中,咱们将会丢失id
数据。这时候咱们就能够定义一个resultMap
,来映射不同的字段。
<resultMap id="getUserByIdMap" type="User">
<result property="id" column="uid"></result>
</resultMap>
复制代码
而后,咱们把上面的select
语句中的resultType
修改成resultMap="getUserByIdMap"
。
这里面column
对应的是数据库的列名或别名;property
对应的是结果集的字段或属性。
这就是resultMap
最简单,也最基础的用法:字段映射。
下面,咱们看看其余几种标签都是怎么应用的。
元素名称 | 描述 |
---|---|
constructor | 用于在实例化类时,注入结果到构造方法中 |
association | 关联一个对象 |
collection | 关联多个对象 |
若是你但愿将结果注入构造方法里,就能够用到constructor
元素。
好比,咱们的User
类增长了一个构造方法:
public User(String id, String name) {
this.id = id+"--------";
this.username = name+"--------";
}
复制代码
咱们须要在resultMap
中定义constructor
元素:
<resultMap id="getUserByIdMap" type="User">
<constructor>
<idArg column="id" name="id" javaType="string"></idArg>
<arg column="username" name="name" javaType="string"></arg>
</constructor>
</resultMap>
复制代码
其中,column
表明数据库字段名称或者别名;name
则是构造方法中的参数名称;javaType
指定了参数的类型。
如你所想,这样指定构造方法后,咱们结果集中的id和username
属性都会发生变化。
{
"id": "1001--------",
"username": "后羿--------",
"password": "123456",
"address": "北京市海淀区",
"email": "510273027@qq.com"
}
复制代码
在实际的业务中,咱们的用户通常都会有一个角色。那么在User
类里面通常也是以一个实体类来表示。
@Data
public class User {
//省略用户属性...
//角色信息
private Role role;
}
复制代码
咱们在查询用户的时候,若是也但愿看到它的角色信息,咱们会这样来写查询语句:
<select id="getUserById" resultType="User">
SELECT
u.id,
u.username,
u.password,
u.address,
u.email,
r.id as 'role_id',
r.name as 'role_name'
FROM
USER u
LEFT JOIN user_roles ur ON u.id = ur.user_id
LEFT JOIN role r ON r.id = ur.role_id
where u.id=#{id}
</select>
复制代码
如上,就要查询单个用户以及用户的角色信息。不过在这里,咱们不能用resultType=User
来返回。
毕竟,User
类中只有一个Role
对象,并无role_id和role_name
字段属性。
因此,咱们要使用association
来关联它们。
<resultMap id="userMap" type="User">
<id property="id" column="id"></id>
<result property="username" column="username"></result>
<result property="password" column="password"></result>
<result property="address" column="address"></result>
<result property="email" column="email"></result>
<association property="role" javaType="Role">
<id property="id" column="role_id"></id>
<result property="name" column="role_name"></result>
</association>
</resultMap>
复制代码
最后咱们就能够将角色信息一块显示出来:
{
"id": "1001",
"username": "后羿",
"password": "123456",
"address": "北京市海淀区",
"email": "510273027@qq.com",
"role": {
"id": "3",
"name": "射手"
}
}
复制代码
事实上,若是你肯定关联信息是一对一
的状况,有个更简便的方法能够替代association
,咱们在本文的第五部分-自动填充关联对象
再看它是怎么实现的。
上面咱们看到一个用户后羿
,它的角色是射手
;但大部分时候,咱们每一个人都不可能只拥有一种角色。因此,咱们须要将User
类中的角色属性的类型改为List
。
@Data
public class User {
//省略用户属性...
//角色信息
private List<Role> roles;
}
复制代码
如今就变成了一个用户对应多个角色,因此就不是简单的association
。
由于association
处理的是有一个
类型的关联;而咱们这里是有多个
类型的关联,因此就须要用到collection
属性。
咱们总体的resultMap
会变成下面这样:
<resultMap id="userMap" type="User">
<id property="id" column="id"></id>
<result property="username" column="username"></result>
<result property="password" column="password"></result>
<result property="address" column="address"></result>
<result property="email" column="email"></result>
<collection property="roles" ofType="Role">
<id property="id" column="role_id"></id>
<result property="name" column="role_name"></result>
</collection>
</resultMap>
复制代码
这样的话,即使你有多个角色也能够被正确显示:
{
"id": "1003",
"username": "貂蝉",
"password": "123456",
"address": "北京市东城区",
"email": "510273027@qq.com",
"roles": [
{
"id": "1",
"name": "中单"
},
{
"id": "2",
"name": "打野"
}
]
}
复制代码
在大部分业务系统中,咱们都会有一个菜单的表,好比像下面这样,一张Menu
表:
id | name | url | parent_id |
---|---|---|---|
1 | 系统管理 | 0 | |
1001 | 用户管理 | /user | 1 |
1002 | 角色管理 | /role | 1 |
1003 | 单位管理 | /employer | 1 |
2 | 平台监控 | 0 | |
2001 | 系统监控 | /system/monitor | 2 |
2002 | 数据监控 | /data/monitor | 2 |
这里咱们给菜单分为两级。咱们给前端返回菜单的时候,也是须要分级的,不可能将这7条数据平级展现。那么,在这里咱们的Menu
实体类以下:
@Data
public class Menu {
private String id;
private String name;
private String url;
private String parent_id;
private List<Menu> childMenu;
}
复制代码
一级菜单,包含一个二级菜单的列表,这里用childMenu
来表示。
SQL
语句中,若是没有parent_id
字段属性,咱们就先查全部的一级菜单:
<select id="getMenus" resultMap="menusMap">
SELECT
m.id,
m.name,
m.url,
m.parent_id
FROM
m_menu m
where 1=1
<choose>
<when test="parent_id!=null">
and m.parent_id = #{parent_id}
</when>
<otherwise>
and m.parent_id = '0'
</otherwise>
</choose>
</select>
复制代码
这个查询语句,在不传输任何参数的状况下,咱们会获得两条一级菜单的数据。
那么在只调用此方法一次的状况下,怎么把全部的菜单信息查询出来,并按层级展现呢?
咱们来看menusMap
的定义:
<resultMap id="menusMap" type="Menu">
<id property="id" column="id"></id>
<result property="name" column="name"></result>
<result property="url" column="url"></result>
<result property="m_desc" column="m_desc"></result>
<result property="parent_id" column="parent_id"></result>
<collection property="childMenu" ofType="Menu" select="getMenus" column="{parent_id=id}"></collection>
</resultMap>
复制代码
重点来看collection
元素:
property="childMenu"
对应的是菜单中的子级菜单列表;
ofType="Menu"
对应返回数据的类型;
select="getMenus"
指定了SELECT
语句的id;
column="{parent_id=id}"
则是参数的表达式。
这个collection
总体的含义能够这样理解:
经过getMenus
这个SELECT语句
来获取一级菜单中的childMenu
属性结果;在上面的SELECT语句
中,须要传递一个parent_id
参数;这个参数的值就是一级菜单中的id
。
经过这种方式,咱们就能够获得已分级的全部菜单信息。
[
{
"id": "1",
"name": "系统管理",
"parent_id": "0",
"childMenu": [
{
"id": "1001",
"name": "用户管理",
"url": "/user",
"parent_id": "1"
},
{
"id": "1002",
"name": "角色管理",
"url": "/role",
"parent_id": "1"
},
{
"id": "1003",
"name": "单位管理",
"url": "/employer",
"parent_id": "1"
}
]
},
{
"id": "2",
"name": "平台监控",
"parent_id": "0",
"childMenu": [
{
"id": "2001",
"name": "系统监控",
"url": "/system/monitor",
"parent_id": "2"
},
{
"id": "2002",
"name": "数据监控",
"url": "/data/monitor",
"parent_id": "2"
}
]
}
]
复制代码
咱们知道,在Mybatis
解析返回值的时候。
第一步是获取返回值类型,拿到Class
对象,而后获取构造器,设置可访问并返回实例,而后又把它包装成MetaObject
对象。
从数据库rs
中拿到结果以后,会调用MetaObject.setValue(String name, Object value)
来填充对象。
在这过程当中,有趣的是,它会以.
来分隔这个name
属性。
若是name
属性中包含.符号
,就找到.符号
以前的属性名称,把它当作一个实体对象来处理。
可能笔者在这里描述的不够直观,咱们仍是来看例子。
在本文第三部分
中,咱们有一个用户对应一个角色
的例子。
其中,User
类定义以下:
@Data
public class User {
//省略用户属性...
//角色信息
private Role role;
}
复制代码
在这里,咱们无需定义resultMap
,直接返回resultType=User
便可。不过须要把role
信息的别名修改一下,重点是.符号
<select id="getUserList" resultType="User">
SELECT
u.id,
u.username,
u.password,
u.address,
u.email,
r.id as 'role.id',
r.name as 'role.name'
FROM
USER u
LEFT JOIN user_roles ur ON u.id = ur.user_id
LEFT JOIN role r ON r.id = ur.role_id
</select>
复制代码
这样,在Mybatis
解析到role.id
属性的时候,以.符号
分隔以后发现,role
别名对应的是Role
对象,则会先初始化Role
对象,并将值赋予id
属性。
相关代码如图: