开发社区首页
- 开发流程
- 1次请求的执行过程
- 分步实现
- 开发社区首页,显示前10个帖子
- 开发分页组件,分页显示所有的帖子
我们首先开发社区首页,显示帖子,先查看一下存帖子的表,在navicat右边可以查看DDL(Data Definition Language),也就是建表语句,workbench查看的方法自行百度。根据DDL了解一下表的结构。
实体类
首先是实体类,和上一节的操作一样,比较简单。根据数据库字段写好就行。其实有插件可以自动生成实体类和xml,但是在学习阶段,建议自己敲一遍熟悉一下。
package com.neu.langsam.community.entity;
import java.util.Date;
public class DiscussPost {
private int id;
private int userId;
private String title;
private String content;
private int type;
private int status;
private Date createTime;
private int commentCount;
private double score;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public int getCommentCount() {
return commentCount;
}
public void setCommentCount(int commentCount) {
this.commentCount = commentCount;
}
public double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
@Override
public String toString() {
return "DiscussPost{" +
"id=" + id +
", userId=" + userId +
", title='" + title + '\'' +
", content='" + content + '\'' +
", type=" + type +
", status=" + status +
", createTime=" + createTime +
", commentCount=" + commentCount +
", score=" + score +
'}';
}
}
DAO层
实体类写完后,写dao层接口,这里定义了两个方法,一个是查询帖子,另一个是查询帖子数量,可以用来实现后面的分页。需要注意的点在注释里写了。
然后需要写接口对应的mapper配置文件,和上一节内容差不多,有几个需要注意的点
- namespace要修改成对应的接口
- 使用了标签动态拼接sql语句,当满足if的条件时,拼接上if标签里的内容,这样我们就可以根据传入的userId的值,来判断是查询首页帖子还是查询我的帖子。
- 另外使用了order by来排序,首先按照帖子类型倒序,再按照时间倒叙,这样就实现了精华帖置顶并且新帖在前的功能,后面还会实现按热度排序。
-
package com.neu.langsam.community.dao; import com.neu.langsam.community.entity.DiscussPost; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import java.util.List; @Mapper public interface DiscussPostMapper { //首页帖子实际不用传入userId,但考虑到之后需要实现我的帖子功能,这里就整合到一起,offset偏移量表示当前是第几行,limit表示每页数量 List<DiscussPost> selectDiscussPosts(int userId,int offset,int limit); //动态拼接条件,方法有且只有一个条件时,需要用@Param注解给参数取别名,也可以在属性名太长的时候取别名来简化 int selectDiscussPostRows(@Param("userId") int userId); }
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.neu.langsam.community.dao.DiscussPostMapper"> <sql id="selectFields"> id,user_id,title,content,type,status,create_time,comment_count,score </sql> <select id="selectDiscussPosts" resultType="DiscussPost"> select <include refid="selectFields"></include> from discuss_post where status!=2 <if test="userId!=0"> and user_id=#{userId} </if> order by type desc,create_time desc limit #{offset},#{limit} </select> <select id="selectDiscussPostRows" resultType="int"> select count(id) from discuss_post where status!=2 <if test="userId!=0"> and user_id=#{userId} </if> </select> </mapper>
然后测试一下,可以看到控制台输出没问题。
Service层
我们这个业务比较简单,但是也必须严格遵守层次去编写,便于我们后面的一些维护和安全性的实现。这里还有另一个问题,discusspost表里有userId字段,但是我们不可能直接给用户显示id,需要显示用户名。有两种方案:
- 写sql的时候用id再查询一下用户名拼接到一起
- 用之前写好的查询user的方法,和我查询到的discusspost再做一个组合
看起来第二种方法更麻烦一些,但是当我们后面用到redis缓存数据库后,这样的方法就可能效率更高了。那么我们再写一个UserService提供查询用户方法。
package com.neu.langsam.community.service;
import com.neu.langsam.community.dao.DiscussPostMapper;
import com.neu.langsam.community.entity.DiscussPost;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class DiscussPostService {
@Autowired
private DiscussPostMapper discussPostMapper;
public List<DiscussPost> findDiscussPosts(int userId,int offset,int limit){
return discussPostMapper.selectDiscussPosts(userId,offset,limit);
}
public int findDiscussPostRows(int userId){
return discussPostMapper.selectDiscussPostRows(userId);
}
}
package com.neu.langsam.community.service;
import com.neu.langsam.community.dao.UserMapper;
import com.neu.langsam.community.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public User findUserById(int id){
return userMapper.selectById(id);
}
}
导入网页
前端的网页我们使用教程模板,在我的github可以找到,把template下的文件复制到项目就行。
Controller层
首先在controller包下新建HomeController,使用注解注入Service
@Autowired
private DiscussPostService discussPostService;
@Autowired
private UserService userService;
第一个任务
首页上显示10个帖子。这里先写固定值,后续再进行改进。前面提到的显示用户名的方式,采用第二种,利用post里的id去查询对应的user,然后用HashMap装到一起,再用list处理map。
最终我们传给模板引擎的就是一个list,list里是每个帖子对应的map,每个map里有post对象和user对象,每个对象有自己的属性和get、set方法。那么利用thymeleaf的语法,去遍历这些对象,将值赋给相应的标签就行。html已经写好了,有兴趣的可以学一学。我们启动项目,在浏览器里访问。
@RequestMapping(path = "/index",method = RequestMethod.GET)
public String getIndexPage(Model model){
//查询10条数据
List<DiscussPost> list=discussPostService.findDiscussPosts(0,0,10);
//用Map把post和user装到一起
List<Map<String,Object>> discussPosts=new ArrayList<>();
//遍历post,用post里的id查询user
if(list!=null){
for (DiscussPost post:list){
Map<String,Object> map=new HashMap<>();
map.put("post",post);
User user=userService.findUserById(post.getUserId());
map.put("user",user);
discussPosts.add(map);
}
}
//把我们准备好的数据传给model
model.addAttribute("discussPosts",discussPosts);
//返回模板
return "/index";
}
第二个任务
完成第一个任务后,在这基础上完成分页的功能,我们可以自己封装一个分页类,提高代码的复用。在entity包下创建Page类。使用idea生成get和set方法,这里我们需要修改一下生成的set方法,判断一下传入的数据是否合理。比如setLimit方法,我规定limit必须大于1小于100。
//当前页码
private int current=1;
//显示上限
private int limit=10;
//数据的总数,用于计算总页数
private int rows;
//查询路径,用于复用分页链接
private String path;
public void setLimit(int limit) {
if (limit>=1&&limit<=100){
this.limit = limit;}
}
另外,还需要自定义一些方法。
/*
*获取当前页的起始行
*/
public int getOffset(){
//current*limit-limit
return (current-1)*limit;
}
/*
*获取总页数
*/
public int getTotal(){
//rows/limit[+1]
if (rows%limit==0){
return rows/limit;
}else {
return rows/limit+1;
}
}
/*
*获取起始页码
* @return
*/
public int getFrom(){
int from=current-2;
return from < 1 ? 1 : from;
}
/*
*获取结束页码
* @return
*/
public int getTo(){
int to=current+2;
int total=getTotal();
return to>total?total:to;
}
然后对controller进行一些改造。
@RequestMapping(path = "/index",method = RequestMethod.GET)
public String getIndexPage(Model model, Page page){
//方法调用之前,Spring MVC会自动实例化Model和Page,并讲Page注入Model
//所以在thymeleaf中可以直接访问page对象中的数据
page.setRows(discussPostService.findDiscussPostRows(0));
page.setPath("/index");
List<DiscussPost> list=discussPostService.findDiscussPosts(0,page.getOffset(),page.getLimit());