本文已参与「新人创作礼」活动,一起开启掘金创作之路。
在实际开发中我们一般情况下,结果映射有以下三种:
map作为结果映射
<select id="selectUsers" resultType="map">
select id, username, password
from User
where id = #{id}
</select>
resultType作为结果映射
<select id="selectUsers" resultType="User">
select
user_id as "id",
user_name as "userName",
hashed_password as "hashedPassword"
from User
where id = #{id}
</select>
<select id="selectUsers" resultType="com.evader.mapper.User">
select id, username, password
from User
where id = #{id}
</select>
resultMap作为结果映射
<resultMap id="userResultMap" type="User">
<id property="id" column="user_id" />
<result property="username" column="user_name"/>
<result property="password" column="user_password"/>
</resultMap>
<select id="selectUsers" resultMap="userResultMap">
select user_id, user_name, user_password
from User
where id = #{id}
</select>
小结:实际开发中一般不采用第一种方式,将返回的结果映射到map的key上。简单的结果通常是映射到POJO上,复杂的一般使用自己定义的JavaBean。使用resultType时注意点就是需要用as起别名将数据库的列名与JavaBean的字段名对应上。使用resultMap的SQL语句中也可以使用as起别名,只要将别名配置到标签的column属性上即可。
一, 结果映射
有很多子标签,实话说,平时我用不到这么多。
- resultMap
-
- constructor:用于实例化类时,注入结果到构造方法中
-
- idArg:用于标记主键,提高性能
- arg:被注入到构造方法中的参数
- id:表中的主键,提高整体性能
- result:注入到字段或JavaBean上的某个字段
- association:注入到某个对象类型
-
- 嵌套结果查询:该标签下可以嵌套标签,或是对其他结果映射的引用
- collection:注入到一个集合类型上
-
- 嵌套结果查询:该标签下可以嵌套标签,或是对其他结果映射的引用
- discriminator:使用结果值来决定使用哪个resultMap
-
- case:基于某些值的结果映射
-
- 嵌套结果映射:该标签下可以是对其他结果映射的引用
二, id&result
一个简单的resultMap
<resultMap id="BaseResultMap" type="com.evader.generator.pojo.Goods">
<id column="goods_id" jdbcType="INTEGER" property="goodsId" />
<result column="goods_name" jdbcType="VARCHAR" property="goodsName" />
<result column="goods_type_id" jdbcType="INTEGER" property="goodsTypeId" />
<result column="goods_sales" jdbcType="INTEGER" property="goodsSales" />
<result column="shop_id" jdbcType="INTEGER" property="shopId" />
<result column="pic_url" jdbcType="VARCHAR" property="picUrl" />
</resultMap>
这是最常见的子标签,其中是映射表里的主键,一般在第一个位置。是用来映射表中其他属性。当JavaBean是自己构建或表没有主键时那么就不存在这个标签。
三,构造方法
在实际开发中我们声明的JavaBean通常都是作为结果映射的类。一般是不会定义构造函数的,在Java中默认会提供一个无参的构造函数。你好奇给你返回的对象是如何生成的吗?实际是Mybatis调用默认的无参构造函数创建的对象。(我盲猜的)如果你提供了有参构造函数,就像这样:
public class User {
public User(Integer id, String username, int age) {
//TODO
}
}
这个时候User类就不再有默认的无参构造了,所以需要告诉Mybatis用那个构造函数创建对象。这个机制在Mybatis中使用的是标签。
<constructor>
<idArg column="id" javaType="int"/>
<arg column="username" javaType="String"/>
<arg column="age" javaType="_int"/>
</constructor>
这里的类似于标签下的,类似于。需要注意的是,写标签时尽量顺序与构造方法参数的顺序一致,尽管Mybatis在3.4.3做了说明,可以随意放置参数的位置。
三,对象类型映射
3. 1 关联(association)
标签的作用是映射类中的对象类型,比如这篇博客的有一个帅气的作者,这个时候Blog类中就应该有一个属性是User类型的。对于简单类型之前的文章已经提到过,Mybatis是会自动去推断的。如果是对象类型,那么Mybatis是无法推断的,所以需要你告诉Mybatis博客与作者如何关联。Mybatis的关联方式有两种:
- 嵌套查询:通过查询两个sql,以其中一个sql的运行结果来加载另一个sql中的对象属性
- 嵌套结果映射:使用一个sql语句,将嵌套的结果映射来处理连接结果的重复子集
该标签的属性如下:
| property | JavaBean的属性名。简单写可写成username,复杂的可以使用点分法写成user.username |
|---|---|
| javaType | 当前字段的全限定类名/别名,如果映射到JavaBean上,Mybatis会自动推断,如果使用HashMap作为映射对象,那么就应该明确javaType的类型 |
| jdbcType | Mybatis并不对这个属性做类型处理,开发者只需要在可能执行插入、更新和删除的且允许空值的列上指定 JDBC 类型 |
| typeHandler | 类型处理器 |
| column | 对应表中的列名,如果是复合主键,可以写成column="{prop1=col1,prop2=col2}",这样就可以传递多个参数给指定的select查询语句 |
| select | 用于加载其他sql语句的属性,一般用在映射对象属性的映射语句的ID。它会从column属性中获取查询出来的数据,然后将此数据传递给目标select语句,如果是复合主键,可以写成column="{prop1=col1,prop2=col2}",这样就可以传递多个参数给指定的select查询语句 |
| fetchType | 有效值为 lazy 和 eager。 指定属性后,将在映射中忽略全局配置参数 lazyLoadingEnabled,使用属性的值。 |
| resultMap | 与select属性差不多,是select的替代方案,它可以引入一个其他的,实现的嵌套。也称"串联"结果映射 |
| columnPrefix | 当一个类中有两个属性是同一种类型切该属性是对象类型时,可以使用它,复用参考下方的例子 |
| notNullColumn | 默认情况下,在至少一个被映射到属性的列不为空时,子对象才会被创建。 你可以在这个属性上指定非空的列来改变默认行为,指定后,Mybatis 将只在这些列非空时才创建一个子对象。可以使用逗号分隔来指定多个列。 |
| autoMapping | 如果设置这个属性,MyBatis 将会为本结果映射开启或者关闭自动映射。 这个属性会覆盖全局的属性 autoMappingBehavior。注意,本属性对外部的结果映射无效,所以不能搭配 select 或 resultMap 元素使用。 |
3.2 关联的嵌套查询
示例:
// 该类中种有个属性是User对象
@Data
public class BlogDto {
private Integer blogId;
private String blogName;
private User user;
}
<resultMap id="blogResult" type="com.evader.pojo.vo.BlogDto">
<result property="blogId" column="blog_id"/>
<result property="blogName" column="blog_name"/>
<association property="user" column="user_id" javaType="user" select="selectUser"/>
</resultMap>
<select id="selectBlog" resultMap="blogResult">
select * from Blog where blog_id = #{id}
</select>
<select id="selectUser" resultType="user">
select * from User where id = #{id}
</select>
这个示例包含两个查询语句,一个是查询博客(Blog)表的,一个是查询作者(User)表的。这个示例的执行流程是先执行查询博客的语句,这时就能查到blog_id,blog_name,user_id三个字段,由于关联标签的存在,此时会把user_id这个属性当成参数传递给selectUser这个查询语句,selectUser查询到作者后将作者映射到中。
这种方式虽然容易理解,但在数据量打的时候性能很差。这就是常说的"N+1 查询问题"。假设上述案例没有where条件。那么其中+1就是单独使用一条sql来获取博客。N就是博客列表中所有user_id都执行了一个select查询语句。 虽然Mybatis能够实现延迟加载,但是架不住数据量多与遍历,所以有了另一种解决方式。
3.3 关联的嵌套结果映射
示例:使用实现嵌套查询
<resultMap id="blogResultSecond" type="com.evader.pojo.vo.BlogDto">
<result property="blogId" column="blog_id"></result>
<result property="blogName" column="blog_name"/>
<association property="user" column="user_id" javaType="User" resultMap="selectUser"/>
</resultMap>
<resultMap id="selectUser" type="User">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="password" column="password"/>
</resultMap>
<select id="selectBlogSecond" resultMap="blogResultSecond">
select *
from Blog b
left join USER u
on b.user_id = u.id
where blog_id = #{id}
</select>
这种方式采用嵌套的结果映射,并且将嵌套的独立出来,方便复用。如果你的映射文件中不需要复用时,你可以写成这样:
<resultMap id="blogResultPlus" type="com.evader.pojo.vo.BlogDto">
<result property="blogId" column="blog_id"/>
<result property="blogName" column="blog_name"/>
<association property="user" javaType="User">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="password" column="password"/>
</association>
</resultMap>
关联的多结果集
拓展:如果一个博客有多个作者的情况下,如何映射结果集呢?
@Data
public class BlogDtoPlus {
private Integer blogId;
private String blogName;
private User user;
private User anotherUser;
}
<resultMap id="selectBlogDtoPlus" type="com.evader.pojo.vo.BlogDtoPlus">
<result property="blogId" column="blog_id"/>
<result property="blogName" column="blog_name"/>
<association property="user" resultMap="selectUser"/>
<association property="anotherUser" resultMap="selectUser" columnPrefix="another_"/>
</resultMap>
<select id="selectBlogDtoPlus" resultMap="selectBlogDtoPlus">
select
B.blog_id,
B.blog_name,
A.id,
A.name,
A.password,
C.id as another_id,
C.name as another_name,
C.password as another_password
from Blog B
left join User A on b.user_id = a.id
left join User C on b.another_user_id = c.id
where blog_id = #{id}
</select>
既然有两个User那么就使用两个同样的关联标签即可,需要在其中一个标签上加上columnPrefix属性,用于区分两个User对象的具体字段。属性的值就是统一的前缀了,看具体的sql就明了。
四 集合
4.1 集合
标签与标签差不多是一样的,后者是用来映射对象属性的,前者是用来映射集合属性的的。与对象属性一样,Mybatis有两种解决方法:
- 嵌套查询:通过查询两个sql,以其中一个sql的运行结果来加载另一个sql
- 嵌套结果映射:使用一个sql语句,将嵌套的结果映射来处理连接结果的重复子集
4.2 集合的嵌套Select查询
集合的嵌套查询与对象的嵌套查询的设计思想是差不多的,与其相比集合的嵌套多了两个注意点,第一个是ofType属性,这个属性的值是集合中存储的对象类型。另一个是javaType,它的值需要配成ArrayList。Mybatis有推断类型的功能,所以javaType这个属性是可以不写的,就像下面这样:
public class UserDto {
private Integer id;
private String name;
private String password;
private List<Blog> blogList;
}
<resultMap id="selectUserDto" type="UserDto">
<result property="id" column="id"/>
<result property="name" column="name"/>
<result property="password" column="password"/>
<collection property="blogList" column="id" ofType="Blog" select="selectBlog"/>
</resultMap>
<select id="selectUserDTo" resultMap="selectUserDto">
select * from User where id = #{id}
</select>
<select id="selectBlog" parameterType="integer" resultType="com.evader.pojo.Blog">
select blog_id as blogId,blog_name as blogName
,user_id as userId,another_user_id as anotherUserId from Blog where user_id = #{id}
</select>
4.3 集合的嵌套结果映射
集合的嵌套结果映射与对象的映射是差不多的,与嵌套select查询的区别在于它用一条sql就查出来所有的数据,并且全部映射到一个结果集里面,就像下面这样:
<resultMap id="selectUserDToSecond" type="UserDto">
<result property="id" column="id"/>
<result property="name" column="name"/>
<result property="password" column="password"/>
<collection property="blogList" ofType="blog" >
<id property="blogId" column="blog_id"/>
<result property="blogName" column="blog_name"/>
<result property="userId" column="user_id"/>
</collection>
</resultMap>
<select id="selectUserDToSecond" resultMap="selectUserDToSecond">
select u.id,u.name,u.password,b.blog_id,b.blog_name,b.user_id,b.another_user_id
from User u left join Blog b on u.id = b.user_id
where id = #{id}
</select>
如果你想提取可重用的部分,那么你可以参考对象的方式,使用嵌套的方式来写。这里就不做示例了。