java web project--learning through building 5

Tue, Jan 12, 2021 8-minute read

a full-stack web application for users to understand java web system

QQZone 业务需求

业务需求

    1. 用户登录
    1. 登录成功,显示主界面。左侧显示好友列表;上端显示欢迎词。如果不是自己的空间,显示超链接:返回自己的空间;下端显示日志列表
    1. 查看日志详情:
      • 日志本身的信息(作者头像、昵称、日志标题、日志内容、日志的日期)
      • 回复列表(回复者的头像、昵称、回复内容、回复日期)
      • 主人回复信息
    1. 删除日志
    1. 删除特定回复
    1. 删除特定主人回复
    1. 添加日志、添加回复、添加主人回复
    1. 点击左侧好友链接,进入好友的空间

数据库设计

    1. 抽取实体 : 用户登录信息、用户详情信息 、 日志 、 回贴 、 主人回复
    1. 分析其中的属性:
    • 用户登录信息:账号、密码、头像、昵称
    • 用户详情信息:真实姓名、星座、血型、邮箱、手机号
    • 日志:标题、内容、日期、作者
    • 回复:内容、日期、作者、日志
    • 主人回复:内容、日期、作者、回复
image

servlets

    1. 分析实体之间的关系
    • 用户登录信息 : 用户详情信息 1:1 PK
    • 用户 : 日志 1:N
    • 日志 : 回复 1:N
    • 回复 : 主人回复 1:1 UK
    • 用户 : 好友 M : N

数据库的范式

    1. 第一范式:列不可再分 (地址就不能是,因为可分)
    1. 第二范式:一张表只表达一层含义(只描述一件事情)
    1. 第三范式:表中的每一列和主键都是直接依赖关系,而不是间接依赖

数据库设计的范式和数据库的查询性能很多时候是相悖的,我们需要根据实际的业务情况做一个选择

  • 查询频次不高的情况下,我们更倾向于提高数据库的设计范式,从而提高存储效率
  • 查询频次较高的情形,我们更倾向于牺牲数据库的规范度,降低数据库设计的范式,允许特定的冗余,从而提高查询的性能

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 项目开发的“套路”是这样的:

  1. 拷贝 myssm 包

  2. 新建配置文件 applicationContext.xml 或者可以不叫这个名字,在 web.xml 中指定文件名

  3. 在 web.xml 文件中配置:

    1. 配置前缀和后缀,这样 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>
    
    1. 配置监听器要读取的参数,目的是加载 IOC 容器的配置文件(也就是 applicationContext.xml)
      <context-param>
          <param-name>contextConfigLocation</param-name>
          <param-value>applicationContext.xml</param-value>
      </context-param>
    

😼😼😼😼😼😼😼😼

开发具体的业务模块

  1. 一个具体的业务模块纵向上由几个部分组成:

    • html 页面
    • POJO 类
    • DAO 接口和实现类
    • Service 接口和实现类
    • Controller 控制器组件

😼😼😼😼😼😼😼😼


step4. DAO


😼😼😼😼😼😼😼😼

业务需求

    1. 用户登录
    1. 登录成功,显示主界面。左侧显示好友列表;上端显示欢迎词。如果不是自己的空间,显示超链接:返回自己的空间;下端显示日志列表
    1. 查看日志详情:
      • 日志本身的信息(作者头像、昵称、日志标题、日志内容、日志的日期)
      • 回复列表(回复者的头像、昵称、回复内容、回复日期)
      • 主人回复信息
    1. 删除日志
    1. 删除特定回复
    1. 删除特定主人回复
    1. 添加日志、添加回复、添加主人回复
    1. 点击左侧好友链接,进入好友的空间

😼😼😼😼😼😼😼😼


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
image

DAO impl

image

DAO impl

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(); //判断如果是自定义类型,则需要调用这个自定义类的带一个参数的构造方法,创建出这个自定义的实例对象,然后将实例对象赋值给这个属性

汇总

  1. top.html 页面显示登录者昵称、判断是否是自己的空间

    1)显示登录者昵称: ${session.userBasic.nickName}

    2)判断是否是自己的空间 : ${session.userBasic.id!=session.friend.id}

    如果不是期望的效果,首先考虑将两者的 id 都显示出来

  2. 点击左侧的好友链接,进入好友空间

    1. 根据 id 获取指定 userBasic 信息,查询这个 userBasic 的 topicList,然后覆盖 friend 对应的 value

    2. main 页面应该展示 friend 中的 topicList,而不是 userBasic 中的 topicList

    3. 跳转后,在左侧(left)中显示整个 index 页面

      • 问题:在 left 页面显示整个 index 布局

      • 解决:给超链接添加 target 属性: target="_top” 保证在顶层窗口显示整个 index 页面

    4. top.html 页面需要修改: “欢迎进入${session.friend}”

      top.html 页面的返回自己空间的超链接需要修改:

  3. 日志详情页面实现

    1. 已知 topic 的 id,需要根据 topic 的 id 获取特定 topic

    2. 获取这个 topic 关联的所有的回复

    3. 如果某个回复有主人回复,需要查询出来

    • 在 TopicController 中获取指定的 topic

    • 具体这个 topic 中关联多少个 Reply,由 ReplyService 内部实现

    1. 获取到的 topic 中的 author 只有 id,那么需要在 topicService 的 getTopic 方法中封装,在查询 topic 本身信息时,同时调用 userBasicService 中的获取 userBasic 方法,给 author 属性赋值

    2. 同理,在 reply 类中也有 author,而且这个 author 也是只有 id,那么我们也需要根据 id 查询得到 author,最后设置关联

  4. 添加回复

  5. 删除回复

    1. 如果回复有关联的主人回复,需要先删除主人回复
    2. 删除回复 Cannot delete or update a parent row: a foreign key constraint fails (qqzonedb.t_host_reply, CONSTRAINT FK_host_reply FOREIGN KEY (reply) REFERENCES t_reply (id)) 我们在删除回复表记录时,发现删除失败,原因是:在主人回复表中仍然有记录引用待删除的回复这条记录 如果需要删除主表数据,需要首先删除子表数据
  6. 删除日志

    1. 删除日志,首先需要考虑是否有关联的回复
    2. 删除回复,首先需要考虑是否有关联的主人回复
    3. 另外,如果不是自己的空间,则不能删除日志

汇总

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

  1. DispatcherServlet -> urlPattern : *.do 拦截/page.do

  2. request.getServletPath() -> /page.do

  3. 解析处理字符串,将/page.do -> page

  4. 拿到 page 这个字符串,然后去 IOC 容器(BeanFactory)中寻找 id=page 的那个 bean 对象 -> PageController.java

  5. 获取 operate 的值 -> page 因此得知,应该执行 PageController 中的 page()方法

  6. PageController 中的 page 方法定义如下:

public String page(String page){ return page ; }

  1. 在 queryString: ?operate=page&page=login 中 获取请求参数,参数名是 page,参数值是 login
    因此 page 方法的参数 page 值会被赋上"login” 然后 return “login” , return 给 谁??

  2. 因为 PageController 的 page 方法是 DispatcherServlet 通过反射调用的 method.invoke(..), 因此,字符串"login"返回 DispatcherServlet

  3. DispatcherServlet 接收到返回值,然后处理视图. 目前处理视图的方式有两种: 1.带前缀 redirect: 2.不带前缀 当前,返回"login",不带前缀 那么执行 super.processTemplete(“login”,request,response);

  4. 此时 ViewBaseServlet 中的 processTemplete 方法会执行,效果是:

在"login"这个字符串前面拼接 “/” (其实就是配置文件中 view-prefixe 配置的值)

在"login"这个字符串后面拼接 “.html” (其实就是配置文件中 view-suffix 配置的值) 最后进行服务器转发

javaweb 项目开发的 套路

    1. 拷贝 myssm 包
    1. 新建配置文件 applicationContext.xml 或者可以不叫这个名字,在 web.xml 中指定文件名
    1. 在 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>
    
    1. 开发具体的业务模块:
      • 一个具体的业务模块纵向上由几个部分组成:

        • html 页面
        • POJO 类
        • DAO 接口和实现类
        • Service 接口和实现类
        • Controller 控制器组件
      • 如果 html 页面有 thymeleaf 表达式,一定不能够直接访问,必须要经过 PageController

      • 在 applicationContext.xml 中配置 DAO、Service、Controller,以及三者之间的依赖关系

      • DAO 实现类中 , 继承 BaseDAO,然后实现具体的接口, 需要注意,BaseDAO 后面的泛型不能写错。 例如:

        public class UserDAOImpl extends BaseDAO implements UserDAO{}

    1. Service 是业务控制类,这一层我们只需要记住一点

      • 业务逻辑我们都封装在 service 这一层,不要分散在 Controller 层。也不要出现在 DAO 层(我们需要保证 DAO 方法的单精度特性)
      • 当某一个业务功能需要使用其他模块的业务功能时,尽量的调用别人的 service,而不是深入到其他模块的 DAO 细节
    1. Controller 类的编写规则

      • 在 applicationContext.xml 中配置 Controller

      <bean id=“user” class=“com.atguigu.qqzone.controllers.UserController>

      那么,用户在前端发请求时,对应的 servletpath 就是 /user.do , 其中的 user 就是对应此处的beanid

      • 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”) }

    1. DispatcherServlet 中步骤大致分为
        1. 从 application 作用域获取 IOC 容器
        1. 解析 servletPath , 在 IOC 容器中寻找对应的 Controller 组件
        1. 准备 operate 指定的方法所要求的参数
        1. 调用 operate 指定的方法
        1. 接收到执行 operate 指定的方法的返回值,对返回值进行处理 - 视图处理
    1. 为什么 DispatcherServlet 能够从 application 作用域获取到 IOC 容器

      ContextLoaderListener 在容器启动时会执行初始化任务,而它的操作就是:
      
      • 解析 IOC 的配置文件,创建一个一个的组件,并完成组件之间依赖关系的注入
      • 将 IOC 容器保存到 application 作用域

代码导出

    1. 修改 BaseDAO,让其支持 properties 文件以及 druid 数据源连接池 讲解了两种方式:
      1. 直接自己配置 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) {
            ...
        }
    }
    
    
      1. 或使用 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();
     }
 }

```

javaweb2

javaweb3

javaweb4

javaweb5

spring

springmvc