- 1、熟悉 QQZone 业务需求
- 2、数据库设计
- 2.1 抽取实体
- 2.2 分析其中的属性
- 2.3 分析实体之间的关系
- 2.4 数据库的范式
- 3、根据数据库的表新建 pojo 类(ORM编程思想)
- 3.1 UserBasic 类:
- 3.2 UserDetail 类:
- 3.3 Topic 类:
- 3.4 Reply 类:
- 3.5 HostReply 类:
- 4、DAO 层 - service 层 - controller 控制器
- 5、用户登录功能
- 5.1 登陆功能
- 5.2 获取好友列表
- 5.3 获取日志列表
- 5.4 登录功能的错误排查
- 6、显示主界面
- 6.1 显示左侧好友列表
- 6.2 显示中间日志列表
- 6.3 显示顶端欢迎界面
- 6.4 总结
- 6.5 左侧好友名称变超链接
- 7、日志详情页面
- 7.1 获取所有 topic 信息
- 7.2 获取这个 topic 关联的所有的回复以及主人回复
- 7.3 两个报错信息
- 7.4 将日志详情动态显示
- 8、添加回复功能
- ReplyController 控制器实现:
- ReplyService 层实现:
- ReplyDAO 层实现:
- 9、删除回复功能
- ReplyController 控制器实现:
- ReplyService 层实现:
- HostReplyService 层实现:
- ReplyDAO 层实现:
- HostReplyDAO 层实现:
- 10、删除日志功能
- 11、实现主人回复和添加新日志功能
- 11.1 想要实现的功能
- 11.2 主人回复功能
- 11.3 发表新日志功能
- 11.4 网页展示
-
用户登录
-
登录成功,显示主界面。左侧显示好友列表;上端显示欢迎词,如果不是自己的空间,显示超链接:返回自己的空间;下端显示日志列表
-
查看日志详情:
- 日志本身的信息(作者头像、昵称、日志标题、日志内容、日志的日期)
- 回复列表(回复者的头像、昵称、回复内容、回复日期)
- 主人回复信息
-
删除日志
-
删除特定回复
-
删除特定主人回复
-
添加日志、添加回复、添加主人回复
-
点击左侧好友链接,进入好友的空间
注意:
- 文中的静态页面已经提供好的,如果没有去尚硅谷公众号下载,直接使用其样式和静态页面即可。
用户登录信息(一键快速登录)、用户详情信息(后面自己再修改手机号和邮箱等等详细信息) 、 日志 、 回贴 、 主人回复
2.2 分析其中的属性- 用户登录信息:账号、密码、头像、昵称
- 用户详情信息:真实姓名、星座、血型、邮箱、手机号…
- 日志:标题、内容、日期、作者
- 回复:内容、日期、作者、日志
- 主人回复:内容、日期、作者、回复
- 用户登录信息 : 用户详情信息 1:1 PK(一对一关系)
- 用户 : 日志 1:N(一对N)
- 日志 : 回复 1:N(一对N)
- 回复 : 主人回复 1:1 UK(一对一)
- 用户 : 好友 M : N(多对多,一个人可以有多个好友,一个人也可以成为多个人的好友)
注意(上图其实是 QQ 空间项目的 ER 设计图): - 实体用矩形表示;
- 实体的属性用椭圆形表示;
- 实体与实体之间的关系用菱形来表示。
- 第一范式:列不可再分。比如说收货地址,上海市xxx街道xxx路xxxx小区,每一个所属地都可以分成一列,这样在同一个地区的,比如都是xxx街道的,他们的数据库存储xxx路就可以用一个数字来代替,这样存储数字比存储汉字节省空间。
- 第二范式:一张表只表达一层含义(只描述一件事情),确保表中的每一列都和主键相关。
- 第三范式:表中的每一列和主键都是直接依赖关系,而不是间接依赖。
参考链接:数据库-----三大范式–详解
一般数据库设计中遵循的规则:
数据库设计的范式和数据库的查询性能很多时候是相悖的,我们需要根据实际的业务情况做一个选择:
- 查询频次不高的情况下,我们更倾向于提高数据库的设计范式,从而提高存储效率;
- 查询频次较高的情形,我们更倾向于牺牲数据库的规范度,降低数据库设计的范式,允许特定的冗余,从而提高查询的性能;
比如这个 QQ空间项目,明明主人回复中的作者,我们可以根据 日志-回复-主人回复 之间的关联关系查到作者信息,为什么我们还要在主人回复中设置作者这一项呢(这样做不满足第三范式)?
- 如果我们没有添加作者这一列,想在主人回复中查询作者,那么我们需要多表连接查询,查询三次查到作者
- 如果我们添加作者这一列,那么我们查询的时候进行单表查询就可以了,查询效率更高。
解释: - 主键尽量使用没有实际业务意义的自增列,这样以后在场景改变的时候需要合并数据库的时候不会发生主键冲突;
5个实体有6张表,因为多对多关联会产生中间第三张表 t_friend 表,表示 t_user_basic 这张表和自己产生关联。
ORM 编程思想:(object relational mapping),有点万事万物皆对象那种意思
- 一个数据表对应一个 java 类
- 表中的一条记录对应 java 类的一个对象
- 表中的一个字段对应 java 类的一个属性
注意:
- java 类中的属性名字尽量和数据库的列名相同,如果确实不相同的话,后面写 sql 语句的时候,记得起列的别名。
public class UserBasic {
private Integer id ;
private String loginId ;
private String nickName ;
private String pwd ;
private String headImg ;
//自定义属性的所属类,代表级联关系
private UserDetail userDetail ; //1:1,一个用户对应一个用户详情
private List topicList ; //1:N,一个用户对应一个日志列表
private List friendList ;//M:N,一个用户对应一个朋友列表
public UserBasic(){}
public UserBasic(Integer id) {this.id = id;}
public Integer getId() {return id;}
public void setId(Integer id) {this.id = id;}
public String getLoginId() {return loginId;}
public void setLoginId(String loginId) {this.loginId = loginId;}
public String getNickName() {return nickName;}
public void setNickName(String nickName) {this.nickName = nickName;}
public String getPwd() {return pwd;}
public void setPwd(String pwd) {this.pwd = pwd;}
public String getHeadImg() {return headImg;}
public void setHeadImg(String headImg) {this.headImg = headImg;}
public UserDetail getUserDetail() {return userDetail;}
public void setUserDetail(UserDetail userDetail) {this.userDetail = userDetail;}
public List getTopicList() {return topicList;}
public void setTopicList(List topicList) {this.topicList = topicList;}
public List getFriendList() {return friendList;}
public void setFriendList(List friendList) {this.friendList = friendList;}
}
解释:
- 构造器先定义一个空参构造器,后面需要哪几个参数的构造器再回来补;
- 下面每个类我就不放 get、set 方法了,记得构造所有属性的 get、set 方法。
public class UserDetail {
private Integer id ;
private String realName ;
private String tel ;
private String email ;
private LocalDateTime birth ;
private String star ;
public UserDetail(){}
public Integer getId() {
return id;
}
}
日期的继承关系:
父类:java.util.Date 年月日时分秒毫秒
子类:java.sql.Date 年月日
子类:java.sql.Time 时分秒
public class Topic {
private Integer id ;
private String title ;
private String content ;
private LocalDateTime topicDate ;
private UserBasic author ; //M:1
private List replyList ; //1:N
public Topic(){}
public Topic(Integer id) {
this.id = id;
}
}
解释:
- 这里是因为 MySQL 8.0 中默认的 data 类型是 LocalDateTime,所以这里要和老师定义的 Data 类型不一致,否则后面会报错,下面所有涉及到日期类型的都替换成 LocalDateTime。
public class Reply {
private Integer id ;
private String content ;
private LocalDateTime replyDate ;
private UserBasic author ; //M:1
private Topic topic ; //M:1
private HostReply hostReply ; //1:1
public Reply() {}
public Reply(Integer id) {this.id = id;}
public Reply(String content, LocalDateTime replyDate, UserBasic author, Topic topic) {
this.content = content;
this.replyDate = replyDate;
this.author = author;
this.topic = topic;
}
}
3.5 HostReply 类:
public class HostReply {
private Integer id ;
private String content ;
private LocalDateTime hostReplyDate ;
private UserBasic author ; //M:1
private Reply reply ; //1:1
public HostReply(){}
public HostReply(Integer id) {
this.id = id;
}
}
4、DAO 层 - service 层 - controller 控制器
接下来,我们大致要完成的事情是:
- 建立 DAO 接口:指定某一类对应的接口应该完成什么功能,然后建立这些接口的实现类;
- 建立业务层:每一个基本类的 DAO 实现之后呢,需要考虑实现业务功能,需要建立 service 接口,然后实现这些接口的功能;这里需要创建配置文件,配置 DAO 层和业务层交互的 bean 节点,以及 DAO 层和 service 层之间的依赖关系(以后 spring 不用自己手动配置了)
- 建立 controller 控制器:业务层建好之后,controller 负责调用 service 层封装好的各种业务功能实现对同一个类的各种 业务流程控制。
实际上开发中,我们都是根据想实现的功能需求来一步一步完善 DAO层、service 层、controller 控制器以及配置文件的。
5、用户登录功能首先,我们程序运行之后,首先看到的界面是一个登陆界面,界面如下:
输入用户名和密码之后,需要验证是否登陆成功,也就是要去数据库里面查询是否有对应的账号和密码。
login.html 页面设计成一个表单,被中央控制器 DispatcherServlet 拦截之后,根据配置文件的配置跳转到 UserController 控制器:



