文章目录
mybatis-3中文文档
高级结果映射
一个博客
只会关联一个作者
;
一个博客
中可以有多个文章
;
一个文章
可以有多个评论
;(暂时不考虑父子级评论)
一个文章
也可以有多个标签
;
关系如下图
<!-- 非常复杂的结果映射 -->
<resultMap id="detailedBlogResultMap" type="Blog">
<constructor>
<idArg column="blog_id" javaType="int"/>
</constructor>
<result property="title" column="blog_title"/>
<association property="author" javaType="Author">
<id property="id" column="author_id"/>
<result property="username" column="author_username"/>
<result property="password" column="author_password"/>
<result property="email" column="author_email"/>
<result property="bio" column="author_bio"/>
<result property="favouriteSection" column="author_favourite_section"/>
</association>
<!-- 一个博客 有 多个文章 -->
<collection property="posts" ofType="Post">
<id property="id" column="post_id"/>
<result property="subject" column="post_subject"/>
<!-- 一个文章 只能有 一个作者 -->
<association property="author" javaType="Author"/>
<!-- 一个文章 可以有 多个评论 -->
<collection property="comments" ofType="Comment">
<id property="id" column="comment_id"/>
</collection>
<!-- 一个文章 可以有 多个标签 -->
<collection property="tags" ofType="Tag" >
<id property="id" column="tag_id"/>
</collection>
<discriminator javaType="int" column="draft">
<case value="1" resultType="DraftPost"/>
</discriminator>
</collection>
</resultMap>
<!-- 非常复杂的语句 -->
<select id="selectBlogDetails" resultMap="detailedBlogResultMap">
select
B.id as blog_id,
B.title as blog_title,
B.author_id as blog_author_id,
A.id as author_id,
A.username as author_username,
A.password as author_password,
A.email as author_email,
A.bio as author_bio,
A.favourite_section as author_favourite_section,
P.id as post_id,
P.blog_id as post_blog_id,
P.author_id as post_author_id,
P.created_on as post_created_on,
P.section as post_section,
P.subject as post_subject,
P.draft as draft,
P.body as post_body,
C.id as comment_id,
C.post_id as comment_post_id,
C.name as comment_name,
C.comment as comment_text,
T.id as tag_id,
T.name as tag_name
from Blog B <!-- 博客 -->
left outer join Author A on B.author_id = A.id <!-- 博客作者 -->
left outer join Post P on B.id = P.blog_id <!-- 博客文章 -->
left outer join Comment C on P.id = C.post_id <!-- 文章评论 -->
left outer join Post_Tag PT on PT.post_id = P.id<!-- 文章标签关系表 -->
left outer join Tag T on PT.tag_id = T.id <!-- 文章标签 -->
where B.id = #{id}
</select>
先看如果使用连接查询,它们的数据大致可以如下描述,可以看出来mybatis提供的强大的数据封装能力:
文章A 评论1 (A 有评论1、评论2、评论3)
文章A 评论1
文章A 评论2
文章B 评论4 (B 有评论1)
文章B 评论4
Post{ List<Comment> comments }
-------------------------
文章A 评论1 标签a (A 有评论1、评论2、评论3;标签a、标签b)
文章A 评论1 标签b
文章A 评论2 标签a
文章A 评论2 标签b
文章A 评论3 标签a
文章A 评论3 标签b
文章B 评论4 标签a (B 有评论1;标签a、标签b)
文章B 评论4 标签b
Post{ List<Comment> comments, List<Tag> tags }
-------------------------
博客Ⅰ 文章A 评论1 (博客Ⅰ 有文章A、文章B;文章A 有评论1、评论2、评论3)
博客Ⅰ 文章A 评论2
博客Ⅰ 文章A 评论3
博客Ⅰ 文章B 评论4
博客Ⅱ 文章C 评论5 (博客Ⅱ 有文章C;文章C 有评论5、评论6)
博客Ⅱ 文章C 评论6
Blog{ List<Post> posts},Post{ List<Comment> comments}
-------------------------
博客Ⅰ 文章A 评论1 标签a (博客Ⅰ 有文章A、文章B;文章A 有评论1、评论2、评论3;文章A 有标签a、标签b)
博客Ⅰ 文章A 评论1 标签b
博客Ⅰ 文章A 评论2 标签a
博客Ⅰ 文章A 评论2 标签b
博客Ⅰ 文章A 评论3 标签a
博客Ⅰ 文章A 评论3 标签b
博客Ⅰ 文章B 评论4 标签a
博客Ⅰ 文章B 评论4 标签b
博客Ⅱ 文章C 评论5 标签a (博客Ⅱ 有文章C;文章C 有评论5、评论6;文章C 有标签a、标签b))
博客Ⅱ 文章C 评论6 标签a
Blog{ List<Post> posts},Post{ List<Comment> comments, List<Tag> tags }
association
关联的嵌套 Select 查询
查询指定id的博客,并且查询出这个博客的作者
。
<select id="selectAuthor" resultType="Author">
SELECT * FROM AUTHOR WHERE ID = #{id}
</select>
<resultMap id="blogResult" type="Blog">
<!-- 以column为条件, 使用另外一条sql去作查询(上面那条) -->
<!-- 1. 补充:这里的column可以写成:column="{prop1=col1,prop2=col2}"形式,
将prop1和prop2作为另外一条sql查询的查询参数,其中prop1的值取得是当前查询的col1列的值,prop2的值同理 -->
<!-- 2. 但这样会造成N+1的问题:本来只要查一次就完了,可是这样,就对每条结果都要来这么一次“关联的嵌套 Select 查询”.
mybatis里面可以使用延迟加载, 即设置 fetchType="lazy", 当需要用到数据时,才会发起关联查询的语句,
但是,如果查完之后要遍历的话,那就没啥大用的,只不过它这里用了动态代理实现的-->
<association property="author" column="author_id" javaType="Author" select="selectAuthor"/>
</resultMap>
<select id="selectBlog" resultMap="blogResult">
SELECT * FROM BLOG WHERE ID = #{id}
</select>
关联的嵌套结果映射
依然是:查询指定id的博客,并且查询出这个博客的作者
。
但是mybatis给出了另外一种映射方案,我们可以使用连接查询的方式,并且修改映射方式。
<resultMap id="blogResult" type="Blog">
<id property="id" column="blog_id" />
<result property="title" column="blog_title"/>
<association property="author" javaType="Author">
<id property="id" column="author_id"/>
<result property="username" column="author_username"/>
<result property="password" column="author_password"/>
<result property="email" column="author_email"/>
<result property="bio" column="author_bio"/>
</association>
</resultMap>
<select id="selectBlog" resultMap="blogResult">
select
B.id as blog_id,
B.title as blog_title,
B.author_id as blog_author_id,
A.id as author_id,
A.username as author_username,
A.password as author_password,
A.email as author_email,
A.bio as author_bio
from
Blog B
left outer join Author A on B.author_id = A.id
where
B.id = #{id}
</select>
<!-- 我们也可以如下这样写, 目的是让这个id=“authorResult”的resultMap能够复用,但感觉没啥必要~
<resultMap id="authorResult" type="Author">
<id property="id" column="author_id"/>
<result property="username" column="author_username"/>
<result property="password" column="author_password"/>
<result property="email" column="author_email"/>
<result property="bio" column="author_bio"/>
</resultMap>
<resultMap id="blogResult" type="Blog">
<id property="id" column="blog_id" />
<result property="title" column="blog_title"/>
<association property="author" column="blog_author_id" javaType="Author" resultMap="authorResult"/>
</resultMap>
-->
collection
集合的嵌套 Select 查询
查询指定id的博客,并且查询该博客下的所有文章
<select id="selectPostsForBlog" resultType="Post">
SELECT * FROM POST WHERE BLOG_ID = #{id}
</select>
<resultMap id="blogResult" type="Blog">
<!-- 以column为条件, 使用另外一条sql去作查询(上面那条) -->
<!-- 其中,有一个“ofType” 属性。这个属性非常重要,它用来将 JavaBean(或字段)属性的类型和集合存储的类型区分开,
读作: “posts 是一个存储 Post 的 ArrayList 集合” -->
<!-- 在一般情况下,MyBatis 可以推断 javaType 属性,因此并不需要填写 -->
<!-- 1. 补充:这里的column可以写成:column="{prop1=col1,prop2=col2}"形式,
将prop1和prop2作为另外一条sql查询的查询参数,其中prop1的值取得是当前查询的col1列的值,prop2的值同理 -->
<!-- 2. 但这样会造成N+1的问题:本来只要查一次就完了,可是这样,就对每条结果都要来这么一次“关联的嵌套 Select 查询”.
mybatis里面可以使用延迟加载, 即设置 fetchType="lazy", 当需要用到数据时,才会发起关联查询的语句,
但是,如果查完之后要遍历的话,那就没啥大用的,只不过它这里用了动态代理实现的-->
<collection property="posts" javaType="ArrayList"
column="id" ofType="Post" select="selectPostsForBlog"/>
</resultMap>
<select id="selectBlog" resultMap="blogResult">
SELECT * FROM BLOG WHERE ID = #{id}
</select>
集合的嵌套结果映射
依然是:查询指定id的博客,并且查询该博客下的所有文章
。
但是mybatis给出了另外一种映射方案,我们可以使用连接查询的方式,并且修改映射方式。
<resultMap id="blogResult" type="Blog">
<id property="id" column="blog_id" />
<result property="title" column="blog_title"/>
<collection property="posts" ofType="Post">
<id property="id" column="post_id"/>
<result property="subject" column="post_subject"/>
<result property="body" column="post_body"/>
</collection>
</resultMap>
<select id="selectBlog" resultMap="blogResult">
select
B.id as blog_id,
B.title as blog_title,
B.author_id as blog_author_id,
P.id as post_id,
P.subject as post_subject,
P.body as post_body,
from
Blog B
left outer join Post P on B.id = P.blog_id
where
B.id = #{id}
</select>
<!-- 我们也可以如下这样写, 目的是让这个id=“blogResult”的resultMap能够复用,但感觉没啥必要~
<resultMap id="blogResult" type="Blog">
<id property="id" column="blog_id" />
<result property="title" column="blog_title"/>
<collection property="posts" ofType="Post" resultMap="blogPostResult" columnPrefix="post_"/>
</resultMap>
<resultMap id="blogPostResult" type="Post">
<id property="id" column="id"/>
<result property="subject" column="subject"/>
<result property="body" column="body"/>
</resultMap>
-->
注意点
虽然这种方式避免了N+1的问题,但是我们应当注意到,当我们需要对主表,比如说Blog表,进行分页查询时,这个时候,使用上面的集合的嵌套结果映射
就有问题了。假设我们使用的PageHelper分页插件,一个Blog查出来,它会对应好多个文章,也就是数据库中的一条Blog记录,但是这条Blog记录连接查询后的结果,由于一对多的关系,形成了多条Blog记录(一个Blog记录对应多个文章),那这里这么分页肯定就不对了(除非你又对他按博客id分组,但是又只能对关联的文章作聚合函数查询),因此,这种情况,只能用上面的集合的嵌套Select查询
,因为PageHelper是把分页信息存储在ThreadLocal中,并且只会在开启分页后第一次执行查询时才会拼接分页sql,因此,后面的嵌套Select查询就没分页了。
注意点补充
如果不使用分页插件,使用手动分页也是可以做的,做法如下:
文章只能有一个分类,可以有多个标签,分类和标签都不存在上下级关系。以 categoryId 和 tagId为条件分页查询文章,并且将文章关联到的所有标签给查询出来(这文章关联到的标签会是一个集合),因为里面是个集合,所以并不能直接就使用分页插件来玩。可以考虑使用下面的手动分页,先按条件查询出满足条件的文章(此时已完成分页),再使用关联查询出所有标签
<select id="listArticleBacks" resultMap="articleBackResultMap">
SELECT
a.id,
article_cover,
article_title,
type,
is_top,
a.is_delete,
a.status,
a.create_time,
category_name,
t.id AS tag_id,
t.tag_name
FROM
(
SELECT
id,
article_cover,
article_title,
type,
is_top,
is_delete,
status,
create_time,
category_id
FROM
tb_article
<where>
is_delete = #{condition.isDelete}
<if test="condition.keywords != null">
and article_title like concat('%',#{condition.keywords},'%')
</if>
<if test="condition.status != null">
and status = #{condition.status}
</if>
<if test="condition.categoryId != null">
and category_id = #{condition.categoryId}
</if>
<if test="condition.type != null">
and type = #{condition.type}
</if>
<if test="condition.tagId != null">
and id in
(
SELECT
article_id
FROM
tb_article_tag
WHERE
tag_id = #{condition.tagId}
)
</if>
</where>
ORDER BY
is_top DESC,
id DESC
LIMIT #{current},#{size}
) a
LEFT JOIN tb_category c ON a.category_id = c.id
LEFT JOIN tb_article_tag atg ON a.id = atg.article_id
LEFT JOIN tb_tag t ON t.id = atg.tag_id
ORDER BY
is_top DESC,
a.id DESC
</select>
RBAC权限Collection嵌套查询示例
有如下表:
sys_user_info 用户表
sys_user_role 用户-角色关系表
sys_role 角色表
sys_role_menu 角色-菜单关系表
sys_menu 菜单表
- 一个用户,可以有多个角色,一个角色可以关联多个菜单(菜单即权限)。
- 那么对于一个用户来说,它可以有多个角色,它也可以有多个菜单(通过角色关联到多个菜单)
- 那么,可以确定这样的关系:
UserPermDTO{ Integer userInfoId , List<String> roles,List<String> perms}
数据说明:
- 有2个用户
- userInfoId为1的用户,有admin的角色和user的角色;
- userInfoId为2的用户,有user的角色
- admin的角色有:user:list, role:list, menu:list 这3个权限
- user的角色有: role_menu:list 这1个权限
现要求查询所有人,并且每个人的角色集合和菜单(权限标识符)集合,如下:
UserInfoMapper.xml
<resultMap id="userPpermDTOMap" type="com.zzhua.blog.config.security.UserPermDTO">
<id property="userInfoId" column="user_info_id"/>
<collection property="roles" ofType="java.lang.String">
<constructor>
<arg column="role_label"/>
</constructor>
</collection>
<collection property="perms" ofType="java.lang.String">
<constructor>
<arg column="perm"/>
</constructor>
</collection>
</resultMap>
<select id="listPermsForUser" resultMap="userPpermDTOMap">
SELECT
ui.id AS user_info_id,
r.role_label,
m.`perm`
FROM
sys_user_info ui
LEFT JOIN sys_user_role ur1 ON ui.id = ur1.user_info_id
INNER JOIN sys_role r ON ur1.role_id = r.id
LEFT JOIN sys_user_role ur2 ON ui.id = ur2.user_info_id
LEFT JOIN sys_role_menu rm ON ur2.role_id = rm.role_id
INNER JOIN sys_menu m ON rm.menu_id = m.id
WHERE
ui.id = #{userInfoId}
</select>
UserInfoMapper
public interface UserInfoMapper extends BaseMapper<UserInfoEntity> {
List<UserPermDTO> listPermsForUser(@Param("userInfoId") Integer userInfoId);
}
BootBlogApplicationTests
@RunWith(SpringRunner.class)
@SpringBootTest(classes = BootBlogApplication.class)
public class BootBlogApplicationTests {
@Autowired
private UserInfoMapper userInfoMapper;
@Test
public void test002() {
List<UserPermDTO> userPermDTOS = userInfoMapper.listPermsForUser();
System.out.println(userPermDTOS);
}
}