1. resultType
resultType用于配置返回值类型。
使用别名或者全类名,如果返回的是集合,书写为集合中元素的类型,mybatis会把每条结果封装为一个对象,并放入集合中,不能和resultMap同时使用。
返回某个实体类对象:
<select id="selectOneById" resultType="user">
select
*
from base_user where user_id = #{collection[0]}
</select>
返回集合:
<select id="getAllUser" resultType="org.pearl.mybatis.demo.pojo.entity.User">
SELECT * FROM base_user
</select>
返回单个Map:
返回Map时,只需要指定resultType为map即可,返回的map会以数据库字段为key,值为value。
Map<String,Object> selectOneById(Long id);
<!--根据ID查询用户-->
<select id="selectOneById" resultType="map">
select
*
from base_user where user_id = #{id}
</select>
返回结果示例:
{password=$2a$12$/V9KqbnIRWuyzUsfDmADR.urue.m750mgiTsYYR5Ut19U0tsbOd3y, login_name=Angel, gender=0, user_id=1, user_name=Angel-bo, organization_id=1, remark=, state=true, create_date=2018-11-06 06:20:56.0, modify_date=2018-11-06 06:20:56.0}
返回多个Map:
比如多条数据封装为Map,以主键为key,每条数据对象为value时,只需要使用@MapKey指定key即可。
@MapKey(value = "user_id")
Map<Long,User> getAllUser();
<select id="getAllUser" resultType="map">
SELECT * FROM base_user
</select>
2. resultMap
在原生的JDBC中,映射结果集时,需要判断是有值,然后获取值,再封装到返回对象中。
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT user_name, age FROM imooc_goddess");
//如果有数据,rs.next()返回true
while(rs.next()){
System.out.println(rs.getString("user_name")+" 年龄:"+rs.getInt("age"));
}
resultMap的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。
2.1 自动结果映射
全局setting设置中, autoMappingBehavior默认是PARTIAL,开启自动映射的功能。唯一的要求是列名和javaBean属性名一致。
自动映射配置项:
<setting name="autoMappingBehavior" value="PARTIAL"/>
如果autoMappingBehavior设置为null则会取消自动映射。
如果数据库字段命名规范,比如使用字母加下划线_,POJO属性符合驼峰命名法,可以开启自动驼峰命名规则映射功能。
mapUnderscoreToCamelCase=true。
在这些情况下,MyBatis 会在幕后自动创建一个 ResultMap,再根据属性名来映射列到 JavaBean 的属性上。如果列名和属性名不能匹配上,可以在 SELECT 语句中设置列别名来完成匹配。
示例:
<select id="selectUsers" resultType="User">
select
user_id as "id",
user_name as "userName",
hashed_password as "hashedPassword"
from some_table
where id = #{id}
</select>
有三种自动映射等级:
- NONE - 禁用自动映射。仅对手动映射的属性进行映射。
- PARTIAL - 对除在内部定义了嵌套结果映射(也就是连接的属性)以外的属性进行映射。
- FULL - 自动映射所有属性。
默认值是 PARTIAL,因为当对连接查询的结果使用 FULL 时,连接查询会在同一行中获取多个不同实体的数据,因此可能导致非预期的映射。
2.2 高级结果映射
如果数据库字段和返回结果类无法对应上时,可以使用HashMap接受, 但是HashMap并不是一个很好的领域模型,应该使用 JavaBean 或 POJO作为领域模型。
数据库不可能永远是你所想或所需的那个样子。 我们希望每个数据库都具备良好的第三范式或 BCNF 范式,可惜它们并不都是那样。 如果能有一种数据库映射模式,完美适配所有的应用程序,那就太好了,但可惜也没有。 而 ResultMap 就是 MyBatis 对这个问题的答案。使用ResultMap可以自定义结果集映射,实现复杂结果集对象封装。
resultMap高级结果映射将结果联合映射到目标实体中。这里的目标实体通常包含其他实体对象、集合等元素。常用在一对一,一对多、多对一、多对多的关系表中。
下面是resultMap 元素的概念视图:
- constructor - 用于在实例化类时,注入结果到构造方法中(当创建实体类时需要传构造参数时使用)
- idArg -
ID参数;标记出作为ID的结果可以帮助提高整体性能 - arg - 将被注入到构造方法的一个普通结果
- idArg -
- id – 一个 主键
ID结果;标记出作为键ID的结果可以帮助提高整体性能(标示主键id,可以提高性能) - result – 注入到字段或
JavaBean属性的普通结果,数据库字段和JavaBean的对应关系(映射普通字段) - association – 一个复杂类型的关联;许多结果将包装成这种类型(映射实体类内部包含的其他实体类)
- 嵌套结果映射 – 关联可以是
resultMap元素,或是对其它结果映射的引用
- 嵌套结果映射 – 关联可以是
- collection – 一个复杂类型的集合(映射实体类中包含的集合)
- 嵌套结果映射 – 集合可以是
resultMap元素,或是对其它结果映射的引用
- 嵌套结果映射 – 集合可以是
- discriminator – 使用结果值来决定使用哪个
resultMap(根据判断条件,有选择的映射)- case – 基于某些值的结果映射
- 嵌套结果映射 –
case也是一个结果映射,因此具有相同的结构和元素;或者引用其它的结果映射
- 嵌套结果映射 –
- case – 基于某些值的结果映射
ResultMap 的属性列表:
| 属性 | 描述 |
|---|---|
id | 当前命名空间中的一个唯一标识,用于标识一个结果映射。 |
type | 类的完全限定名, 或者一个类型别名(关于内置的类型别名,可以参考上面的表格)。 |
autoMapping | 如果设置这个属性,MyBatis 将会为本结果映射开启或者关闭自动映射。 这个属性会覆盖全局的属性 autoMappingBehavior。默认值:未设置(unset)。 |
比如连表查询时,结果集没有对应的实体类进行封装,那么只能返回Map,但是Map并不规范也不好使用,一般都会定义PO对象,封装各种复杂类型,用于复杂对象封装。
比如连表查询用户表及角色表,此时返回的对象应该定义如下结构(一对一,实际应该是一对多):
@Data
@ToString
public class UserInfoPo {
private Long userId;
private String userName;
private String loginName;
private Integer gender;
private String phone;
private String address;
private Integer organizationId;
private Boolean state;
private String email;
// 角色信息
private Role role;
}
2.2.1 级联属性
Mapper接口声明方法:
UserInfoPo getUserInfoById(Long userId);
编写XML,在resultMap中指定数据库列名和返回对象的映射关系:
<!--查询某个用户的信息及角色信息-->
<select id="getUserInfoById" resultMap="userAndRole">
SELECT
base_user.* , base_role.*
FROM
base_user
LEFT JOIN base_user_role ON ( base_user.user_id = base_user_role.user_id )
LEFT JOIN base_role ON (base_user_role.role_id = base_role.role_id )
WHERE base_user.user_id = #{ userId }
</select>
<resultMap id="userAndRole" type="org.pearl.mybatis.demo.pojo.po.UserInfoPo">
<!--role_id字段的值,赋值给UserInfoPo对象role属性对象的roleId字段-->
<result column="role_id" property="role.roleId"/>
<result column="role_name" property="role.roleName"/>
</resultMap>
执行查询,返回了复杂结果集,并按照resultMap配置的规则进行了映射:
2.2.2 关联对象
可以使用association标签,指定某个属性的关联对象。association 中需要指定result,否则内部无法自动映射。
<resultMap id="userAndRole" type="org.pearl.mybatis.demo.pojo.po.UserInfoPo" autoMapping="true">
<id property="userId" column="user_id"/>
<association property="role" javaType="org.pearl.mybatis.demo.pojo.entity.Role">
<id column="role_id" property="roleId"/>
<result column="role_name" property="roleName"/>
</association>
</resultMap>
2.2.3 关联的嵌套查询
某些多表关联查询情况下,可以使用分步查询,比如查询用户及用户机构,可以先查询出用户,再根据机构ID查询机构信息,分步操作,这种方式还可以实现懒加载功能。
association嵌套 Select 查询属性:
| 属性 | 描述 | |
|---|---|---|
column | 数据库中的列名,或者是列的别名。 | |
select | 用于加载复杂类型属性的映射语句的 ID,它会从 column 属性指定的列中检索数据,作为参数传递给目标 select 语句。注意:在使用复合主键的时候,你可以使用 column="{prop1=col1,prop2=col2}这样的语法来指定多个传递给嵌套 Select 查询语句的列名。这会使得 prop1 和prop2 作为参数对象,被设置为对应嵌套 Select 语句的参数。 | |
fetchType | 可选的。有效值为 lazy 和 eager。 指定属性后,将在映射中忽略全局配置参数 lazyLoadingEnabled,使用属性的值。 |
示例: 首先添加根据机构ID查询机构信息,创建实体类:
@Data
public class Organization implements Serializable {
private static final long serialVersionUID = 1L;
private Integer organizationId;
private String organizationName;
private String organizationRemark;
private Integer organizationParentId;
private Integer organizationType;
private Integer createUserId;
private Date createDate;
private Integer modifyUserId;
private Date modifyDate;
private Integer state;
private String showOrganizationName;
}
接口及XML:
Organization getOrgById(Long id);
<select id="getOrgById" resultType="org.pearl.mybatis.demo.pojo.entity.Organization">
SELECT * FROM base_organization WHERE organization_id = #{id}
</select>
添加分步查询用户信息包含机构信息,首先添加实体类:
@Data
@ToString
public class UserInfoPo {
private Long userId;
private String userName;
private String loginName;
private Integer gender;
private String phone;
private String address;
private Integer organizationId;
private Boolean state;
private String email;
/*
// 角色信息
private Role role;*/
// 机构信息
private Organization organization;
}
映射文件:
UserInfoPo getUserInfoById(Long userId);
<select id="getUserInfoById" resultMap="userAndOrg">
SELECT * FROM base_user WHERE base_user.user_id = #{ userId }
</select>
<resultMap id="userAndOrg" type="org.pearl.mybatis.demo.pojo.po.UserInfoPo" autoMapping="true">
<id property="userId" column="user_id"/>
<!--select:调用目标的方法查询当前属性的值-->
<!--column:将指定列的值传入目标方法-->
<association property="organization"
javaType="org.pearl.mybatis.demo.pojo.entity.Role"
select="org.pearl.mybatis.demo.dao.UserMapper.getOrgById"
column="organization_id"/>
</resultMap>
执行查询操作:
MyBatis能够对分步嵌套查询进行延迟加载,当第二步查询的结果集使用时,才会进行查询操作。
全局配置项:
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
fetchType=eager/lazy可以覆盖全局的延迟加载策略,指定立即加载(eager)或者延迟加载(lazy)。
可以看出设置以后,当嵌套对象(机构)未使用时,只执行了查询用户的SQL。
2.2.4 集合类型
对于一对多查询时,一个用户会对应多个角色,那么查询的结果集对象为:
public class UserInfoPo {
// other....
// 角色信息
private List<Role> roles;
}
这种场景需要使用到collection标签:
UserInfoPo getUserInfoById(Long userId);
<!--查询某个用户的信息及角色信息-->
<select id="getUserInfoById" resultMap="userAndRole">
SELECT
base_user.* , base_role.*
FROM
base_user
LEFT JOIN base_user_role ON ( base_user.user_id = base_user_role.user_id )
LEFT JOIN base_role ON (base_user_role.role_id = base_role.role_id )
WHERE base_user.user_id = #{ userId }
</select>
<resultMap id="userAndRole" type="org.pearl.mybatis.demo.pojo.po.UserInfoPo" autoMapping="true">
<id property="userId" column="user_id"/>
<!--collection: 表示集合属性-->
<!--ofType:表示集合中元素的类型-->
<collection property="roles" ofType="org.pearl.mybatis.demo.pojo.entity.Role">
<id property="roleId" column="role_id"/>
<id column="role_name" property="roleName"/>
</collection>
</resultMap>
集合类型分步查询: 可以把集合查询剥离出来为单独的SQL语句,然后通过collection嵌套进别的查询语句中,也可以实现懒加载。
添加集合查询语句,通过用户ID查询出所有的角色信息:
List<Role> getRolesByUserId(Long id);
<select id="getRolesByUserId" resultType="org.pearl.mybatis.demo.pojo.entity.Role">
SELECT
*
FROM
base_role
LEFT JOIN base_user_role ON base_role.role_id = base_user_role.role_id
WHERE
base_user_role.user_id =#{id}
</select>
将第一步的查询语句嵌套到用户信息查询的语句中:
<select id="getUserInfoById" resultMap="userAndRole" >
SELECT * FROM base_user WHERE base_user.user_id = #{ userId }
</select>
<resultMap id="userAndRole" type="org.pearl.mybatis.demo.pojo.po.UserInfoPo" autoMapping="true">
<id property="userId" column="user_id"/>
<collection property="roles" javaType="java.util.List" select="getRolesByUserId" column="user_id"/>
</resultMap>
多列值传递
在分步查询中,嵌套查询语句中,我们只传递了一个参数过column指定,将对应的列的数据传递过去,我们有时需要传递多列数据。这种情况可以通过使用{key1=column1,key2=column2…}的形式。
column="{organization_id=id,organization_name=name}"/>
3. 鉴别器
有时候,一个数据库查询可能会返回多个不同的结果集(但总体上还是有一定的联系的)。 鉴别器(discriminator)元素就是被设计来应对这种情况的,另外也能处理其它情况,例如类的继承层次结构。 鉴别器的概念很好理解——它很像 Java 语言中的 switch 语句。
一个鉴别器的定义需要指定 column 和 javaType 属性。column 指定了 MyBatis 查询被比较值的地方。 而 javaType 用来确保使用正确的相等测试(虽然很多情况下字符串的相等测试都可以工作)。
例如:
<resultMap id="vehicleResult" type="Vehicle">
<id property="id" column="id" />
<result property="vin" column="vin"/>
<result property="year" column="year"/>
<result property="make" column="make"/>
<result property="model" column="model"/>
<result property="color" column="color"/>
<discriminator javaType="int" column="vehicle_type">
<case value="1" resultMap="carResult"/>
<case value="2" resultMap="truckResult"/>
<case value="3" resultMap="vanResult"/>
<case value="4" resultMap="suvResult"/>
</discriminator>
</resultMap>