有了分类后,就可以做博客管理,博客新增、查询,删除,编辑修改,搜索博客功能,重点是mybatis的多表查询

这里引用oneStar的内容,不详细描述,具体可看大佬的内容。

并不是全部按顺序步骤写的记录,只是以这种顺序写,内容结构比较好排版。

一、搭建好对应的基础结构:

  • dao包下的BlogDao接口,BlogDao.xml
  • service包下的BlogService 接口和impl包下的BlogServiceImpl接口实现类
  • admin包下的BlogController控制器

如果对下面的Controller觉得不理解,可以再编写之前,先细看一遍前端页面,对了解建立哪些接口,哪些功能有帮助。


二、博客列表查询

在查询文章列表的时候,前端页面需要显示分类名称,但博客数据表没有分类字段,这时候需要构建DTO/VO实体类。

  1. entity 里的每一个字段,与数据库相对应,
  2. vo 里的每一个字段,是和你前台 html 页面相对应,
  3. dto 这是用来转换从 entity 到 vo,或者从 vo 到 entity 的中间的东西 。(DTO中拥有的字段应该是entity中或者是vo中的一个子集)


com.xx包下创建queryvo包,创建BlogQuery查询列表实体类,根据前端需要查询的内容来定义变量,有:主键(id)、标题(title)、更新时间(updateTime)、是否推荐(recommend)、是否发布(published)、分类id(typeId)、分类(type)

代码如下:(省去get、set、toString、构造)

public class BlogQuery {
    //根据前端 需要查询的内容来定义变量
    /*分类->id,名字; 博客id,标题,推荐,草稿or发布,修改时间*/
    private Long id;
    private String title;
    private Date updateTime;
    private boolean recommend;
    private boolean published;
    private Long typeId; //选分类要用的
    private Type type;
}
  1. 在BlogDao下添加接口
//查询文章管理列表
List<BlogQuery> getAllBlogQuery();
  1. 重点)mapper文件的多表查询
<!--查询文章管理 列表,查出指定内容。分类名称需要联表查询-->
    <select id="getAllBlogQuery" resultMap="blog">
        select b.id,
               b.title,
               b.update_time,
               b.recommend,
               b.published,
               b.type_id,
               tt.id,
               tt.name
        from t_blog b
                 left join t_type tt on tt.id = b.type_id
        order by b.update_time desc;
    </select>

    <!--type="com.hxj.queryvo.BlogQuery"-->
    <resultMap id="blog" type="BlogQuery">
        <!--    id 属性是 resultMap 的唯一标识      -->
        <!--column数据库中的字段, property实体类的属性-->
        <id property="id" column="id"/>
        <result property="title" column="title"/>
        <result property="updateTime" column="update_time"/>
        <result property="recommend" column="recommend"/>
        <result property="published" column="published"/>
        <result property="typeId" column="type_id"/>
        <association property="type" javaType="Type">
            <id property="id" column="id"/>
            <result property="name" column="name"/>
        </association>
    </resultMap>

在mybatis配置了驼峰转换(而且BlogQuery中的字段除了驼峰和同名外,没有与数据库又差别),实际上resultMap 只需要association多对一映射,一个复杂类型的关联。(mybatis多表查询知识,不在此讲述。)

复杂的属性,需要单独处理
    对象 : association  (多个学生 关联 一个老师)--》多/一个博客对应着一个分类
    集合 : collection (老师有多个学生)--》一个分类又多篇博客

- List<T> 集合用collection,集合中的泛型信息, 我们使用ofType获取。
- javaType=" " 指定实体类中属性的类型。
  1. 在BlogService接口下添加
//查询文章管理列表
List<BlogQuery> getAllBlog();
  1. 在BlogServiceImpl接口实现类添加
@Autowired
private BlogDao blogDao;

//查询文章管理列表
@Override
public List<BlogQuery> getAllBlogQuery() {
    return blogDao.getAllBlogQuery();
}
  1. 在admin包下的BlogController类中添加
    (都是使用pageHelper分页查询,此后不再强调)
@Autowired
private BlogService blogService;
@Autowired
private TypeService typeService;

//博客列表
@RequestMapping("/blogs")
public String blogs(Model model, @RequestParam(defaultValue = "1",value = "pageNum")Integer pageNum){
    //按照排序字段, 倒序
    String orderBy = " update_time desc";
    PageHelper.startPage(pageNum, 5,orderBy);
    List<BlogQuery> list = blogService.getAllBlogQuery();
    PageInfo<BlogQuery> pageInfo = new PageInfo<>(list);//封装 查询信息

    model.addAttribute("types",typeService.getAllType());
    model.addAttribute("pageInfo",pageInfo);

    return "admin/blogs";
}

三、搜索博客列表

搜索是使用的MySQL的模糊查询,根据博客标题和博客分类查询出想要搜索的文章,需要创建有标题分类属性的实体类做vo查询

queryvo包下创建SearchBlog实体类(省去get、set、toString、构造)

public class SearchBlog {
    private String title;
    private  Long typeId;
}
  1. 在BlogDao下添加接口:
//查询文章(传searchBlog , 返回的是 BlogQuery 列表)
    List<BlogQuery> searchByTitleAndType(SearchBlog searchBlog);
  1. mapper文件
<!--搜索博客列表: 复用 blog的 resultMap(解决命名冲突),因为结果 类型是 QueryBlog
    有 传title 就拼; 有传typeId也拼上-->
    <select id="searchByTitleAndType" parameterType="SearchBlog" resultMap="blog">
        select  b.id,
                b.title,
                b.update_time,
                b.recommend,
                b.published,
                b.type_id,
                t.id,
                t.name
        from t_blog b,t_type t
        <where>
            <if test="1 == 1">
               and b.type_id = t.id
            </if>
            <if test="typeId != null">
                and b.type_id = #{typeId}
            </if>
            <if test="title != null">
                and b.title like concat('%',#{title},'%')
            </if>
        </where>
    </select>
  1. 在BlogService接口下添加:
//查询文章(传searchBlog , 返回的是 BlogQuery 列表)
    List<BlogQuery> searchByTitleAndType(SearchBlog searchBlog);
  1. 在BlogService接口实现类添加:
@Override
    public List<BlogQuery> searchByTitleAndType(SearchBlog searchBlog) {
        return blogDao.searchByTitleAndType(searchBlog);
    }
  1. 在admin包下的BlogController类中添加:
//搜索结果 也需要分页
    @PostMapping("/blogs/search")
    public  String search(SearchBlog searchBlog,Model model,
                          @RequestParam(defaultValue = "1",value = "pageNum" ) Integer pageNum){

        //前端送来的 title 和 typeId 组成searchBlog, 查出来 QueryBlog 列表,和展示 全部文章操作类似
        String orderBy = " update_time desc "; //按照排序字段, 倒序

        PageHelper.startPage(pageNum, 5,orderBy);
        List<BlogQuery> blogBySearch = blogService.searchByTitleAndType(searchBlog);  //顺序不能乱

        PageInfo<BlogQuery> pageInfo = new PageInfo<>(blogBySearch);//封装查询结果
        model.addAttribute("pageInfo",pageInfo);
        return "admin/blogs :: blogList";  //刷新 查询结果页面的 thymeleaf片段

    }

分页处理,兼容了搜索

  1. 修改了一下前端

主要使用到这3个隐藏域

springboot重写run springboot重写开源方法_java


定义要更新区域成一个fragment:

springboot重写run springboot重写开源方法_spring boot_02

分页部分:

<div class="ui container">
...
.....
.....
<div class="two wide column" align="center">
    <a class="item" style="cursor: pointer" onclick="page(this)" th:attr="data-page=1" th:unless="${pageInfo.isFirstPage}">首页</a>
</div>

<div class="two wide column" align="center">
    <!--<a class="item" th:href="@{/admin/blogs(pageNum=${pageInfo.hasPreviousPage}?${pageInfo.prePage}:1)}" th:unless="${pageInfo.isFirstPage}">上一页</a>-->
    <a class="item" style="cursor: pointer" onclick="page(this)" th:attr="data-page=${pageInfo.hasPreviousPage}?${pageInfo.prePage}:1" th:unless="${pageInfo.isFirstPage}">上一页</a>
</div>

<div class="eight wide column" align="center">
    <p>第 <span th:text="${pageInfo.pageNum}"></span> 页,共 <span th:text="${pageInfo.pages}"></span> 页,有 <span th:text="${pageInfo.total}"></span> 篇文章</p>
</div>

<div class="two wide column " align="center">
    <!--<a class="item" th:href="@{/admin/blogs(pageNum=${pageInfo.hasNextPage}?${pageInfo.nextPage}:${pageInfo.pages})}" th:unless="${pageInfo.isLastPage}">下一页</a>-->
    <a class="item" style="cursor: pointer" onclick="page(this)" th:attr="data-page=${pageInfo.hasNextPage}?${pageInfo.nextPage}:${pageInfo.pages}" th:unless="${pageInfo.isLastPage}">下一页</a>
</div>

<div class="two wide column " align="center">
    <a class="item" style="cursor: pointer" onclick="page(this)" th:attr="data-page=${pageInfo.pages}" th:unless="${pageInfo.isLastPage}">尾页</a>
</div>
    
</div>

JS部分

$('#clear-btn')
    .on('click', function() {
    $('.ui.type.dropdown')
        .dropdown('clear')
    ;
})
;

function page(obj) {
    $("[name='page']").val($(obj).data("page"));
    loaddata();
}

$("#search-btn").click(function () {
    $("[name='page']").val(0);
    loaddata();
});
function loaddata() {
    $("#table-container").load(/*[[@{/admin/blogs/search}]]*/
        "/admin/blogs/search",{
            title : $("[name='title']").val(),
            typeId : $("[name='typeId']").val(),
            pageNum : $("[name='page']").val()
        });
}

这样其实点跳转页的时候,就相当于 点击了搜索。(输入搜索条件后,直接点击下一页也能够实现条件查询)

即使没有输入条件,也传的是空字符串,等于全查询。(实现兼容效果)

  1. 后端控制器接口

就是搜索博客的Controller,实现了分页和搜索功能。

四、博客新增

新增不需要构建VO, 使用entity即可

  1. 在BlogDao下添加接口
//保存新增博客
    int saveBlog(Blog blog);
  1. mapper文件
<!--保存新增博客,id自增,不写也行,-->
    <insert id="saveBlog" parameterType="Blog">
        insert into t_blog (appreciation, commentabled, content, create_time, description, first_picture, flag,
                            published, recommend, share_statement, title, update_time, views, type_id, user_id,
                            comment_count)
        values (#{appreciation}, #{commentabled}, #{content}, #{createTime}, #{description}, #{firstPicture}, #{flag},
                #{published}, #{recommend}, #{shareStatement}, #{title}, #{updateTime}, #{views}, #{typeId}, #{userId},
                #{commentCount})
    </insert>
  1. 在BlogService接口下添加
//保存新增博客
    int saveBlog(Blog blog);
  1. 在BlogServiceImpl接口实现类添加
@Override
    public int saveBlog(Blog blog) {
        blog.setCreateTime(new Date());
        blog.setUpdateTime(new Date());
        blog.setViews(0);
        blog.setCommentCount(0);
        //其余前端可以传
        return blogDao.saveBlog(blog);
    }

前端传来的字段比实体类的少了一点,需要在这里补充。

  1. 在admin包下的BlogController类中添加

这里的后端校验不再解释,和分类的后端校验同理。(此后的一样)

到entity类中,对要校验的字段加注解即可。(可翻我的分类管理博客),记得到前端增加要显示的校验字段的错误显示。

例如我的:

springboot重写run springboot重写开源方法_web_03


springboot重写run springboot重写开源方法_web_04

//跳转到新增页面
    @RequestMapping("/blogs/input")
    public  String input(Model model){

        model.addAttribute("blog",new Blog()); //传博客对象
        model.addAttribute("types",typeService.getAllType());  //传分类信息

        return "admin/blogs-input";
    }

    //提交 新增(没有传blog id)
    @PostMapping("/blogs")
    public String post(@Valid Blog blog, BindingResult result, RedirectAttributes attributes, HttpSession session,Model model){

        if(result.hasErrors()){
            //校验出来,发现问题,把message传前端
            return "admin/blogs-input";
        }

        //新增需要传递blog对象, 里面需要 有 user,分类
        blog.setUser((User) session.getAttribute("user"));
        blog.setType(typeService.getType( blog.getType().getId() ));
        //设置blog中的typeId属性,设置用户id
        blog.setTypeId(blog.getType().getId());
        blog.setUserId(blog.getUser().getId());



        int k = blogService.saveBlog(blog);
        if(k>0){
            attributes.addFlashAttribute("message","新增成功");
        }else {
            attributes.addFlashAttribute("message","新增失败");
        }

        return "redirect:/admin/blogs";
    }

前端传来的字段比实体类的少了一点,需要在此补充。

五、博客编辑

为了简化查询,单独创建博客显示类BlogShow类,查询出需要编辑的博客信息。(在新增时使用Blog类,但是编辑时(一些东西不需要更新),只需要将初始化过的Blog部分属性抽出来修改即可,创建一个VO是比较合理的。)

queryvo包下创建ShowBlog实体类(省去get、set、toString、构造)

public class ShowBlog {
    private Long id;
    private String title;
    private String content;
    private String description;
    private String firstPicture;

    private String flag; //是否为原创
    private boolean recommend;
    private boolean published;
    private boolean shareStatement;
    private boolean commentabled;
    private boolean appreciation;

    private Date updateTime;//更新时间
    private Long typeId;
}
  1. 在BlogDao下添加接口
//编辑博客
    int updateBlog(ShowBlog showBlog);

    //查询编辑修改的文章
    ShowBlog getBlogById(Long id);
  1. mapper文件
<!--只查出 ShowBlog的数据(除了updateTime)-->
    <select id="getBlogById" resultMap="ShowBlog">
        select id,
               appreciation,
               commentabled,
               content,
               description,
               first_picture,
               flag,
               published,
               recommend,
               share_statement,
               title,
               type_id
        from t_blog
        where id = #{id};
    </select>

    <resultMap id="ShowBlog" type="ShowBlog">
        <!--column数据库中的字段, property实体类的属性-->
        <!--    哪里不一致就写哪里就行 -->
        <result column="first_picture" property="firstPicture"/>
        <result column="share_statement" property="shareStatement"/>
        <result column="type_id" property="typeId"/>

    </resultMap>

当时还没配置驼峰转换,所以写了resultMap,实际配置后可以不需要写

  1. 在BlogService接口下添加
//编辑博客
    int updateBlog(ShowBlog showBlog);

    //查询编辑修改的文章
    ShowBlog getBlogById(Long id);
  1. 在BlogServiceImpl接口实现类添加
@Override
    public ShowBlog getBlogById(Long id) {
        return blogDao.getBlogById(id);
    }

    @Override
    public int updateBlog(ShowBlog showBlog) {
        showBlog.setUpdateTime( new Date());
        return blogDao.updateBlog(showBlog);
    }
  1. 在admin包下的BlogController类中添加
//跳转到 修改博客
    @GetMapping("/blogs/{id}/input")
    public String editInput(@PathVariable Long id,Model model){
        ShowBlog blogById = blogService.getBlogById(id);
        System.out.println(blogById.toString());

        List<Type> allType = typeService.getAllType();


        model.addAttribute("blog",blogById);
        model.addAttribute("types",allType); //分类列表选择


        return "admin/blogs-input";
    }

    //提交编辑修改的为文章
    @PostMapping("/blogs/{id}")
    public String editPost(ShowBlog showBlog,RedirectAttributes attributes){

        int k = blogService.updateBlog(showBlog);
        if(k>0){
            attributes.addFlashAttribute("message","修改成功");
        }else{
            attributes.addFlashAttribute("message","修改失败");
        }

        return "redirect:/admin/blogs";
    }

六、博客删除

删除只需要根据博客Id来访问对应的控制器即可

  1. 在BlogDao下添加接口
//删除博客
    void deleteBlog(Long id );
  1. mapper文件
<!--删除博客-->
    <delete id="deleteBlog">
        delete
        from t_blog
        where id = #{id};
    </delete>
  1. 在BlogService接口下添加
//删除博客
    void deleteBlog(Long id);
  1. 在BlogServiceImpl接口实现类添加
@Override
    public void deleteBlog(Long id) {
        blogDao.deleteBlog(id);
    }
  1. 在admin包下的BlogController类中添加
//删除博客
    @GetMapping("/blogs/{id}/delete")
    public String delete(@PathVariable Long id,RedirectAttributes attributes){
        blogService.deleteBlog(id);
        attributes.addFlashAttribute("message","删除成功");
        return "redirect:/admin/blogs";
    }

至此博客管理功能完成,这部分创建了3个VO实体类, BlogQuery(博客列表),SearchBlog(博客搜索),ShowBlog(编辑博客)。

接下来的友链、相册就比较简单了,业务不算复杂和分类很像。