MyBatis之一对一和一对多及分页问题,实现文章列表+分类列表+标签列表+用户的关联查询

本文主要介绍 MyBatis 一对一和一对多相关使用,还有就是分页的简单介绍。

一、背景

之前我在做文章列表,如下图1这种后台文章列表的时候,通常都是从数据库将文章表查出来,然后遍历往文章列表里填用户信息、分类列表信息、标签信息

MyBatis之一对一和一对多及分页问题,实现文章列表+分类列表+标签列表+用户的关联查询

图1 文章后台图示例图

代码 实现如下

  1. for (Post p : page.getRecords()) {
  2.     //封装分类,标签,用户详细信息
  3.     List<Category> categories = categoryService.findByPostId(p.getId());
  4.     List<Tag> tags = tagService.findByPostId(p.getId());
  5.     User user = userService.get(p.getId());
  6.     p.setCategories(categories);
  7.     p.setTags(tags);
  8.     p.setUser(user);
  9. }

我们发现这种写法,如果按照一页显示10篇文章,需要查询数据库(在不用redis缓存情况下) 1+10+10+10 = 31 次,这个消耗是很多的,光数据库往返消耗就不少。

基于上面的痛点,我们不得不考虑减少数据库查询,即便是关联查询需要关联5张表,一条SQL语句需要查询6张表,我们也要考虑一下。

just do it!

 

二、文章、分类、标签和用户的关系

一篇文章通常只有一个用户,多个分类,多个标签。

直接用 navicat 查看几张表的结构,从左到右,自上而下分别是 用户表、文章表、分类表、标签表、文章和标签关联表、文章和分类关联表

 

文章实体类

Post.java

  1. package com.liuyanzhao.sens.modules.blog.entity;
  2. import com.baomidou.mybatisplus.annotations.TableField;
  3. import com.baomidou.mybatisplus.annotations.TableId;
  4. import com.baomidou.mybatisplus.annotations.TableName;
  5. import com.fasterxml.jackson.annotation.JsonFormat;
  6. import com.liuyanzhao.sens.common.constant.CommonConstant;
  7. import com.liuyanzhao.sens.common.utils.SnowFlakeUtil;
  8. import lombok.Data;
  9. import org.springframework.format.annotation.DateTimeFormat;
  10. import java.util.Date;
  11. import java.util.List;
  12. /**
  13.  * @author 言曌
  14.  */
  15. @Data
  16. @TableName("t_blog_post")
  17. public class Post {
  18.     private static final long serialVersionUID = 1L;
  19.     @TableId
  20.     private String id = String.valueOf(SnowFlakeUtil.getFlowIdInstance().nextId());
  21.     /**
  22.      * 用户ID
  23.      */
  24.     private String userId;
  25.     /**
  26.      * 文章标题
  27.      */
  28.     private String title;
  29.     /**
  30.      * 文章类型
  31.      * post  文章
  32.      * page  页面
  33.      */
  34.     private String type = "post";
  35.     /**
  36.      * 文章内容 html格式
  37.      */
  38.     private String content;
  39.     /**
  40.      * 文章路径
  41.      */
  42.     private String url;
  43.     /**
  44.      * 文章摘要
  45.      */
  46.     private String summary;
  47.     /**
  48.      * 缩略图
  49.      */
  50.     private String thumbnail;
  51.     /**
  52.      * 0 已发布
  53.      * 1 草稿
  54.      * 3 待审核
  55.      * 4 回收站
  56.      */
  57.     private Integer status = 0;
  58.     /**
  59.      * 文章访问量
  60.      */
  61.     private Long views;
  62.     /**
  63.      * 点赞访问量
  64.      */
  65.     private Long likes;
  66.     /**
  67.      * 评论数量(冗余字段,加快查询速度)
  68.      */
  69.     private Long comments;
  70.     /**
  71.      * 文章来源(原创1,转载2,翻译3)
  72.      */
  73.     private Integer source = 1;
  74.     /**
  75.      * 是否允许评论(允许1,禁止0)
  76.      */
  77.     private Integer commentFlag = 1;
  78.     private String createBy;
  79.     @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
  80.     private Date createTime;
  81.     private String updateBy;
  82.     @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
  83.     @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
  84.     private Date updateTime;
  85.     /**
  86.      * 删除标志 默认0正常, 1删除
  87.      */
  88.     private Integer delFlag = CommonConstant.STATUS_NORMAL;
  89.     @TableField(exist = false)
  90.     private List<Category> categories;
  91.     @TableField(exist = false)
  92.     private List<Tag> tags;
  93.     @TableField(exist = false)
  94.     private User user;
  95. }

 

三、Mapper

这里使用Mybatis-plus

PostMapper.java

  1. package com.liuyanzhao.sens.modules.blog.dao.mapper;
  2. import com.baomidou.mybatisplus.mapper.BaseMapper;
  3. import com.liuyanzhao.sens.modules.blog.dto.PageDto;
  4. import com.liuyanzhao.sens.modules.blog.entity.Post;
  5. import org.apache.ibatis.annotations.Param;
  6. import java.util.List;
  7. /**
  8.  * @author 言曌
  9.  * @date 2019-06-19 21:06
  10.  */
  11. public interface PostMapper  extends BaseMapper<Post> {
  12.     /**
  13.      * 根据条件查询
  14.      * @param condition 条件
  15.      * @param pageDto 分页封装类
  16.      * @return 文章列表
  17.      */
  18.     List<Post> findByCondition(@Param("condition") Post condition,
  19.                                @Param("pageDto") PageDto pageDto);
  20. }

 

PostMapper.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  3. <mapper namespace="com.liuyanzhao.sens.modules.blog.dao.mapper.PostMapper">
  4.     <resultMap id="postWithCategoriesAndTags" type="com.liuyanzhao.sens.modules.blog.entity.Post">
  5.         <id column="id" property="id"/>
  6.         <result column="create_by" property="createBy"/>
  7.         <result column="create_time" property="createTime"/>
  8.         <result column="del_flag" property="delFlag"/>
  9.         <result column="update_by" property="updateBy"/>
  10.         <result column="update_time" property="updateTime"/>
  11.         <result column="comment_flag" property="commentFlag"/>
  12.         <result column="comments" property="comments"/>
  13.         <result column="content" property="content"/>
  14.         <result column="likes" property="likes"/>
  15.         <result column="source" property="source"/>
  16.         <result column="status" property="status"/>
  17.         <result column="summary" property="summary"/>
  18.         <result column="thumbnail" property="thumbnail"/>
  19.         <result column="title" property="title"/>
  20.         <result column="type" property="type"/>
  21.         <result column="url" property="url"/>
  22.         <result column="views" property="views"/>
  23.         <result column="user_id" property="userId"/>
  24.         <association property="user" javaType="com.liuyanzhao.sens.modules.base.entity.User">
  25.             <id column="user_id" property="id"/>
  26.             <result column="user_username" property="username"/>
  27.             <result column="user_nickName" property="nickName"/>
  28.             <result column="user_avatar" property="avatar"/>
  29.         </association>
  30.         <collection property="categories" ofType="com.liuyanzhao.sens.modules.blog.entity.Category">
  31.             <id column="category_id" property="id"/>
  32.             <result column="category_title" property="title"/>
  33.         </collection>
  34.         <collection property="tags" ofType="com.liuyanzhao.sens.modules.blog.entity.Tag">
  35.             <id column="tag_id" property="id"/>
  36.             <result column="tag_title" property="title"/>
  37.         </collection>
  38.     </resultMap>
  39.     <!-- 文章主要字段:不包括文章内容  -->
  40.     <sql id="mainPostColumns">
  41.         id, user_id, create_by, create_time, comment_flag, comments,
  42.         likes, source, status, summary, thumbnail, title, `type`, url, views
  43.     </sql>
  44.     <!-- 文章主要字段:不包括文章内容  -->
  45.     <sql id="mainPostColumnsWithTable">
  46.         post.id, post.user_id, post.create_by, post.create_time, post.comment_flag,
  47.         post.comments, post.likes, post.source, post.status, post.summary, post.thumbnail,
  48.         post.title, post.`type`, post.url, post.views
  49.     </sql>
  50.     <select id="findByCondition" resultMap="postWithCategoriesAndTags">
  51.        SELECT <include refid="mainPostColumnsWithTable"/>,
  52.           category.id as category_id, category.title as category_title,
  53.           tag.id as tag_id, tag.title as tag_title
  54.        FROM (
  55.         SELECT <include refid="mainPostColumns"/> FROM t_blog_post
  56.         <where>
  57.             del_flag = 0
  58.             <if test="condition.userId != null and condition.userId != ''">
  59.                 AND user_id = #{condition.userId}
  60.             </if>
  61.             <if test="condition.title != null and condition.title != ''">
  62.                 AND title LIKE CONCAT('%','${condition.title}','%')
  63.             </if>
  64.             <if test="condition.status != null and condition.status != ''">
  65.                 AND status = #{condition.status}
  66.             </if>
  67.             <if test="condition.source != null and condition.source != ''">
  68.                 AND source = #{condition.source}
  69.             </if>
  70.             <if test="searchDate.startDate != null and searchDate.startDate != ''">
  71.                 AND create_time &gt;= #{searchDate.startDate}
  72.             </if>
  73.             <if test="searchDate.endDate != null and searchDate.endDate != ''">
  74.                 AND create_time &lt;= #{searchDate.endDate}
  75.             </if>
  76.         </where>
  77.         LIMIT #{pageDto.from}, #{pageDto.pageSize}) post
  78.        LEFT JOIN t_blog_post_category as post_category ON post.id = post_category.post_id
  79.        LEFT JOIN t_blog_category as category ON post_category.category_id = category.id
  80.        LEFT JOIN t_blog_post_tag as post_tag ON post.id = post_tag.post_id
  81.        LEFT JOIN t_blog_tag as tag ON post_tag.tag_id = tag.id
  82.        LEFT JOIN t_user as user ON post.user_id = user.id
  83.     </select>
  84. </mapper>

 

association 是一对一的关键字,collection是一对多的关键字

 

这里说明一下

一对多会导致分页出现数据缺失,比如我们目前有20篇文章,每篇文章都有1到两个分类和标签,这会导致查出100条数据,然后如果直接LIMIT 0,10 会导致最终的文章表只有三五个记录。

所以我们需要如shang'm'd

  • 微信
  • 交流学习,有偿服务
  • weinxin
  • 博客/Java交流群
  • 资源分享,问题解决,技术交流。群号:590480292
  • weinxin
言曌

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: