尚硅谷JavaWeb笔记——书城项目(第五阶段:图书模块(课程精华!!!!))

墨蓝 2022-11-01 10:54 809阅读 0赞

文章目录

  • 第五阶段-图书模块
    • MVC说明
    • 开发流程
      • Step1:编写图书模块的数据库表
      • Step2:编写图书模块的JavaBean对象
      • Step3:编写图书模块Dao和测试Dao
        • 出现的问题:数据无法插入
      • Step4:编写图书模块的Service和测试Service
      • Step5:编写图书模块的Web层,和页面联调测试
        • 功能一:列出当前数据库中的全部图书信息
        • 功能二:添加图书
        • 功能三:删除图书
        • 功能四:修改图书信息
          • 第一步:数据回显
          • 第二步:提交修改
        • 几个需要注意的问题:
        • 疑惑点
      • Step6:进阶修改——图书分页
        • 构建`Page`对象
        • 修改`BookServlet`程序
        • 修改`BookService`程序
        • 修改`BookDao`程序
        • 代码测试
        • 页面功能的填充
          • 首页、上一页、下一页、末页功能的实现
          • 跳转到指定页
          • 页码点击效果实现
          • 增加页面后对其他功能的修改
            • 添加模块
            • 删除模块
            • 修改模块
      • Step7:根据价格搜索

第五阶段-图书模块

MVC说明

MVC全称:Model模型、View试图、Controller控制器

MVC最早出现在JavaEE三层中的Web层,它可以有效的指导Web层的代码如何有效分离,单独工作。

  • View试图:只负责数据和页面的显示,不接受任何与现实无关的代码,便于程序员和美工分工合作
  • Controller控制器:只负责接受请求,调用业务层的代码处理请求,然后派发页面,是“调度者”的角色——Servlet程序
  • Model模型:将与业务逻辑相关的数据封装为具体的JavaBean对象,其中不掺杂任何与数据处理相关的代码——JavaBean/domain/entity/pojo

⭐️MVC是一种思想:理念是将软件代码拆分成为组件,单独开发,组合使用(解耦合)

\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pBnTv5Es-1614146871617)(/Users/gaojunsong/Library/Application Support/typora-user-images/Java学习/JAVAWeb/image-20210216141124286.png)\]

开发流程

Step1:编写图书模块的数据库表

首先需要根据需求创建对应的sql表以及插入数据

  1. create table t_book(
  2. `id` int primary key auto_increment, # 注意这里是` 而不是 '
  3. `name` varchar(100),
  4. `price` decimal(11,2),
  5. `author` varchar(100),
  6. `sales` int,
  7. `stock` int,
  8. `img_path` varchar(200)
  9. );
  10. # 修改当前表的字符集
  11. alter table t_book convert to charset utf8;
  12. insert into t_book(`id`,`name`,`author`,`price`,`sales`,`stock`,`img_path`)
  13. values(null,'java从入门到放弃','国哥',80,9999,9,'static/img/default.jpg');
  14. insert into t_book(`id`,`name`,`author`,`price`,`sales`,`stock`,`img_path`)
  15. values(null,'数据结构与算法','严敏均',78.5,6,13,'static/img/default.jpg');
  16. insert into t_book(`id`,`name`,`author`,`price`,`sales`,`stock`,`img_path`)
  17. values(null,'怎样拐跑别人的媳妇','龙舞',68,99999,52,'static/img/default.jpg');
  18. insert into t_book(`id`,`name`,`author`,`price`,`sales`,`stock`,`img_path`)
  19. values(null,'木须肉盖饭','小胖',16,1000,50,'static/img/default.jpg');
  20. insert into t_book(`id`,`name`,`author`,`price`,`sales`,`stock`,`img_path`)
  21. values(null,'C++编程思想','岗子',45.5,14,95,'static/img/default.jpg');
  22. insert into t_book(`id`,`name`,`author`,`price`,`sales`,`stock`,`img_path`)
  23. values(null,'蛋炒饭','周新星',9.9,12,53,'static/img/default.jpg');
  24. insert into t_book(`id`,`name`,`author`,`price`,`sales`,`stock`,`img_path`)
  25. values(null,'赌神','龙舞',66.5,125,535,'static/img/default.jpg');
  26. insert into t_book(`id`,`name`,`author`,`price`,`sales`,`stock`,`img_path`)
  27. values(null,'java编程思想','氧割',99.5,47,36,'static/img/default.jpg');
  28. insert into t_book(`id`,`name`,`author`,`price`,`sales`,`stock`,`img_path`)
  29. values(null,'Javascript从入门到精通','婷姐',9.9,85,95,'static/img/default.jpg');
  30. insert into t_book(`id`,`name`,`author`,`price`,`sales`,`stock`,`img_path`)
  31. values(null,'cocos2d-x游戏编程入门','国哥',49,52,62,'static/img/default.jpg');
  32. insert into t_book(`id`,`name`,`author`,`price`,`sales`,`stock`,`img_path`)
  33. values(null,'C语言程序设计','谭浩强',28,52,74,'static/img/default.jpg');
  34. insert into t_book(`id`,`name`,`author`,`price`,`sales`,`stock`,`img_path`)
  35. values(null,'lua语言程序设计','雷锋杨',51.5,48,82,'static/img/default.jpg');
  36. insert into t_book(`id`,`name`,`author`,`price`,`sales`,`stock`,`img_path`)
  37. values(null,'水浒传','华仔',33.05,22,88,'static/img/default.jpg');
  38. insert into t_book(`id`,`name`,`author`,`price`,`sales`,`stock`,`img_path`)
  39. values(null,'西游记','罗贯中',12,19,9999,'static/img/default.jpg');
  40. insert into t_book(`id`,`name`,`author`,`price`,`sales`,`stock`,`img_path`)
  41. values(null,'操作系统原理','刘优',133.05,122,188,'static/img/default.jpg');
  42. insert into t_book(`id`,`name`,`author`,`price`,`sales`,`stock`,`img_path`)
  43. values(null,'数据结构java版','峰大神',173.15,21,81,'static/img/default.jpg');
  44. insert into t_book(`id`,`name`,`author`,`price`,`sales`,`stock`,`img_path`)
  45. values(null,'Unitx高级环境编程','乐天',99.15,210,810,'static/img/default.jpg');
  46. insert into t_book(`id`,`name`,`author`,`price`,`sales`,`stock`,`img_path`)
  47. values(null,'大话设计模式','国哥',89.15,20,10,'static/img/default.jpg');
  48. insert into t_book(`id`,`name`,`author`,`price`,`sales`,`stock`,`img_path`)
  49. values(null,'人间神话','刚哥',88.15,20,80,'static/img/default.jpg');
  50. select id,name,author,price,sales,stock,img_path from t_book;

于是数据库中存入如下信息

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzcyNTgxOA_size_16_color_FFFFFF_t_70

Step2:编写图书模块的JavaBean对象

根据对应的book对象

  1. public class Book {
  2. private Integer id;
  3. private String name;
  4. private String author;
  5. private BigDecimal price;
  6. private Integer sales;
  7. private Integer stock;
  8. private String imgPath = "static/img/default.jpg";
  9. public Book() {
  10. }
  11. public Book(Integer id, String name, String author, BigDecimal price, Integer sales, Integer stock, String imgPath) {
  12. this.id = id;
  13. this.name = name;
  14. this.author = author;
  15. this.price = price;
  16. this.sales = sales;
  17. this.stock = stock;
  18. if (imgPath != null && "".equals(imgPath)) {
  19. this.imgPath = imgPath;
  20. }
  21. }
  22. public Integer getId() {
  23. return id;
  24. }
  25. public void setId(Integer id) {
  26. this.id = id;
  27. }
  28. public String getName() {
  29. return name;
  30. }
  31. public void setName(String name) {
  32. this.name = name;
  33. }
  34. public String getAuthor() {
  35. return author;
  36. }
  37. public void setAuthor(String author) {
  38. this.author = author;
  39. }
  40. public BigDecimal getPrice() {
  41. return price;
  42. }
  43. public void setPrice(BigDecimal price) {
  44. this.price = price;
  45. }
  46. public Integer getSales() {
  47. return sales;
  48. }
  49. public void setSales(Integer sales) {
  50. this.sales = sales;
  51. }
  52. public Integer getStock() {
  53. return stock;
  54. }
  55. public void setStock(Integer stock) {
  56. this.stock = stock;
  57. }
  58. public String getImgPath() {
  59. return imgPath;
  60. }
  61. public void setImgPath(String imgPath) {
  62. if (imgPath != null && "".equals(imgPath)) {
  63. this.imgPath = imgPath;
  64. }
  65. }
  66. @Override
  67. public String toString() {
  68. return "book{" +
  69. "id=" + id +
  70. ", name='" + name + '\'' +
  71. ", author='" + author + '\'' +
  72. ", price=" + price +
  73. ", sales=" + sales +
  74. ", stock=" + stock +
  75. ", imgPath='" + imgPath + '\'' +
  76. '}';
  77. }
  78. }

Step3:编写图书模块Dao和测试Dao

根据业务需求设置对应的Dao接口(规定有哪些操作需要执行)

无非就是增删改+各种查询

  1. public interface BookDao {
  2. // 设置增删改查的方法
  3. public int addBook(Book book);
  4. // 删除
  5. public int deleteBookById(Integer id);
  6. // 修改
  7. public int updateBook(Book book);
  8. // 各种花式查询
  9. // 通过id查书
  10. public Book queryBookById(Integer id);
  11. // 查询所有书
  12. public List<Book> queryBooks();
  13. }

对应的Dao实现类代码如下:

  1. public class BookDaoImpl extends BaseDao implements BookDao {
  2. @Override
  3. // 添加一本书
  4. public int addBook(Book book) {
  5. // id自增,这里不需要添加
  6. String sql = "insert into t_book(`name` , `author` , `price` , `sales` , `stock` , `img_path`) values(?,?,?,?,?,?)";
  7. int i = update(sql, book.getName(), book.getAuthor(), book.getPrice(), book.getSales(), book.getStock(), book.getImgPath());
  8. return i;
  9. }
  10. // 通过ID删除书
  11. @Override
  12. public int deleteBookById(Integer id) {
  13. String sql = "DELETE FROM t_book WHERE id = ?";
  14. int i = update(sql, id);
  15. System.out.println("已经删除 " + i + " 本书····");
  16. return i;
  17. }
  18. // 修改某本书
  19. @Override
  20. public int updateBook(Book book) {
  21. System.out.println("BookDaoImpl updateBook 程序在[" +Thread.currentThread().getName() + "]中");
  22. String sql = "UPDATE t_book SET `name`=? , `author`=? , `price`=? , `sales`=? , `stock`=? , `img_path` = ? WHERE id = ?";
  23. int i = update(sql, book.getName(), book.getAuthor(), book.getPrice(), book.getSales(), book.getStock(), book.getImgPath(), book.getId());
  24. return i;
  25. }
  26. // 通过ID查询书
  27. @Override
  28. public Book queryBookById(Integer id) {
  29. // 这里由于需要返回查询的对象,需要使用别名来查询,不然无法查询到结果
  30. String sql = "SELECT `id` , `name` , `author` , `price` , `sales` , `stock` , `img_path` imgPath FROM t_book where id = ?";
  31. Book book = queryForOne(Book.class, sql, id);
  32. return book;
  33. }
  34. // 查询所有书
  35. @Override
  36. public List<Book> queryBooks() {
  37. String sql = "SELECT `id` , `name` , `author` , `price` , `sales` , `stock` , `img_path` imgPath FROM t_book";
  38. List<Book> books = queryForList(Book.class, sql);
  39. return books;
  40. }

出现的问题:数据无法插入

趁着记忆鲜活,记录一下刚刚出现的问题:数据无法在idea中通过对应java语句插入,经过debug后也没有发现问题出在哪,进入JDBC.Utils工具类中发现获取连接的代码如下:

  1. public static Connection getConnection3Druid() throws SQLException{
  2. Connection conn = conns.get();
  3. if (conn == null) {
  4. conn = source1.getConnection();//从数据库连接池中获取连接
  5. conns.set(conn); 保存到ThreadLocal对象中,供后面的jdbc操作使用 相对最后一次保存有效!
  6. conn.setAutoCommit(false);// 设置为手动管理事务
  7. }
  8. return conn;
  9. }

同时又发现虽然无法读取到数据,但通过mysql控制台插入的数据id不按照正确的顺序增加,也就是说先前在java中确实成功进入了数据库,并执行了对应的增加操作,但由于某些原因数据没能正确的插入进去。

同时又在网上看到,当设置手动提交数据后,需要记得commit提交数据,同时结合上述代码成功发现问题所在:

应当将conn.setAutoCommit(false);语句注释调,让其自动提交才能实现真正将数据插入进去。

同时修改properties中的配置信息,使得访问数据库时采用UTF-8的格式来编码数据。

Step4:编写图书模块的Service和测试Service

书模块Service层的接口规范

  1. public interface BookService {
  2. // 添加一本书
  3. public void addBook(Book book);
  4. // 删除一本书
  5. public void deleteBookById(Integer id);
  6. // 更新书的信息
  7. public void updateBook(Book book);
  8. // 通过ID查书
  9. public Book queryBookById(Integer id);
  10. // 查询所有书
  11. public List<Book> queryBooks();
  12. }

实现类:

  1. public class BookServiceimpl implements BookService {
  2. BookDao bookDao = new BookDaoImpl();
  3. // 添加一本书
  4. @Override
  5. public void addBook(Book book) {
  6. bookDao.addBook(book);
  7. }
  8. // 通过ID删除一本书
  9. @Override
  10. public void deleteBookById(Integer id) {
  11. bookDao.deleteBookById(id);
  12. }
  13. // 更新书的信息
  14. @Override
  15. public void updateBook(Book book) {
  16. bookDao.updateBook(book);
  17. }
  18. // 通过ID查询一本书
  19. @Override
  20. public Book queryBookById(Integer id) {
  21. return bookDao.queryBookById(id);
  22. }
  23. // 查询所有书
  24. @Override
  25. public List<Book> queryBooks() {
  26. return bookDao.queryBooks();
  27. }
  28. }

Step5:编写图书模块的Web层,和页面联调测试

web层的请求响应流程如下:在web层中,用户请求的数据内容并不能直接在jsp中获取,而是需要先访问对应的Servlet程序,Servlet从数据库中得到数据后,将数据保存在request域(同次请求有效),将获取到的数据联通请求一起请求响应给对应jsp页面,jsp页面再从request域中获取Servlet查询到的数据,将数据动态响应至页面上。

整个过程的流程示意图以及对应模块所做的具体内容如下图所示:

\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZfIhNQwq-1614146871620)(/Users/gaojunsong/Library/Application Support/typora-user-images/Java学习/JAVAWeb/image-20210216164904744.png)\]

先前讲过每一个模块都可以继承BaseServlet完成请求的分发响应,BookServlet也不例外。在BookServlet中同样只需要定义对应请求的操作即可,代码如下:

  1. public class BookServlet extends BaseServlet {
  2. // 调用Service层的对象实例
  3. private BookService bookService = new BookServiceimpl();
  4. // 增图书操作
  5. protected void add(HttpServletRequest request, HttpServletResponse response){ }
  6. // 删图书操作
  7. protected void delete(HttpServletRequest request, HttpServletResponse response) { }
  8. // 改图书操作
  9. protected void update(HttpServletRequest request, HttpServletResponse response) { }
  10. // 查询一本图书操作
  11. protected void getBook(HttpServletRequest request, HttpServletResponse response) { }
  12. // 查询所有图书操作
  13. protected void list(HttpServletRequest request, HttpServletResponse response)}
  14. }

功能一:列出当前数据库中的全部图书信息

  1. protected void list(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  2. // 1、通过BookService查询全部图书
  3. List<Book> books = bookService.queryBooks();
  4. // 2、把全部图书保存到Request域中
  5. request.setAttribute("books", books);
  6. // 3、请求转发到/pages/manager/book_manager.jsp页面
  7. request.getRequestDispatcher("/pages/manager/book_manager.jsp").forward(request,response);
  8. }

说明:由于我们最先呈现出的是有所有图书信息的,故最先需要实现的功能是list功能,然后让所有其他功能嵌入其中。

功能二:添加图书

  1. protected void add(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  2. // 1、获取请求的参数==封装成为Book对象
  3. Book book = WebUtils.copyParamToBean(request.getParameterMap(), new Book());
  4. // 2、调用BookService.addBook()保存图书
  5. bookService.addBook(book);
  6. // 3、跳到图书列表页面
  7. // /manager/bookServlet?action=list
  8. // request.getRequestDispatcher("/manager/bookServlet?action=list").forward(request, response);
  9. response.sendRedirect(request.getContextPath() + "/manager/bookServlet?action=list");
  10. }

关于添加图书的流程以及注意要点说明:

\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PahDztZr-1614146871623)(/Users/gaojunsong/Library/Application Support/typora-user-images/Java学习/JAVAWeb/image-20210217112156054.png)\]

流程说明:具体地,在07_book/manager/bookServlet?action=list页面中,点击添加图书按钮后会跳到07_book/pages/manager/book_edit.jsp页面,然后将需要添加的书的信息传给BookServlet程序中,并调用add方法将书存入数据库,存入完成后,再次返回07_book/manager/bookServlet?action=list,刷新显示添加了图书后的所有图书信息。

注意要点:BookServlet添加成功后返还的页面不能使用请求转发来传递信息,即下方代码

  1. request.getRequestDispatcher("/manager/bookServlet?action=list").forward(request, response);

如果使用请求转发,会出现表单重复提交的bug

  • 当用户提交完请求,浏览器会记录下最后一次请求的全部信息,当用户按F5刷新页面时,就会发起浏览器记录的最后一次请求(这也是每次进入F12查看Network后点击刷新能看到上次请求的原因)。而最后一次请求内容就是add操作(因为请求转发始终都是一个请求add)

修改方法:将请求转发更改为请求重定向,重新发起进入显示页面的请求,这样当在对应页面再点击刷新时就只会有一个显示当前页面的请求

  1. response.sendRedirect(request.getContextPath() + "/manager/bookServlet?action=list");
  • 此外,请求重定向需要当前工程的全部路径

功能三:删除图书

和添加图书模块的过程类似,删除图书的过程可由下图展示

\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7Cpr3Rus-1614146871624)(/Users/gaojunsong/Library/Application Support/typora-user-images/Java学习/JAVAWeb/image-20210217131822309.png)\]

用户在http://localhost:8080/07_book/manager/bookServlet?action=list页面选择要删除的图书后,在该页面就会通过标签通过get请求访问bookServlet页面,同时带着要删除的书的id信息,请求参数为action=delete。对应的jsp页面代码如下:

  1. <td><a href="manager/bookServlet?action=delete&id=${book.id}">删除</a> </td>

bookServlet收到上述请求参数后就会调用在doget()方法中调用对应的delete()方法,从request域中获取id信息,再调用Service层中对应的方法完成对数据库的操作。最后再将请求重定向至初始页面。

  1. protected void delete(HttpServletRequest request, HttpServletResponse response) throws IOException {
  2. // 1、获取请求的参数id,图书编程
  3. int id = WebUtils.parseInt(request.getParameter("id"), 0);
  4. // 2、调用bookService.deleteBookById();删除图书
  5. bookService.deleteBookById(id);
  6. // 3、重定向回图书列表管理页面
  7. // /book/manager/bookServlet?action=list
  8. response.sendRedirect(request.getContextPath() + "/book/manager/bookServlet?action=list";
  9. }

注意:BookServlet类中的delete方法根据id删除对应数据时需要完成String类型向int类型的转换,转换函数在WebUtil类中定义

  1. public static int parseInt(String strInt, int defaultValue) {
  2. try {
  3. return Integer.parseInt(strInt);
  4. } catch (NumberFormatException e) {
  5. // e.printStackTrace();
  6. }
  7. return defaultValue;
  8. }

此外,为了防止用户误触碰到删除按钮,还需要给用户一个提示框,用于询问是否删除该对象,因此需要在book_manager.jsp中加入如下内容:

  1. <script type="text/javascript">
  2. $(function () {
  3. // 给删除的a标签绑定单击事件,用于删除的确认提示操作
  4. $("a.deleteClass").click(function () {
  5. // 在事件的function函数中,有一个this对象。这个this对象,是当前正在响应事件的dom对象。
  6. /** * confirm是确认提示框函数 * 参数是它的提示内容 * 它有两个按钮,一个确认,一个是取消。 * 返回true表示点击了,确认,返回false表示点击取消。 */
  7. // 取两次父元素,再找该父元素的第一个元素,就对应到书名来
  8. return confirm("你确定删除出【" + $(this).parent().parent().find("td:first").text() + "】?");
  9. // return false// 阻止元素的默认行为===不提交请求
  10. })
  11. })
  12. </script>
  13. <!-- 对应的td标签如下。-->
  14. <td><a class= "deleteClass" href="manager/bookServlet?action=delete&id=${book.id}">删除</a> </td>

功能四:修改图书信息

修改图书时分为两个步骤

  1. 把修改的图书信息回显到表单项
  2. 提交修改后的数据给服务器保存修改
第一步:数据回显

当我们在显示所有图书的页面07_book/pages/manager/manager.jsp点击修改时

\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hjpYaMux-1614146871625)(/Users/gaojunsong/Library/Application Support/typora-user-images/Java学习/JAVAWeb/image-20210217170120560.png)\]

我们实际上时,我们会进入一个新的修改页面07_book/pages/manager/book_edit.jsp

\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LtRwwKVM-1614146871626)(/Users/gaojunsong/Library/Application Support/typora-user-images/Java学习/JAVAWeb/image-20210217170248475.png)\]

但我们并非是直接进入,而是先在第一个页面将我们需要修改的请求数据发送给BookServlet程序,后者通过请求数据的id信息从数据库中获取数据后,将对应的数据回显至后面的页面中供我们修改,这个过程可用如下流程框图实现

\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vdgswGFO-1614146871627)(/Users/gaojunsong/Library/Application Support/typora-user-images/Java学习/JAVAWeb/image-20210217170604997.png)\]

因此,在BookServlet类中应当定义一个getBook()方法,代码如下

  1. protected void getBook(HttpServletRequest request, HttpServletResponse response) {
  2. // 1、获取请求的参数图书编号
  3. int id = WebUtils.parseInt(request.getParameter("i"), 0);
  4. // 2、调用bookService.queryBookById查询图书
  5. Book book = bookService.queryBookById(id);
  6. // 3、保存到图书到Request域中
  7. request.setAttribute("book", book);
  8. // 4、请求转发到。pages/manager/book_edit.jsp页面
  9. request.getRequestDispatcher("/pages/manager/book_edit.jsp").foward(request,response);
  10. }

该方法通过id数据从数据库中获取对应的图书,并将请求响应转发给/pages/manager/book_edit.jsp,从而实现回显功能

第二步:提交修改

当在上述页面点击提交按钮后,会把表单信息再次通过Post请求发送给BookServlet程序,并调用update方法。后者会将请求发送的数据封装称为一个完整的JavaBean对象,执行bookService中的updateBook方法完成对数据库中数据的修改,最后将此次请求转发给"/manager/bookServlet?action=list"。对应的源代码如下:

  1. protected void update(HttpServletRequest request, HttpServletResponse response) {
  2. // 1、获取请求的参数==封装成为Book对象
  3. Book book = WebUtils.copyParamToBean(request.getParameterMap(), new Book());
  4. // 2、调用BookService.updateBook( book );修改图书
  5. bookService.updateBook(book);
  6. // 3、重定向回图书列表管理页面
  7. // 地址:/工程名/manager/bookServlet?action=list
  8. request.getRequestDispatcher("/manager/bookServlet?action=list").forward(request,response);
  9. }

然后和添加删除图书一样,用户刷新页面只会保留最后一次请求——显示所有图书

⭐️上述过程会出现的小问题

问题一:页面复用:对于/pages/manager/book_edit.jsp页面,增加图书和修改图书都会访问该页面。因此需要根据两者访问该页面时的不同之处设计运行逻辑实现复用。

  • 方法一:通过方法名。可以分别给add请求和update增加一个属性值methos,分别对应不同的响应,对应的修改如下

    • 修改请求:

      1. <td><a href="client/bookServlet?action=getBook&id=${book.id}&method=update">修改</a></td>--%>
    • 添加请求:

      1. <td><a href="pages/manager/book_edit.jsp?method=update">添加图书</a></td>
    • book_edit.jsp页面的逻辑判断
  • 方法二:判断是否有book信息(以id值为key)

    • 如果当前请求域中包含有id信息,就说明时update方法,否则就是add方法

      1. <input type="hidden" name="action" value="${empty param.id ? "add" : "update"}" />

问题二:请求id缺失

注意到,如果是添加书,并不需要id信息,但如果是查询书,还需要将id信息发送给BookServlet。然而修改页面中并没有id信息,因此需要在表单的隐藏域中增加id信息,以供查询使用

  1. <form action="manager/bookServlet" method="get">
  2. <input type="hidden" name="action" value="${empty param.id ? "add" : "update"}" />
  3. ⭐️<input type="hidden" name="id" value="${requestScope.book.id}" />⭐️
  4. <table>
  5. <tr>
  6. <td>名称</td>
  7. <td>价格</td>
  8. <td>作者</td>
  9. <td>销量</td>
  10. <td>库存</td>
  11. <td colspan="2">操作</td>
  12. </tr>
  13. <tr>
  14. <td><input name="name" type="text" value="${requestScope.book.name}"/></td>
  15. <td><input name="price" type="text" value="${requestScope.book.price}"/></td>
  16. <td><input name="author" type="text" value="${requestScope.book.author}"/></td>
  17. <td><input name="sales" type="text" value="${requestScope.book.sales}"/></td>
  18. <td><input name="stock" type="text" value="${requestScope.book.stock}"/></td>
  19. <td><input type="submit" value="提交"/></td>
  20. </tr>
  21. </table>
  22. </form>

几个需要注意的问题:

问题一:通过标签进入的都是get请求,但如果仍然希望通过get得到post请求时应当将post请求嵌入get请求中,同时为了避免出现乱码的问题,需要在获得请求的时候就设置当前请求的编码格式(一定要在最开始设置)

  1. protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  2. doPost(request, response);
  3. }
  4. protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  5. // 解决post请求中文乱码问题
  6. // 一定要在获取请求参数之前调用才有效
  7. request.setCharacterEncoding("UTF-8");
  8. // 解决响应中文乱码
  9. response.setContentType("text/html; charset=UTF-8");
  10. ...
  11. }

问题二:为什么bookServlet网页的访问前要加上/manager/,内容如下:

  1. <servlet>
  2. <servlet-name>BookServlet</servlet-name>
  3. <servlet-class>web.BookServlet</servlet-class>
  4. </servlet>
  5. <servlet-mapping>
  6. <servlet-name>BookServlet</servlet-name>
  7. <url-pattern>/manager/bookServlet</url-pattern>
  8. </servlet-mapping>

这是因为在开发中有着前台和后台之分

  • 前台:给普通用户使用。一般不需要权限检查,就可以访问的资源,或者功能都属于前台功能。比如:淘宝不需要登陆就可以访问首页,其对应的地址是/cliednt/bookServlet
  • 后台:给管理员使用。一般都会有权限检查,才能访问的资源、页面或功能都是后台。对应的地址是manager/bookServlet

疑惑点

疑惑点一:在请求重转发中如果不加forward会怎么样

Step6:进阶修改——图书分页

图书分页的流程与说明如下图所示;

\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OzlSZX8n-1614146871628)(/Users/gaojunsong/Library/Application Support/typora-user-images/Java学习/JAVAWeb/image-20210217195432229.png)\]

思维过程:分页相关数据(构成一个类)—>>思考类中每个数据如何计算—>>>考虑相关类如何在三层架构中实现

构建Page对象

  1. /** * Page是分页的模型对象 * @param <T> 是具体的模块的javaBean类 */
  2. public class Page<T> {
  3. public static final Integer PAGE_SIZE = 4;
  4. //当前页码
  5. private Integer pageNo;
  6. //总页码
  7. private Integer pageTotal;
  8. //当前页显示的数量
  9. private Integer pageSize = PAGE_SIZE;
  10. //总记录数
  11. private Integer pageTotalCount;
  12. //当前页数据
  13. private List<T> item; // 这里为了该页面
  14. //这是分页条的请求地址
  15. private String url;
  16. ...对应的get set toString方法
  17. // 注意这里
  18. public void setPageNo(Integer pageNo) {
  19. //数据边界的有效检查
  20. if (pageNo < 1) {
  21. pageNo = 1;
  22. }
  23. if (pageNo > pageTotal) {
  24. pageNo = pageTotal;
  25. }
  26. this.pageNo = pageNo;
  27. }
  28. }
  • 说明:为例提高复用性,这里最好使用泛型

修改BookServlet程序

首先需要注意到,之前在进入页面的请求是:/manager/bookServlet?action=list,但要求每次进入该页面都应当使用的page方法,故这里应当修改为action=page

根据先前建立的计算模型,当前page()方法接收到来自jsp的请求(包含PageNo以及PageSize);将这些数据作为参数,向下调用BookService层的page方法,请求获取当前参数下对应的Item数据;再将得到的数据信息装入request域中;最后再将该request域的数据转发给"/page/manager/book_manager.jsp"

对应源代码如下:

  1. protected void page(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  2. //1 获取请求的参数 pageNo 和 pageSize
  3. int pageNo = WebUtils.parseInt(request.getParameter("pageNo"), 1);//如果没有传数值,默认第一页
  4. int pageSize = WebUtils.parseInt(request.getParameter("pageSize"), Page.PAGE_SIZE);//如果传值类就用所传数据,否则用默认值
  5. //2 调用BookService.page(pageNo,pageSize):Page对象
  6. Page<Book> page = bookService.page(pageNo, pageSize);
  7. page.setUrl("manager/book/Servlet?action=page");
  8. //3 保存Page对象到Request域中
  9. request.setAttribute("page", page);
  10. //4 请求转发到pages/manager/book_manager.jsp页面
  11. request.getRequestDispatcher("/page/manager/book_manager.jsp").forward(request, response);
  12. }

修改BookService程序

根据上层的讨论,首先需要再BookService接口增加对应的方法规范

  1. public Page<Book> page(int pageNo, int pageSize);

然后再在BookServiceImpl中实现该方法

  1. @Override
  2. public Page<Book> page(int pageNo, int pageSize) {
  3. Page<Book> page = new Page<>();
  4. // 设置每页显示的数量
  5. page.setPageSize(pageSize);
  6. // 求总记录数
  7. Integer pageTotalCount = bookDao.qureyForPageTotalCount();
  8. // 设置总记录数
  9. page.setPageTotal(pageTotalCount);
  10. // 求总页码
  11. int pageTotal = pageTotalCount / pageSize;
  12. if (pageTotalCount / pageSize > 0) {
  13. pageTotal++;
  14. }
  15. // 设置总页码
  16. page.setPageTotal(pageTotal);
  17. // 设置当前页码
  18. page.setPageNo(pageNo);
  19. // 求当前页数据的开始索引
  20. int begin = (page.getPageNo() - 1) * pageSize;
  21. // 求当前页数据
  22. List<Book> items = bookDao.qureyForPageItems(begin, pageSize);
  23. // 设置当前页数据
  24. page.setItem(items);
  25. return page;
  26. }

说明:这里是对web层的进一步封装,只需要接受web层传来的两个参数pageNo以及pageSize,即可得到完整的页面信息类page类。在该方法中需要完成对Dao层的调用,重点需要获取的是PageTotalCount,以及pageItems的内容(调用两个sql语句)。

这里需要提前思考号如何规范地调用sql语句

  1. select from t_book limit begin,size

修改BookDao程序

在BookDao接口中增加对应的方法规范

  1. public Integer qureyForPageTotalCount();//定义查询页面总数的方法
  2. List<Book> qureyForPageItems(int begin, int pageSize); //定义查询从begin开始到begin+pageSize的数据项的内容

在BookDaoImpl实现类中增加该方法的具体实现方法

  1. @Override
  2. public Integer qureyForPageTotalCount() {
  3. String sql = "select count(*) from t_book";
  4. Number count = (Number) queryForSingleValue(sql);
  5. return count.intValue();
  6. }
  7. @Override
  8. public List<Book> qureyForPageItems(int begin, int pageSize) {
  9. String sql = "SELECT `id` , `name` , `author` , `price` , `sales` , `stock` , `img_path` imgPath FROM t_book limit ?,?";
  10. return queryForList(Book.class, sql, begin, pageSize);
  11. }

代码测试

从小单元向大单元测试,web层内容需要加断点联动web页面测试

因此在显示页面,将不再使用访问list的方式来获取数据,而是指请求当前request域中的page.item中的图书信息

  1. <c:forEach items="${requestScope.page.item}" var="book">
  2. <tr>
  3. <td>${book.name}</td>
  4. <td>${book.price}</td>
  5. <td>${book.author}</td>
  6. <td>${book.sales}</td>
  7. <td>${book.stock}</td>
  8. </tr>
  9. <td><a href="manager/bookServlet?action=getBook&id=${book.id}">修改</a> </td>
  10. <td><a class= "deleteClass" href="manager/bookServlet?action=delete&id=${book.id}">删除</a> </td>
  11. </tr>
  12. </c:forEach>

页面功能的填充

首页、上一页、下一页、末页功能的实现

如果当前页是不是在首页,则可以有首页与上一页

  1. <c:if test="${requestScope.page.pageNo > 1}">
  2. <a href="${requestScope.page.url}&pageNO=1">首页</a>
  3. <a href="${requestScope.page.url}&pageNO=${requestScope.page.pageNo - 1}">上一页</a>
  4. </c:if>

如果当前页不是在末页,则可以有末页与下一页

  1. <c:if test="${requestScope.page.pageNo < requestScope.page.pageTotal}">
  2. <a href="${requestScope.page.url}&pageNO=${requestScope.page.pageNo + 1}">下一页</a>
  3. <a href="${requestScope.page.url}&pageNO=${requestScope.page.pageTotalCount}">末页</a>
  4. </c:if>
跳转到指定页

跳到指定页码的功能实际上是通过location.href方法重写覆盖当前页面的url地址实现,因此只需要在绑定了的单击事件中根据参数

  1. 到第<input value="${param.pagNo}" name="pn" id="pn_input"/>页
  2. <input id="searchPageBtn" type="button" value="确定">
  3. <script type="text/javascript"> $(function () { $("#searchPageBtn").click(function () { //跳到指定的 页码 jsp var pageNo = $("#pn_input").val(); var pageTotal = ${ requestScope.page.pageTotal}; if(pageNo>0&&pageNo<pageTotal) //js 提供了 一个location地址栏 对象 //它有一个属性叫herf,可以获取浏览器地址栏中的地址 //herf属性 可读 可写 location.href = "${pageScope.basePath}${requestScope.page.url}&pageNo=" + pageNo; else{ alert("输入页码不合法!"); return false; } }) }) </script>

注意,这里location.href使用的路径需要达到当前工程路径,同时为了防止登陆ip对ip造成影响,这里应该使用basePath动态获取

页码点击效果实现

该部分要完成的功能是点击页码时能够跳转到对应页面,同时也需要根据当前页码所在值显示一定范围的页码值域。需要考虑如下逻辑:

  • 情况一:当前页码总数<5(某个特定值,这里以5为例):显示全部页码,高亮当前所在页面并使其不可点击。
  • 情况二:当前页码总数>5

    • **子情况一:当前页码<3:**显示]前5个页码
    • 子情况二:当前页码>pageTotal-3:显示最后5个页码
    • 子情况三:3<当前页码<pageTotal-3:显示pageNo-2pageNo+2个页码

根据上述逻辑可以得到如下代码

  1. <c:choose>
  2. <%-- 情况1 假如总页码 小于 5 页码的范围 1 - 总页码 --%>
  3. <c:when test="${requestScope.page.pageTotal <= 5}">
  4. <c:set var="begin" value="1" />
  5. <c:set var="end" value="${requestScope.page.pageTotal}"/>
  6. <%-- 遍历输出一串数字 --%>
  7. </c:when>
  8. <%-- 情况2 假如总页码 大于 5--%>
  9. <c:when test="${requestScope.page.pageTotal > 5}">
  10. <c:choose>
  11. <%-- 小情况1 当前页码为1 2 3的情况 页码的范围 1 - 5--%>
  12. <c:when test="${requestScope.page.pageNo<3}">
  13. <c:set var="begin" value="${1}"/>
  14. <c:set var="end" value="${5}"/>
  15. </c:when>
  16. <%-- 小情况2 当前页码为后3个的情况 页码的范围 总页码-4 - 总页码--%>
  17. <c:when test="${requestScope.page.pageNo>requestScope.page.pageTotal-3}">
  18. <c:set var="begin" value="${requestScope.page.pageTotal-4}"/>
  19. <c:set var="end" value="${requestScope.page.pageTotal}"/>
  20. </c:when>
  21. <%-- 小情况3 当前页码为每5个后2个的情况 页码的范围 总页码-2 - 总页码+2--%>
  22. <c:otherwise>
  23. <c:set var="begin" value="${requestScope.page.pageNo-2}"/>
  24. <c:set var="end" value="${requestScope.page.pageNo+2}"/>
  25. </c:otherwise>
  26. </c:choose>
  27. </c:when>
  28. </c:choose>
  29. <%-- 遍历执行模块 --%>
  30. <c:forEach begin="${begin}" end="${end}" var="i">
  31. <c:if test="${i == requestScope.page.pageNo}">
  32. 【${i}】
  33. </c:if>
  34. <%-- 不是当前页码--%>
  35. <c:if test="${i != requestScope.page.pageNo}">
  36. <a href="manager/bookServlet?action=page&pageNo=${i}">${i}</a>
  37. </c:if>
  38. </c:forEach>
增加页面后对其他功能的修改
添加模块

添加需求:当添加一条数据后,需要返回当前数据所在页面

需要在BookServlet中添加如下代码(提前设置了页面溢出检测

  1. int pageNo = WebUtils.parseInt(request.getParameter("pageNo"), 0);
  2. pageNo++;
  3. // 让请求带着pageNo信息重定向至 page方法
  4. response.sendRedirect(request.getContextPath() + "/manager/bookServlet?action=page&pageNo=" + pageNo);
删除模块

添加需求:当删除数据后,维持在删除数据的页面

  1. //让请求带着pageNo信息 重定向至 page方法
  2. response.sendRedirect(request.getContextPath() + "/manager/bookServlet?action=page&pageNo="+request.getParameter("pageNo"));
修改模块

添加需求:当修改数据后,维持在修改数据的页面

  1. // 让请求带着pageNo信息 转发至 page方法
  2. request.getRequestDispatcher("/manager/bookServlet?action=page&pageNo="+request.getParameter("pageNo")).forward(request,response);

此外,还需要在book_edit.jsp中添加一个隐藏于存储pageNo

  1. <input type="hidden" name="pageNo" value="${param.pageNo}">

当把信息传递给add以及update方法时,同时需要把pageNo信息保存在request域中供对应方法调用。

出现的问题

忘记在/client/index.jsp中添加<%@include file="/pages/common/head.jsp" %>,造成相对路径错误。

Step7:根据价格搜索

需求:根据用户输入的价格区间,得到价格在区间内的全部图书信息

对应执行流程如下图所示,需求分析从web层反向进入Dao层

\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IPM7RfNz-1614146871629)(/Users/gaojunsong/Library/Application Support/typora-user-images/Java学习/JAVAWeb/image-20210218153144434.png)\]

和实现分页功能时的思考方式相同,结合每层的供鞥综合分析各个层的输入输出

Web层——该层需要获取用户输入的minmaxpageNopageSize信息,得到当前页面应当得到的对应的物品信息(page)。

  1. public void pageByPrice(){
  2. 1. 请求获取参数的`pageNo``pageSize``min`,`max`信息
  3. 2. 调用`bookService.pageByPrice(pageNo,pageSize,min,max):Page`方法
  4. 3. 保存分页对象`page``Request`域中
  5. 4. 将请求转发到`/page/client/index.jsp`页面
  6. }

Sercive层——进一步抽取Web层的数据,将Web层需要的功能封装成BookService函数:构建一个Page类,存放了页码,等Page信息

  1. public Page pageByPrice(pageNo,pageSize,min,max){
  2. 主要求三个数据:总记录数,总页码,当前页面数据
  3. 总记录数:调用 queryForPageTotalCount(min,max) // 获取当前价格区间内页面的总数
  4. 当前页面数据:调用 queryForPageItems(begin,size,min,max) //获取当前价格区间的数据
  5. }

DAO层——封装sql语句

  1. public Integer queryForPageTotalCountByPrice(int min,int max){
  2. String sql = "select count(*) from t_book where where price between ? and ?";
  3. Number count = (Number)queryForSingleValue(sql,min,max);
  4. return count.intValue();
  5. }
  6. public List<Book> queryForPageItemsByPrice(int begin,int pageSize,int min,int max){
  7. String sql = "SELECT `id` , `name` , `author` , `price` , `sales` , `stock` , `img_path` imgPath " +
  8. "FROM t_book where price between ? and ? order by price limit ?,?";
  9. return queryForList(Book.class,sql,min,max,begin,pageSize);
  10. }

Service层

  1. public Page pageByPrice(pageNo,pageSize,min,max){
  2. Page page = new Page();
  3. // 设置当前页码
  4. page.setPageNo(pageNo);
  5. // 设置当前页面大小
  6. page.setPageNo(pageSize);
  7. // 设置总记录数
  8. page.setPageTotalCount(bookDao.queryForPageTotalCount(min,max));
  9. // 设置总页码数
  10. int pageTotal = page.PageTotalCount/pageSize;
  11. if(page.PageTotalCount%pageSize>0)
  12. pageTotal++;
  13. // 求当前页面数据开始的索引,如果当前页为1,则查询从0开始的4条数据,如果是2,则查从4开始的4条数据
  14. int begin = (page.getPageNo()-1)*page.Size;
  15. List<Book> = bookDao.queryForPageItems(begin,size,min,max) //获取当前价格区间的数据
  16. //至此,Page中6项数据在这里完成5项的设置,最后一项path在Web层设置
  17. return page;
  18. }

Web层

  1. public void pageByPrice(){
  2. // 1. 请求获取参数的`pageNo`,`pageSize`,`min`,`max`信息
  3. int pageNo = request.getParameter("pageNo");
  4. int pageSize = request.getParameter("pageSize");
  5. int min = WebUtils.parseInt(request.getParameter("min"), 0);
  6. int max = WebUtils.parseInt(request.getParameter("max"), Integer.MAX_VALUE);
  7. // 2. 调用`bookService.pageByPrice(pageNo,pageSize,min,max):Page`方法
  8. Page<page> page = bookService.pageByPrice(pageNo,pageSize,min,max);
  9. //⭐️除了记录page数据,还需要将max和min数据回显给用户因此需要在这里设置url地址
  10. StringBuilder sb = new StringBuilder("client/bookServlet?action=pageByPrice");
  11. if(min!=null){
  12. sb.append("&min=").append(request.getParameter("min"));
  13. }
  14. if(max!=null){
  15. sb.append("&max=").append(request.getParameter("max"));
  16. }
  17. page.setUrl(sb);
  18. // 3. 保存分页对象`page`到`Request`域中
  19. request.setAttribute("page",page);
  20. // 4. 将请求转发到`/page/client/index.jsp`页面
  21. request.getRequestDispatcher("/pages/client/index.jsp").foward(request,response);

⚠️Web层中request.getParameter()方法是用于获取前台传入后台的数据,request.setAttribute()方法则是用于后台设置数据发送给前台

小结:对于某个需求如何实现,需要先从Web层一步步分析到Dao层。然后再反向一步步实现——测试,最后到达Web层。

发表评论

表情:
评论列表 (有 0 条评论,809人围观)

还没有评论,来说两句吧...

相关阅读