有了分类后,就可以做博客管理,博客新增、查询,删除,编辑修改,搜索博客功能,重点是mybatis的多表查询。
这里引用oneStar的内容,不详细描述,具体可看大佬的内容。
并不是全部按顺序步骤写的记录,只是以这种顺序写,内容结构比较好排版。
一、搭建好对应的基础结构:
-
dao
包下的BlogDao
接口,BlogDao.xml
-
service
包下的BlogService
接口和impl
包下的BlogServiceImpl
接口实现类 -
admin
包下的BlogController
控制器
如果对下面的Controller觉得不理解,可以再编写之前,先细看一遍前端页面,对了解建立哪些接口,哪些功能有帮助。
二、博客列表查询
在查询文章列表的时候,前端页面需要显示分类名称,但博客数据表没有分类字段,这时候需要构建DTO
/VO
实体类。
- entity 里的每一个字段,与数据库相对应,
- vo 里的每一个字段,是和你前台 html 页面相对应,
- 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;
}
- 在BlogDao下添加接口
//查询文章管理列表
List<BlogQuery> getAllBlogQuery();
- (重点)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=" " 指定实体类中属性的类型。
- 在BlogService接口下添加
//查询文章管理列表
List<BlogQuery> getAllBlog();
- 在BlogServiceImpl接口实现类添加
@Autowired
private BlogDao blogDao;
//查询文章管理列表
@Override
public List<BlogQuery> getAllBlogQuery() {
return blogDao.getAllBlogQuery();
}
- 在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;
}
- 在BlogDao下添加接口:
//查询文章(传searchBlog , 返回的是 BlogQuery 列表)
List<BlogQuery> searchByTitleAndType(SearchBlog searchBlog);
- 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>
- 在BlogService接口下添加:
//查询文章(传searchBlog , 返回的是 BlogQuery 列表)
List<BlogQuery> searchByTitleAndType(SearchBlog searchBlog);
- 在BlogService接口实现类添加:
@Override
public List<BlogQuery> searchByTitleAndType(SearchBlog searchBlog) {
return blogDao.searchByTitleAndType(searchBlog);
}
- 在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片段
}
分页处理,兼容了搜索
- 修改了一下前端
主要使用到这3个隐藏域
定义要更新区域成一个fragment:
分页部分:
<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()
});
}
这样其实点跳转页的时候,就相当于 点击了搜索。(输入搜索条件后,直接点击下一页也能够实现条件查询)
即使没有输入条件,也传的是空字符串,等于全查询。(实现兼容效果)
- 后端控制器接口
就是搜索博客的Controller,实现了分页和搜索功能。
四、博客新增
新增不需要构建
VO
, 使用entity即可
- 在BlogDao下添加接口
//保存新增博客
int saveBlog(Blog blog);
- 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>
- 在BlogService接口下添加
//保存新增博客
int saveBlog(Blog blog);
- 在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);
}
前端传来的字段比实体类的少了一点,需要在这里补充。
- 在admin包下的BlogController类中添加
这里的后端校验不再解释,和分类的后端校验同理。(此后的一样)
到entity类中,对要校验的字段加注解即可。(可翻我的分类管理博客),记得到前端增加要显示的校验字段的错误显示。
例如我的:
//跳转到新增页面
@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;
}
- 在BlogDao下添加接口
//编辑博客
int updateBlog(ShowBlog showBlog);
//查询编辑修改的文章
ShowBlog getBlogById(Long id);
- 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,实际配置后可以不需要写
- 在BlogService接口下添加
//编辑博客
int updateBlog(ShowBlog showBlog);
//查询编辑修改的文章
ShowBlog getBlogById(Long id);
- 在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);
}
- 在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来访问对应的控制器即可
- 在BlogDao下添加接口
//删除博客
void deleteBlog(Long id );
- mapper文件
<!--删除博客-->
<delete id="deleteBlog">
delete
from t_blog
where id = #{id};
</delete>
- 在BlogService接口下添加
//删除博客
void deleteBlog(Long id);
- 在BlogServiceImpl接口实现类添加
@Override
public void deleteBlog(Long id) {
blogDao.deleteBlog(id);
}
- 在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
(编辑博客)。
接下来的友链、相册就比较简单了,业务不算复杂和分类很像。