java web project--learning through building 5
a full-stack web application for users to understand java web system
QQZone 业务需求
业务需求
-
- 用户登录
-
- 登录成功,显示主界面。左侧显示好友列表;上端显示欢迎词。如果不是自己的空间,显示超链接:返回自己的空间;下端显示日志列表
-
- 查看日志详情:
- 日志本身的信息(作者头像、昵称、日志标题、日志内容、日志的日期)
- 回复列表(回复者的头像、昵称、回复内容、回复日期)
- 主人回复信息
- 查看日志详情:
-
- 删除日志
-
- 删除特定回复
-
- 删除特定主人回复
-
- 添加日志、添加回复、添加主人回复
-
- 点击左侧好友链接,进入好友的空间
数据库设计
-
- 抽取实体 : 用户登录信息、用户详情信息 、 日志 、 回贴 、 主人回复
-
- 分析其中的属性:
- 用户登录信息:账号、密码、头像、昵称
- 用户详情信息:真实姓名、星座、血型、邮箱、手机号
- 日志:标题、内容、日期、作者
- 回复:内容、日期、作者、日志
- 主人回复:内容、日期、作者、回复
-
- 分析实体之间的关系
- 用户登录信息 : 用户详情信息 1:1 PK
- 用户 : 日志 1:N
- 日志 : 回复 1:N
- 回复 : 主人回复 1:1 UK
- 用户 : 好友 M : N
数据库的范式
-
- 第一范式:列不可再分 (地址就不能是,因为可分)
-
- 第二范式:一张表只表达一层含义(只描述一件事情)
-
- 第三范式:表中的每一列和主键都是直接依赖关系,而不是间接依赖
数据库设计的范式和数据库的查询性能很多时候是相悖的,我们需要根据实际的业务情况做一个选择
- 查询频次不高的情况下,我们更倾向于提高数据库的设计范式,从而提高存储效率
- 查询频次较高的情形,我们更倾向于牺牲数据库的规范度,降低数据库设计的范式,允许特定的冗余,从而提高查询的性能
step1. 数据库设计
-
数据库主键最好不要跟业务有关,比如主键设计为学生学号,但是将来学校合并等,主键就比较麻烦
-
多对多关联会产生中间的第三张表
一号用户好友是二号,1 号用户好友是 3 号
id uid fid
1 1 2
2 1 3
CREATE DATABASE `qqzonedb2` CHAR SET utf8;
USE qqzonedb2;
CREATE TABLE `t_friend` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`uid` INT(11) DEFAULT NULL,
`fid` INT(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `FK_friend_basic_uid` (`uid`),
KEY `FK_friend_basic_fid` (`fid`),
CONSTRAINT `FK_friend_basic_fid` FOREIGN KEY (`fid`) REFERENCES `t_user_basic` (`id`),
CONSTRAINT `FK_friend_basic_uid` FOREIGN KEY (`uid`) REFERENCES `t_user_basic` (`id`)
) ENGINE=INNODB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8;
-- /*Data for the table `t_friend` */
INSERT INTO `t_friend`(`id`,`uid`,`fid`) VALUES (1,1,2),(2,1,3),(3,1,4),(4,1,5),(5,2,3),(6,2,1),(7,2,4),(8,3,1),(9,3,2),(10,5,1);
-- /*Table structure for table `t_host_reply` */
CREATE TABLE `t_host_reply` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`content` VARCHAR(500) NOT NULL,
`hostReplyDate` DATETIME NOT NULL,
`author` INT(11) NOT NULL,
`reply` INT(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `FK_host_basic` (`author`),
KEY `FK_host_reply` (`reply`),
CONSTRAINT `FK_host_basic` FOREIGN KEY (`author`) REFERENCES `t_user_basic` (`id`),
CONSTRAINT `FK_host_reply` FOREIGN KEY (`reply`) REFERENCES `t_reply` (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
-- /*Data for the table `t_host_reply` */
-- /*Table structure for table `t_reply` */
CREATE TABLE `t_reply` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`content` VARCHAR(500) NOT NULL,
`replyDate` DATETIME NOT NULL,
`author` INT(11) NOT NULL,
`topic` INT(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `FK_reply_basic` (`author`),
KEY `FK_reply_topic` (`topic`),
CONSTRAINT `FK_reply_basic` FOREIGN KEY (`author`) REFERENCES `t_user_basic` (`id`),
CONSTRAINT `FK_reply_topic` FOREIGN KEY (`topic`) REFERENCES `t_topic` (`id`)
) ENGINE=INNODB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
-- /*Data for the table `t_reply` */
INSERT INTO `t_reply`(`id`,`content`,`replyDate`,`author`,`topic`) VALUES (3,'回复','2021-07-14 16:16:54',2,8),(4,'回复2222','2021-07-14 16:17:11',3,8),(5,'这里是第三个回复','2021-07-14 16:30:49',1,8);
-- /*Table structure for table `t_topic` */
CREATE TABLE `t_topic` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`title` VARCHAR(100) NOT NULL,
`content` VARCHAR(500) NOT NULL,
`topicDate` DATETIME NOT NULL,
`author` INT(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `FK_topic_basic` (`author`),
CONSTRAINT `FK_topic_basic` FOREIGN KEY (`author`) REFERENCES `t_user_basic` (`id`)
) ENGINE=INNODB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8;
-- /*Data for the table `t_topic` */
INSERT INTO `t_topic`(`id`,`title`,`content`,`topicDate`,`author`) VALUES (3,'我的空间开通了,先做自我介绍!','大家好,我是铁锤妹妹!','2021-06-18 11:25:30',2),(8,'我的空间','我的空间','2021-07-14 16:16:40',1);
-- /*Table structure for table `t_user_basic` */
CREATE TABLE `t_user_basic` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`loginId` VARCHAR(20) NOT NULL,
`nickName` VARCHAR(50) NOT NULL,
`pwd` VARCHAR(20) NOT NULL,
`headImg` VARCHAR(20) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `loginId` (`loginId`)
) ENGINE=INNODB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
-- /*Data for the table `t_user_basic` */
INSERT INTO `t_user_basic`(`id`,`loginId`,`nickName`,`pwd`,`headImg`) VALUES (1,'u001','jim','ok','h1.jpeg'),(2,'u002','tom','ok','h2.jpeg'),(3,'u003','kate','ok','h3.jpeg'),(4,'u004','lucy','ok','h4.jpeg'),(5,'u005','张三丰','ok','h5.jpeg');
-- /*Table structure for table `t_user_detail` */
CREATE TABLE `t_user_detail` (
`id` INT(11) NOT NULL,
`realName` VARCHAR(20) DEFAULT NULL,
`tel` VARCHAR(11) DEFAULT NULL,
`email` VARCHAR(30) DEFAULT NULL,
`birth` DATETIME DEFAULT NULL,
`star` VARCHAR(10) DEFAULT NULL,
PRIMARY KEY (`id`),
CONSTRAINT `FK_detail_basic` FOREIGN KEY (`id`) REFERENCES `t_user_basic` (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
-- /*Data for the table `t_user_detail` */
-- /*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
-- /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
-- /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
step2. pojo
根据数据库的 5 张表创建 5 个实体
先写完表格对应的参数后,思考彼此之间的关系
- UserBasic
private Integer id ;
private String loginId ;
private String nickName ;
private String pwd ;
private String headImg ;
// 写完基本情况后思考彼此关系:
// UserBasic需要有UserDetail,查询用户详情
private UserDetail userDetail; // 1对1
// 1个用户会拥有多个日志
private List<Topic> topicList; // 1对多
// 1个用户会拥有多个好友
private List<UserBasic> friendList; // 多对多
- UserDetail (Data 这里 父类: java.util.Date 年月日时分秒毫秒) 子类:java.sql.Date 年月日 子类:java.sql.Time 时分秒)
private Integer id ;
private String realName ;
private String tel ;
private String email ;
private Date birth ;
private String star ;
- Reply
private Integer id ;
private String content ;
private Date replyDate ;
private UserBasic author ; //M:1
private Topic topic ; //M:1
private HostReply hostReply; // 1 : 1
- Topic
public class Topic {
private Integer id ;
private String title ;
private String content ;
private Date topicDate ;
private UserBasic author ; // 多对1
// 写完基本情况后思考彼此关系:
// 一个日志中会有多个reply
private List<Reply> replyList; // 1对多
- HostReply
private Integer id ;
private Integer id ;
private String content ;
private Date hostReplyDate ;
private UserBasic author ; //M:1
private Reply reply ; //1:1
}
step3. myssm
包含 util, baseDAO 等文件
目前我们进行 javaweb 项目开发的“套路”是这样的:
-
拷贝 myssm 包
-
新建配置文件 applicationContext.xml 或者可以不叫这个名字,在 web.xml 中指定文件名
-
在 web.xml 文件中配置:
- 配置前缀和后缀,这样 thymeleaf 引擎就可以根据我们返回的字符串进行拼接,再跳转
<context-param> <param-name>view-prefix</param-name> <param-value>/</param-value> </context-param> <context-param> <param-name>view-suffix</param-name> <param-value>.html</param-value> </context-param>
- 配置监听器要读取的参数,目的是加载 IOC 容器的配置文件(也就是 applicationContext.xml)
<context-param> <param-name>contextConfigLocation</param-name> <param-value>applicationContext.xml</param-value> </context-param>
😼😼😼😼😼😼😼😼
开发具体的业务模块
-
一个具体的业务模块纵向上由几个部分组成:
- html 页面
- POJO 类
- DAO 接口和实现类
- Service 接口和实现类
- Controller 控制器组件
😼😼😼😼😼😼😼😼
step4. DAO
😼😼😼😼😼😼😼😼
业务需求
-
- 用户登录
-
- 登录成功,显示主界面。左侧显示好友列表;上端显示欢迎词。如果不是自己的空间,显示超链接:返回自己的空间;下端显示日志列表
-
- 查看日志详情:
- 日志本身的信息(作者头像、昵称、日志标题、日志内容、日志的日期)
- 回复列表(回复者的头像、昵称、回复内容、回复日期)
- 主人回复信息
- 查看日志详情:
-
- 删除日志
-
- 删除特定回复
-
- 删除特定主人回复
-
- 添加日志、添加回复、添加主人回复
-
- 点击左侧好友链接,进入好友的空间
😼😼😼😼😼😼😼😼
public interface UserBasicDAO{
// 用户登录:跟据账户和密码获取特定用户信息
UserBasic getUserBasic(String loginId, String pwd);
// 左侧显示好友列表:获取指定用户的所有好友列表
List<UserBasic> getUserBasicList(UserBasic, userBasic);
// 下端显示日志列表: 根据id查询UserBasic的信息
}
新人困惑为什么这个需求不写到上面, 因为, 上面是操作的是 UserBasic 实体阿
public interface TopicDAO{
// 下端显示日志列表: 根据UserBasic查询日志
List<Topic> getTopicList(UserBasic, userBasic);
// 获取特定日志信息
Topic getTopic(Integer id);
// 增加日志
void addTopic(Topic topic);
// 删除日志
void delTopic(Topic topic);
}
回复需求
public interface ReplyDAO{
// 获取指定日志的回复列表
List<Reply> getReplyList(Topic topic);
// 增加日志
void addReply( Reply reply);
// 删除日志
void delReply(Integer id);
}
主人回复需求
public interface HostReplyDAO{
// 获取指定日志的回复列表
List<HostReply> getReplyList(Topic topic);
// 增加日志
void add HostReply( Reply reply);
// 删除日志
void del HostReply(Integer id);
}
impl 实现
左侧显示好友列表:获取指定用户的所有好友列表
List
getUserBasicList(UserBasic, userBasic)
select * from t_user_basic t1
left join t_friend t2 on t1.id = t2.uid
inner join t_user_basic t3 on t2.fid = t3.id
也可拆开, String sql = “select fid from t_friend where uid= ?";
返回的只有 id 值
这行到了后端会报错,在数据库上查询没事
select fid from t_friend where uid = 1
application.xml after DAO
跟 mybatis 的 xml 配置区分,这里写的是 DAO 和 server 实现方法对象, 后者写的是方法
<beans>
<bean id="userBasicDAO" class = com.learn.qqzone.DAO.impl.UserBasicDAOImpl/>
<bean id="topicDAO" class = com.learn.qqzone.DAO.impl.TopicDAOImpl/>
</beans>
step5. SERVICE
面向业务
public interface UserBasicService {
// 跟据账户和密码获取特定用户信息
UserBasic login(String loginId, String pwd);
// 获取指定用户的所有好友列表
List<UserBasic> getFriendList(UserBasic userBasic);
}
impl
String sql = “select fid from t_friend where uid= ?"; 所以返回的只有 id 值
根据 id 再查用户列表
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);
// String sql = "select fid from t_friend where uid= ?"; 所以返回的只有id值
List<UserBasic> friendList = new ArrayList<>(userBasicList.size());
// 根据 id 再查用户列表
for (int i = 0; i < userBasicList.size(); i++) {
UserBasic friend= userBasicList.get(i);
friend = userBasicDAO.getUserBasicById(friend.getId());
friendList.add(friend);
}
return friendList;
}
}
TopicService && impl
参考 TopicDAO
application.xml after service
<beans>
<bean id="userSerive" class = com.learn.qqzone.service.impl.UserBasicSeriveImpl>
<property name = "userBasicDAO" ref = "userBasicDAO" />
</bean>
<bean id="topicSerive" class = com.learn.qqzone.service.impl.TopicSeriveImpl>
<property name = "topicDAO" ref = "topicDAO" />
</bean>
</beans>
step6. CONTROLLER
public class UserController {
private UserBasicService userBasicService;
public String login(String loginId, String pwd) {
UserBasic userBasic = userBasicService.login(loginId, pwd);
if (userBasic != null) {
return "index";
}
}
}
这样还不够,因为 index 上还需要好友列表,所以还要查询好友列表和日记列表
public class UserController {
private UserBasicService userBasicService;
private TopicService topicService;
public String login(String loginId, String pwd, HttpSession session) {
if (userBasic != null) {
UserBasic userBasic = userBasicService.login(loginId, pwd);
List<UserBasic> friendList = userBasicService.getFriendList(userBasic);
List<Topic> topicList = TopicService.getTopicList(userBasic);
userBasic.setFriendList(friendList);
userBasic.setTopicList(topicList);
// 补一个session保存信息
session.setAttribute("userBasic", userBasic);
return "index";
} else {
return "login";
}
}
}
application.xml after controller
<bean id="user" class="com.learn.qqzone.controller.UserController">
<property name="userBasicService" ref="userBasicService"/>
<property name="topicService" ref="topicService"/>
</bean>
加下以下可以避免一些拼写上错误
<!DOCTYPE beans [
<!ELEMENT beans (bean*)>
<!ELEMENT bean (property*)>
<!ELEMENT property (#PCDATA)>
<!ATTLIST bean id ID #REQUIRED>
<!ATTLIST bean class CDATA #IMPLIED>
<!ATTLIST property name CDATA #IMPLIED>
<!ATTLIST property ref IDREF #IMPLIED>
]>
Artifact qq:war exploded: Error during artifact deployment. See server log for details
tomcat log
java.lang.IllegalArgumentException: InputStream cannot be null
超级想骂人,这个错误弄了很久,看 server.log 说 context.xml 不存在之类的,在这里就卡了半天。试了所有的教程,完全不行, 具体看这里error1
NoSuchFiledException-fid
public List<UserBasic> getUserBasicList(UserBasic userBasic) {
String sql = "select fid from t_friend where uid = ?"
return ...
}
返回的是 UserBasic, 而它并没有 fid,所以会报错
public List<UserBasic> getUserBasicList(UserBasic userBasic) {
String sql = "select fid as `id` from t_friend where uid = ?"
return ...
}
rsmd.getColumnName() 和 rsmd.getColumnLabel()
getColumnLabel() 返回 id
getColumnName() 返回 fid
Can not set com.learn.qqzone.pojo.UserBasic field com.learn.qqzone.pojo.Topic.author to java.lang.Integer
不能把 UserBasic 设置到 topic 里面的 author 上去, 因为结果获取的是 Integer
应该把数字封装成一个 UserBasic 再设置到 author 里面去
java.lang.NoSuchFieldException: userBasicDAO at java.lang.Class.getDeclaredField(Class.java:2070) at com.learn.myssm.ioc.ClassPathXmlApplicationContext.
(ClassPathXmlApplicationContext.java:76) at com.learn.myssm.listeners.ContextLoaderListener.contextInitialized>(ContextLoaderListener.java:22)
java.lang.NoSuchFieldException: userBasicDAO
//获取当前字段的类型名称 String typeName = field.getType().getName(); //判断如果是自定义类型,则需要调用这个自定义类的带一个参数的构造方法,创建出这个自定义的实例对象,然后将实例对象赋值给这个属性
汇总
-
top.html 页面显示登录者昵称、判断是否是自己的空间
1)显示登录者昵称: ${session.userBasic.nickName}
2)判断是否是自己的空间 : ${session.userBasic.id!=session.friend.id}
如果不是期望的效果,首先考虑将两者的 id 都显示出来
-
点击左侧的好友链接,进入好友空间
-
根据 id 获取指定 userBasic 信息,查询这个 userBasic 的 topicList,然后覆盖 friend 对应的 value
-
main 页面应该展示 friend 中的 topicList,而不是 userBasic 中的 topicList
-
跳转后,在左侧(left)中显示整个 index 页面
-
问题:在 left 页面显示整个 index 布局
-
解决:给超链接添加 target 属性: target="_top” 保证在顶层窗口显示整个 index 页面
-
-
top.html 页面需要修改: “欢迎进入${session.friend}”
-
-
日志详情页面实现
-
已知 topic 的 id,需要根据 topic 的 id 获取特定 topic
-
获取这个 topic 关联的所有的回复
-
如果某个回复有主人回复,需要查询出来
-
在 TopicController 中获取指定的 topic
-
具体这个 topic 中关联多少个 Reply,由 ReplyService 内部实现
-
获取到的 topic 中的 author 只有 id,那么需要在 topicService 的 getTopic 方法中封装,在查询 topic 本身信息时,同时调用 userBasicService 中的获取 userBasic 方法,给 author 属性赋值
-
同理,在 reply 类中也有 author,而且这个 author 也是只有 id,那么我们也需要根据 id 查询得到 author,最后设置关联
-
-
添加回复
-
删除回复
- 如果回复有关联的主人回复,需要先删除主人回复
- 删除回复
Cannot delete or update a parent row: a foreign key constraint fails
(
qqzonedb
.t_host_reply
, CONSTRAINTFK_host_reply
FOREIGN KEY (reply
) REFERENCESt_reply
(id
)) 我们在删除回复表记录时,发现删除失败,原因是:在主人回复表中仍然有记录引用待删除的回复这条记录 如果需要删除主表数据,需要首先删除子表数据
-
删除日志
- 删除日志,首先需要考虑是否有关联的回复
- 删除回复,首先需要考虑是否有关联的主人回复
- 另外,如果不是自己的空间,则不能删除日志
汇总
1. 日期和字符串之间的格式化
String -> java.util.Date
String dateStr1 = "2021-12-30 12:59:59";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
try {
Date date1 = sdf.parse(dateStr1);
} catch (ParseException e) {
e.printStackTrace();
}
Date -> String
Date date2 = new Date();
String dateStr2 = sdf.format(date2);
2. thymeleaf 中使用#dates 这个公共的内置对象
${#dates.format(topic.topicDate ,‘yyyy-MM-dd HH:mm:ss’)}
3. 系统启动时,我们访问的页面是: http://localhost:8080/pro23/page.do?operate=page&page=login
为什么不是: http://localhost:8080/pro23/login.html ?
如果是后者,那么属于直接访问静态页面。那么页面上的 thymeleaf 表达式(标签)浏览器是不能识别的
我们访问前者的目的其实就是要执行 ViewBaseServlet 中的 processTemplete()
4. http://localhost:8080/pro23/page.do?operate=page&page=login 访问这个 URL,执行的过程是什么样的
http:// localhost :8080 /pro23 /page.do ?operate=page&page=login 协议 ServerIP port context root request.getServletPath() query
string
-
DispatcherServlet -> urlPattern : *.do 拦截/page.do
-
request.getServletPath() -> /page.do
-
解析处理字符串,将/page.do -> page
-
拿到 page 这个字符串,然后去 IOC 容器(BeanFactory)中寻找 id=page 的那个 bean 对象 -> PageController.java
-
获取 operate 的值 -> page 因此得知,应该执行 PageController 中的 page()方法
-
PageController 中的 page 方法定义如下:
public String page(String page){ return page ; }
-
在 queryString: ?operate=page&page=login 中 获取请求参数,参数名是 page,参数值是 login
因此 page 方法的参数 page 值会被赋上"login” 然后 return “login” , return 给 谁?? -
因为 PageController 的 page 方法是 DispatcherServlet 通过反射调用的 method.invoke(..), 因此,字符串"login"返回 DispatcherServlet
-
DispatcherServlet 接收到返回值,然后处理视图. 目前处理视图的方式有两种: 1.带前缀 redirect: 2.不带前缀 当前,返回"login",不带前缀 那么执行 super.processTemplete(“login”,request,response);
-
此时 ViewBaseServlet 中的 processTemplete 方法会执行,效果是:
在"login"这个字符串前面拼接 “/” (其实就是配置文件中 view-prefixe 配置的值)
在"login"这个字符串后面拼接 “.html” (其实就是配置文件中 view-suffix 配置的值) 最后进行服务器转发
javaweb 项目开发的 套路
-
- 拷贝 myssm 包
-
- 新建配置文件 applicationContext.xml 或者可以不叫这个名字,在 web.xml 中指定文件名
-
- 在 web.xml 文件中配置
-
配置前缀和后缀,这样 thymeleaf 引擎就可以根据我们返回的字符串进行拼接,再跳转
<context-param> <param-name>view-prefix</param-name> <param-value>/</param-value> </context-param> <context-param> <param-name>view-suffix</param-name> <param-value>.html</param-value> </context-param>
-
配置监听器要读取的参数,目的是加载 IOC 容器的配置文件(也就是 applicationContext.xml)
<context-param> <param-name>contextConfigLocation</param-name> <param-value>applicationContext.xml</param-value> </context-param>
-
- 开发具体的业务模块:
-
一个具体的业务模块纵向上由几个部分组成:
- html 页面
- POJO 类
- DAO 接口和实现类
- Service 接口和实现类
- Controller 控制器组件
-
如果 html 页面有 thymeleaf 表达式,一定不能够直接访问,必须要经过 PageController
-
在 applicationContext.xml 中配置 DAO、Service、Controller,以及三者之间的依赖关系
-
DAO 实现类中 , 继承 BaseDAO,然后实现具体的接口, 需要注意,BaseDAO 后面的泛型不能写错。 例如:
public class UserDAOImpl extends BaseDAO
implements UserDAO{}
-
- 开发具体的业务模块:
-
-
Service 是业务控制类,这一层我们只需要记住一点
- 业务逻辑我们都封装在 service 这一层,不要分散在 Controller 层。也不要出现在 DAO 层(我们需要保证 DAO 方法的单精度特性)
- 当某一个业务功能需要使用其他模块的业务功能时,尽量的调用别人的 service,而不是深入到其他模块的 DAO 细节
-
-
-
Controller 类的编写规则
- 在 applicationContext.xml 中配置 Controller
<bean id=“user” class=“com.atguigu.qqzone.controllers.UserController>
那么,用户在前端发请求时,对应的 servletpath 就是
/user.do
, 其中的user
就是对应此处的bean
的id
值- 在
Controller
中设计的方法名需要和operate
的值一致
public String login(String loginId , String pwd , HttpSession session){ return “index”; }
因此,我们的登录验证的表单如下
<inut type="hidden" name="operate" value="login"/>
- 在表单中,组件的 name 属性和 Controller 中方法的参数名一致
public String login(String loginId , String pwd , HttpSession session){
- 另外,需要注意的是: Controller 中的方法中的参数不一定都是通过请求参数获取的
if(“request”.equals…) else if(“response”.equals..) else if(“session”.equals..){
直接赋值
}else{
此处才是从 request 的请求参数中获取
request.getParameter(“loginId”) }
-
-
- DispatcherServlet 中步骤大致分为
-
- 从 application 作用域获取 IOC 容器
-
- 解析 servletPath , 在 IOC 容器中寻找对应的 Controller 组件
-
- 准备 operate 指定的方法所要求的参数
-
- 调用 operate 指定的方法
-
- 接收到执行 operate 指定的方法的返回值,对返回值进行处理 - 视图处理
-
- DispatcherServlet 中步骤大致分为
-
-
为什么 DispatcherServlet 能够从 application 作用域获取到 IOC 容器
ContextLoaderListener 在容器启动时会执行初始化任务,而它的操作就是:
- 解析 IOC 的配置文件,创建一个一个的组件,并完成组件之间依赖关系的注入
- 将 IOC 容器保存到 application 作用域
-
代码导出
-
- 修改 BaseDAO,让其支持 properties 文件以及 druid 数据源连接池 讲解了两种方式:
-
- 直接自己配置 properties,然后读取,然后加载驱动
public static String DRIVER; ... static { InputStream is = ConnUtil.class.getClassLoader().getResourcesAsStream("jdbc.properties"); Properties properties = new Properties(); try { properties.load(is); DRIVER = properties.getProperty("jdbc.driver"); ... } catch(IOException e) { ... } }
-
- 或使用 druid 连接池技术,那么 properties 中的 key 是有要求的
不用数据链接池的时候
private static Connection createConn() { try { // 1. 加载驱动 Class.forName(DRIVER); // 2. 通过驱动管理器获取连接对象 return DriverManager.getConnection(URL, USER, PWD); } }
用druid 后, 常用代码还是要熟练
private static Connection createConn() { try { DruidDataSource druidDataSource = new DruidDataSourc(); druidDataSource.setDriverClassName(DRIVER); druidDataSource.setDriverClassName(URL); druidDataSource.setDriverClassName(USER); druidDataSource.setDriverClassName(PWD); ... return druidDataSource.getConnection(); } }
工厂模式
```java
static Properties properties = new Properties();
static {
InputStream is = ConnUtil.class.getClassLoader().getResourcesAsStream("jdbc.properties");
try {
properties.load(is);
} catch(IOException e) {
...
}
}
private static Connection createConn() {
try {
DataSource druidDataSource = DruidDataSourceFactory. createDataSource(properties);
...
return druidDataSource.getConnection();
}
}
```