开发一个项目的流程
首先,web项目主要解决的就是浏览器和服务器之间的交互,开发流程就是1次请求的执行过程,任何功能都可以拆解成若干次请求。
请求步骤
- 请求提交给视图层,视图层代码主要由/Controller和/templates(模板)构成
- Controller处理请求期间要访问业务层,要业务组件处理具体业务
- 业务组件调用数据组件,访问数据库
开发步骤
按照依赖顺序Dao->Service->Controller
以实现社区首页显示前10个帖子功能为例
Dao
1.在数据库中建表
- MySQL Workbench点击表后面的i按钮,选择DDL可以查看表的字段和字段含义
- 字段备注可以在表上点击右键Alter
- Table进行修改 “content”字段类型为text不为varchar,因为帖子内容比较长
2.在/entity包下创建实体类
实体类的作用是封装表里的数据,命名为大写驼峰式,eg:DiscussPost.java
- 根据数据表中的字段加上对应的属性
private int userId;
PS:字段名称为user_id,属性名为userId
- 因为属性都是private类型,通过Idea快捷键Alt+Insert给字段生成get和set函数
- 为了测试打印数据方便,通过Idea快捷键Alt+Insert给字段生成toString函数
package com.nowcoder.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 +
'}';
}
}
3.开发数据访问组件Mapper
要实现某个功能,要在mapper接口中声明查询方法,
并在配置文件mapper.xml中写出与之有关的SQL。
- /dao文件中创建一个DiscussPostMapper.java(接口类型)(命名大写驼峰),一般一个表对应一个mapper接口
PS:mapper接口前@Mapper注解,才能被容器自动扫描这个接口,容器才能自动实现和装配它
@Mapper
public interface DiscussPostMapper {
//分页查询帖子功能,返回的是多条数据的是一个集合,集合里装的是帖子的对象
//方法名自己取,参数是userId,帖子表有userId字段
//首页查询不需要传入userId,默认为0,当userId为0时不管,不拼到SQL中
//不为0时,正常拼到SQL中,SQL是动态的SQL
//分页功能:MySQL数据库实现只需要设置limit参数:起始行行号offset,这一页最多显示多少条数据limit
List<DiscussPost> selectDiscussPosts(int userId, int offset, int limit);
//一共有多少页:一共多少页,每页显示多少条数据
//查询表里一共多少行数据功能
//@Param("userId") 用于给参数起别名,比如有的参数名称比较长
//如果只有一个参数,并且在<if>里使用,则必须要加别名
//如果在SQL中需要用到动态的拼接条件<if>,条件里需要用到参数,且方法只有一个参数,参数必须要起别名
int selectDiscussPostRows(@Param("userId") int userId);
}
- /mapper目录中创建一个discusspost-mapper.xml配置文件
步骤:新建一个file,将后缀写成.xml
代码代码的模板复制于Mabatis官网入门菜单
这里有几个字段需要学习一下
<!-- 提取公有的查询字段 注意因为是字段,所以是user_id -->
<sql id="selectFields">
id, user_id, title, content, type, status, create_time, comment_count, score
</sql>
<!--在SQL语句中用以下替代,注意有的时候要加括号-->
<include refid="selectFields"></include>
<!-- 利用方法名写对应SQL
方法名:selectDiscussPosts
resultType:返回类型
-->
<select id="selectDiscussPosts" resultType="DiscussPost"></select>
<!--
首页查询不需要传入userId,默认为0,当userId为0时不管,不拼到SQL中
不为0时,正常拼到SQL中,SQL是动态的SQL
-->
<if test="userId!=0">
and user_id = #{userId}
</if>
//字段=#{参数}
user_id = #{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.nowcoder.community.dao.DiscussPostMapper">
<sql id="selectFields">
id, user_id, title, content, type, status, create_time, comment_count, score
</sql>
<!-- List不用声明,自动解析 -->
<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>
4.在/test/…目录下创建测试类MapperTests.java
- @Autowired 注入mapper
- @Test编写测试函数
@RunWith(SpringRunner.class)
@SpringBootTest
@ContextConfiguration(classes = CommunityApplication.class)
public class MapperTests {
@Autowired
private UserMapper userMapper;
@Autowired
private DiscussPostMapper discussPostMapper;
@Test
public void testSelectUser(){
User user = userMapper.selectById(101);
System.out.println(user);
user = userMapper.selectByName("liubei");
System.out.println(user);
user = userMapper.selectByEmail("nowcoder101@sina.com");
System.out.println(user);
}
@Test
public void testInsertUser(){
User user = new User();
user.setUsername("test");
user.setPassword("123456");
user.setSalt("abc");
user.setEmail("test@qq.com");
user.setHeaderUrl("https://www.nowcoder.com/101.png");
user.setCreatTime(new Date());
int rows = userMapper.insertUser(user);
System.out.println(rows);
System.out.println(user.getId());
}
@Test
public void updateUser(){
int rows = userMapper.updateStatus(150,1);
System.out.println(rows);
rows = userMapper.updateHeader(150,"https://www.nowcoder.com/102.png");
System.out.println(rows);
rows = userMapper.updatePassword(150,"hello");
System.out.println(rows);
}
@Test
public void testSelectPosts(){
List<DiscussPost> list = discussPostMapper.selectDiscussPosts(149,0,10);
for (DiscussPost post : list){
System.out.println(post);
}
int rows = discussPostMapper.selectDiscussPostRows(149);
System.out.println(rows);
}
}
Service
在/service包下新建业务组件DiscussPostService.java和UserSevice.java
- 加入@Service注解
- 注入mapper接口
- 编写业务层方法
@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);
}
}
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
//根据用户id查询用户的方法
public User findUserById(int id){
return userMapper.selectById(id);
}
}
视图层
Controller
- 在/controller包下创建HomeController.java
- 设置@Controller注解
- 通过@Autowired注入DiscussPostService和UserService
- 通过@RequestMapping(path = “/index”, method = RequestMethod.GET)定义访问路径和方法
- 增加处理请求的方法
@Controller
public class HomeController {
@Autowired
private DiscussPostService discussPostService;
@Autowired
private UserService userService;
@RequestMapping(path = "/index", method = RequestMethod.GET)
List<DiscussPost> list = discussPostService.findDiscussPosts(0, page.getOffset(), page.getLimit());
//遍历DiscussPost,根据每一个userId查user
//封装DiscussPost和User对象
List<Map<String, Object>> discussPosts = new ArrayList<>();
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.addAttribute("discussPosts",discussPosts);
return "/index";
}
}
模板:修改index.html文件
- 将静态和动态页面复制到/static和/templates文件夹中
- 指定模板引擎
<html lang="en" xmlns:th="http://www.thymeleaf.org">
- 修改依赖的样式和文件,绝对路径不修改,相对路径修改
<!-- 修改后thymeleaf到/static文件夹下寻找静态资源-->
<link rel="stylesheet" th:href="@{css/global.css}" />
<link rel="icon" href="https://static.nowcoder.com/images/logo_87_87.png"/>
- 循环输出
th:each="map:${discussPosts}"
- 修改头像路径
th:src="${map.user.headerUrl}"
表示
map.get("user")->User->user.getHeaderUrl()
- 显示文件标题
th:utext="${map.post.title}"
- 满足条件才显示
th:if="${map.post.type==1}"
- 格式化时间
th:text="${#dates.format(map.post.createTime,'yyyy-MM-dd HH:mm:ss')}"
完整代码:
<!-- 帖子列表 -->
<ul class="list-unstyled">
<li class="media pb-3 pt-3 mb-3 border-bottom" th:each="map:${discussPosts}">
<a href="site/profile.html">
<img th:src="${map.user.headerUrl}" class="mr-4 rounded-circle" alt="用户头像" style="width:50px;height:50px;">
</a>
<div class="media-body">
<h6 class="mt-0 mb-3">
<a href="#" th:utext="${map.post.title}">备战春招,面试刷题跟他复习,一个月全搞定!</a>
<span class="badge badge-secondary bg-primary" th:if="${map.post.type==1}">置顶</span>
<span class="badge badge-secondary bg-danger" th:if="${map.post.status==1}">精华</span>
</h6>
<div class="text-muted font-size-12">
<u class="mr-3" th:utext="${map.user.username}">寒江雪</u> 发布于 <b th:text="${#dates.format(map.post.createTime,'yyyy-MM-dd HH:mm:ss')}">2019-04-15 15:32:18</b>
<ul class="d-inline float-right">
<li class="d-inline ml-2">赞 11</li>
<li class="d-inline ml-2">|</li>
<li class="d-inline ml-2">回帖 7</li>
</ul>
</div>
</div>
</li>
</ul>