目录
- 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.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 层实现:
- 删除日志功能
- 11.实现主人的回复和添加新日志功能
-
- 11.1 要实现的功能
- 11.2 主人回复功能
- 11.3 发布新日志功能
- 11.4 网页展示
- 12、所有代码
1、熟悉 QQZone 业务需求
-
用户登录
-
登录成功,显示主界面。左侧显示好友列表;上端显示欢迎词,如果不是自己的空间,显示超链接:返回自己的空间;下端显示日志列表
-
查看日志详情:
- 日志本身的信息(作者的头像、昵称、标题、内容和日志)
- 回复列表(头像、昵称、回复内容、回复日期)
- 主人回复信息
-
删除日志
-
删除特定回复
-
删除特定主人的回复
-
添加日志,添加回复,添加主人回复
-
点击左侧好友链接,进入好友空间
注意:
- 文章中的静态页面已经提供,如果不去尚硅谷微信官方账号下载,可以直接使用其风格和静态页面。
2.数据库设计
2.1 抽取实体
用户登录信息(一键快速登录)、用户详细信息(以后修改手机号码、邮箱等详细信息) 、 日志 、 回贴 、 主人回复
2.2 分析属性
- 用户登录信息:账号、密码、头像、昵称
- 用户详情:真实姓名、星座、血型、邮箱、手机号码…
- 日志:标题、内容、日期、作者
- 回复:内容、日期、作者、日志
- 主人:内容、日期、作者、回复
2.3 分析实体之间的关系
- 用户登录信息 : 用户细节信息 1:1 PK(一对一关系)
- 用户 : 日志 1:N(一对N)
- 日志 : 回复 1:N(一对N)
- 回复 : 主人回复 1:1 UK(一对一)
- 用户 : 好友 M : N(多对多,一个人可以有多个朋友,一个人也可以成为多个人的朋友)
注意(上图其实是 QQ 空间项目的 ER 设计图):
- 实体用矩形表示;
- 椭圆形表示实体的属性;
- 钻石表示实体与实体的关系。
2.4 数据库的范式
- 第一范式:列不能再分。比如收货地址,上海xxx街道xxx路xxxx社区,每个地方都可以分成一列,所以在同一个地区,比如xxx在街上,他们的数据库存储xxx可以用一个数字代替道路,这样存储数字比存储汉字节省空间。
- 第二范式:一张表只表达一层含义(只描述一件事),以确保表中的每一列都与主键有关。
- 第三范式:表中的每一列和主键都是直接依赖关系,而不是间接依赖。 参考链接:数据库-三大范式–详解
数据库设计的范式往往与数据库的查询性能背道而驰。我们需要根据实际业务情况做出选择:
- 在这种情况下,我们更倾向于提高数据库的设计范式,从而提高存储效率;
- 在这种情况下,我们更倾向于牺牲数据库的规格,降低数据库设计的范式,允许特定的冗余,从而提高查询性能;
比如这个 QQ我们可以根据空间项目,明明主人回复的作者 为什么要在主人的回复中设置作者信息(这样做不符合第三范式)?
- 如果我们不添加作者列,想在主人的回复中查询作者,我们需要多表连接查询,查询作者三次
- 如果我们添加作者列,我们可以在查询时查询单表,查询效率更高。 解释:
- 主键尽量使用没有实际业务意义的自增列,这样以后需要合并数据库时就不会发生主键冲突; 五个实体有六个表,因为多对多关联会产生中间第三个表 t_friend 表,表示 t_user_basic 这张表和你自己有关。
3.根据数据库表新建 pojo 类(ORM编程思想)
(object relational mapping),有点万事万物皆对象那种意思
- 对应一个数据表 java 类
- 表中的记录对应 java 类的对象
- 对应表中的一个字段 java 类的属性
- java 类别中的属性名称应尽可能与数据库中的列名相同。如果真的不一样,以后再写 sql 句子时,记得起列的别名。
3.1 UserBasic 类:
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<Topic> topicList ; //1:N,一个用户对应一个日志列表 private List<UserBasic> 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<Topic> getTopicList() { return topicList;} public void setTopicList(List<Topic> topicList) { this.topicList = topicList;} public List<UserBasic> getFriendList() { return friendList;} public void setFriendList(List<UserBasic> friendList) { this.friendList = friendList;} }
解释:
- 构造器先定义一个空参构造器,后面需要哪几个参数的构造器再回来补;
- 下面每个类我就不放
get、set
方法了,记得构造所有属性的 get、set 方法。
3.2 UserDetail 类:
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 时分秒
3.3 Topic 类:
public class Topic {
private Integer id ;
private String title ;
private String content ;
private LocalDateTime topicDate ;
private UserBasic author ; //M:1
private List<Reply> replyList ; //1:N
public Topic(){
}
public Topic(Integer id) {
this.id = id;
}
}
解释:
- 这里是因为 MySQL 8.0 中默认的 data 类型是
LocalDateTime
,所以这里要和老师定义的 Data 类型不一致,否则后面会报错,下面所有涉及到日期类型的都替换成LocalDateTime
。
3.4 Reply 类:
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、用户登录功能
首先,我们程序运行之后,首先看到的界面是一个登陆界面,界面如下: 输入用户名和密码之后,需要验证是否登陆成功,也就是要去数据库里面查询是否有对应的账号和密码。
5.1 登陆功能
login.html 页面设计成一个表单,被中央控制器 DispatcherServlet 拦截之后,根据配置文件的配置跳转到 UserController
控制器:
<form th:action="@{/user.do}" method="get">
配置文件:
<bean id="user" class="com.atguigu.qqzone.controller.UserController">
<property name="userBasicService" ref="userBasicService"/>
<property name="topicService" ref="topicService"/>
</bean>
UserController 类中的登陆方法:
private UserBasicService userBasicService ;
private TopicService topicService ;
public String login(String loginId , String pwd , HttpSession session){
//1.登录验证
UserBasic userBasic = userBasicService.login(loginId, pwd);
if(userBasic!=null){
//1-1 获取相关的好友信息
List<UserBasic> friendList = userBasicService.getFriendList(userBasic);
//1-2 获取相关的日志列表信息(但是,日志只有id,没有其他信息)
List<Topic> topicList = topicService.getTopicList(userBasic);
userBasic.setFriendList(friendList);
userBasic.setTopicList(topicList);
//userBasic这个key保存的是登陆者的信息
//friend这个key保存的是当前进入的是谁的空间,将来点进好友空间这个key要有改动
session.setAttribute("userBasic",userBasic);
session.setAttribute("friend",userBasic);
return "index";
}else{
session.setAttribute("login",123);
return "login";
}
}
解释:
- 首先调用
userBasicService
层中的登陆方法进行登陆验证; - 登陆成功之后要通过 userBasicService 层获取相关好友列表,以及通过
topicService
层获取日志列表,这些都是要展示在主页上的; - 获取到 friendList 和 topicList 之后要调用 userBasic 类的 set 方法设置到 userBasic 对应的属性上去,也就是进行类的关联,其实也就是表的连接;
- 最后,将获取到的 userBasic 类设置到 session 作用域中,方便之后调用,如果想获取 好友列表和日志列表通过 userBasic 来调用就可以了。
5.2 获取好友列表
所有的实现方法均需要提前在接口中定义规范,这里实现类中再重写这些方法,这里省略接口步骤。
UserBasicServiceImpl 实现:
public class UserBasicServiceImpl implements UserBasicService {
private UserBasicDAO userBasicDAO = null;
@Override
public UserBasic login(String loginId, String pwd) {
UserBasic userBasic = userBasicDAO.getUserBasic(loginId, pwd);
return userBasic;
}
@Override
public List<UserBasic> getFriendList(UserBasic userBasic) {
List<UserBasic> userBasicList = userBasicDAO.getUserBasicList(userBasic);
List<UserBasic> friendList = new ArrayList<>(userBasicList.size());
for (int i = 0; i < userBasicList.size(); i++) {
UserBasic friend = userBasicList.get(i);
friend = getUserBasicById(friend.getId());
friendList.add(friend);
}
return friendList;
}
@Override
public UserBasic getUserBasicById(Integer id) {
return userBasicDAO.getUserBasicById(id);
}
}
配置文件:
<bean id="userBasicService" class="com.atguigu.qqzone.service.impl.UserBasicServiceImpl">
<property name="userBasicDAO" ref="userBasicDAO"/>
</bean>
UserBasicDAOImpl 实现:
public class UserBasicDAOImpl extends BaseDAO<UserBasic> implements UserBasicDAO {
@Override
public UserBasic getUserBasic(String loginId, String pwd) {
return super.load("select * from t_user_basic where loginId = ? and pwd = ? " , loginId , pwd);
}
@Override
public List<UserBasic> getUserBasicList(UserBasic userBasic) {
String sql = "SELECT fid as 'id' FROM t_friend WHERE uid = ?";
return super.executeQuery(sql,userBasic.getId());
}
@Override
public UserBasic getUserBasicById(Integer id) {
return load("select * from t_user_basic where id = ? " , id);
}
}
配置文件:
<bean id="userBasicDAO" class="com.atguigu.qqzone.dao.impl.UserBasicDAOImpl"/>
解释:
- UserBasicService 层调用 DAO 层的 getUserBasic 方法从数据库查到了一组 UserBasic 信息,返回给 UserController 层;
- 如果查到了 UserBasic 信息,也就是它不为空的话,那么我们需要调用UserBasicService 层的 getFriendList 方法获取好友列表,UserBasicService 层调用的是 DAO 层的 getUserBasicList 方法,注意这里从 t_friend 表中获取到的是一系列 fid 值,这个 fid 值对应的是 UserBasic 中的某 id 值;
- 拿到一系列 id 值之后我们要去 t_user_basic 中找这些 id 值对应的是哪些用户,即 UserBasicService 层遍历每一个 fid 值,调用 DAO 层的 getUserBasicById 方法获取到真正对应的所有好友的 UserBasic 信息。
5.3 获取日志列表
TopicServiceImpl 实现:
public class TopicServiceImpl implements TopicService {
private TopicDAO topicDAO ;
@Override
public List<Topic> getTopicList(UserBasic userBasic) {
return topicDAO.getTopicList(userBasic);
}
}
TopicDAOImpl 实现:
public class TopicDAOImpl extends BaseDAO<Topic> implements TopicDAO {
@Override
public List<Topic> getTopicList(UserBasic userBasic) {
return super.executeQuery("select * from t_topic where author = ? " , userBasic.getId());
}
}
配置文件:
<bean id="topicDAO" class="com.atguigu.qqzone.dao.impl.TopicDAOImpl"/>
<bean id="topicService" class="com.atguigu.qqzone.service.impl.TopicServiceImpl">
<property name="topicDAO" ref="topicDAO"/>
</bean>
解释:
- UserController 控制器 - topicService 层 - topicDAO 层的 getTopicList 方法,层层调用,功能分离。
5.4 登录功能的错误排查
-
URL没修改,用的还是 fruitdb,将
ConnUtil
工具类中的jdbc:mysql://localhost:3306/fruitdb?useUnicode=true&characterEncoding=utf-8&useSSL=false
地址修改为jdbc:mysql://localhost:3306/qqzonedb?useUnicode=true&characterEncoding=utf-8&useSSL=false
; -
给 fid 起别名,TopicDAOImpl 实现类中的 getUserBasicList 方法要封装成一个 UserBasic 的 list,这个类里面没有 fid 这个属性,所以要起一个别名;
-
并且在 BaseDAO 中的获取别名的方法修改为
rsmd.getColumnLabel()
, 而不是 rsmd.getColumnName() (获取列的列名); -
Can not set com.atguigu.qqzone.pojo.UserBasic field com.atguigu.qqzone.pojo.Topic.author to java.lang.Integer 错误,这个错误是什么原因呢?
-
我们之前将从数据库获取到的数据集,获取数据库列名然后将数据集中的某一列设置到这个运行时类的某个属性上,这里报错是我们需要的是一个 UserBasic 类的属性,但是我们获取到的是 Integer 属性的数据,不能把 Integer 属性的数据强制设置上去;
-
如下图所示,topic 表中最后一列存放的是 author 的 id 值,我们其实获取到的 Integer 值是作者的 id 值,那么我们需要将这个 id 值根据构造器方法封装成一个 UserBasic 类的值赋值上去;
-
获取当前字段的类型名称,判断如果是自定义类型,获取这个自定义类型的 Class 对象,然后获取这个类的带 Integer 类型的构造器(这里因为这个项目只有 Integer 类的某一数据列,其他项目不一定),即需要用调用这个自定义类的带一个参数的,创建出这个自定义类的实例对象,然后将实例对象赋值给这个属性。
下面是 BaseDAO 中对应的 setValue 方法的修改:
private void setValue(Object obj, String property, Object propertyValue) throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException { Class clazz = obj.getClass(); Field field = clazz.getDeclaredField(property); if (field != null) { String typeName = field.getType().getName(); if (isMyType(typeName)) { Class typeNameClass = Class 标签:
th矩形电连接器