尚硅谷JavaWeb笔记——书城项目(第五阶段:图书模块(课程精华!!!!))
文章目录
- 第五阶段-图书模块
- 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是一种思想:理念是将软件代码拆分成为组件,单独开发,组合使用(解耦合)
开发流程
Step1:编写图书模块的数据库表
首先需要根据需求创建对应的sql表以及插入数据
create table t_book(
`id` int primary key auto_increment, # 注意这里是` 而不是 '
`name` varchar(100),
`price` decimal(11,2),
`author` varchar(100),
`sales` int,
`stock` int,
`img_path` varchar(200)
);
# 修改当前表的字符集
alter table t_book convert to charset utf8;
insert into t_book(`id`,`name`,`author`,`price`,`sales`,`stock`,`img_path`)
values(null,'java从入门到放弃','国哥',80,9999,9,'static/img/default.jpg');
insert into t_book(`id`,`name`,`author`,`price`,`sales`,`stock`,`img_path`)
values(null,'数据结构与算法','严敏均',78.5,6,13,'static/img/default.jpg');
insert into t_book(`id`,`name`,`author`,`price`,`sales`,`stock`,`img_path`)
values(null,'怎样拐跑别人的媳妇','龙舞',68,99999,52,'static/img/default.jpg');
insert into t_book(`id`,`name`,`author`,`price`,`sales`,`stock`,`img_path`)
values(null,'木须肉盖饭','小胖',16,1000,50,'static/img/default.jpg');
insert into t_book(`id`,`name`,`author`,`price`,`sales`,`stock`,`img_path`)
values(null,'C++编程思想','岗子',45.5,14,95,'static/img/default.jpg');
insert into t_book(`id`,`name`,`author`,`price`,`sales`,`stock`,`img_path`)
values(null,'蛋炒饭','周新星',9.9,12,53,'static/img/default.jpg');
insert into t_book(`id`,`name`,`author`,`price`,`sales`,`stock`,`img_path`)
values(null,'赌神','龙舞',66.5,125,535,'static/img/default.jpg');
insert into t_book(`id`,`name`,`author`,`price`,`sales`,`stock`,`img_path`)
values(null,'java编程思想','氧割',99.5,47,36,'static/img/default.jpg');
insert into t_book(`id`,`name`,`author`,`price`,`sales`,`stock`,`img_path`)
values(null,'Javascript从入门到精通','婷姐',9.9,85,95,'static/img/default.jpg');
insert into t_book(`id`,`name`,`author`,`price`,`sales`,`stock`,`img_path`)
values(null,'cocos2d-x游戏编程入门','国哥',49,52,62,'static/img/default.jpg');
insert into t_book(`id`,`name`,`author`,`price`,`sales`,`stock`,`img_path`)
values(null,'C语言程序设计','谭浩强',28,52,74,'static/img/default.jpg');
insert into t_book(`id`,`name`,`author`,`price`,`sales`,`stock`,`img_path`)
values(null,'lua语言程序设计','雷锋杨',51.5,48,82,'static/img/default.jpg');
insert into t_book(`id`,`name`,`author`,`price`,`sales`,`stock`,`img_path`)
values(null,'水浒传','华仔',33.05,22,88,'static/img/default.jpg');
insert into t_book(`id`,`name`,`author`,`price`,`sales`,`stock`,`img_path`)
values(null,'西游记','罗贯中',12,19,9999,'static/img/default.jpg');
insert into t_book(`id`,`name`,`author`,`price`,`sales`,`stock`,`img_path`)
values(null,'操作系统原理','刘优',133.05,122,188,'static/img/default.jpg');
insert into t_book(`id`,`name`,`author`,`price`,`sales`,`stock`,`img_path`)
values(null,'数据结构java版','峰大神',173.15,21,81,'static/img/default.jpg');
insert into t_book(`id`,`name`,`author`,`price`,`sales`,`stock`,`img_path`)
values(null,'Unitx高级环境编程','乐天',99.15,210,810,'static/img/default.jpg');
insert into t_book(`id`,`name`,`author`,`price`,`sales`,`stock`,`img_path`)
values(null,'大话设计模式','国哥',89.15,20,10,'static/img/default.jpg');
insert into t_book(`id`,`name`,`author`,`price`,`sales`,`stock`,`img_path`)
values(null,'人间神话','刚哥',88.15,20,80,'static/img/default.jpg');
select id,name,author,price,sales,stock,img_path from t_book;
于是数据库中存入如下信息
Step2:编写图书模块的JavaBean对象
根据对应的book对象
public class Book {
private Integer id;
private String name;
private String author;
private BigDecimal price;
private Integer sales;
private Integer stock;
private String imgPath = "static/img/default.jpg";
public Book() {
}
public Book(Integer id, String name, String author, BigDecimal price, Integer sales, Integer stock, String imgPath) {
this.id = id;
this.name = name;
this.author = author;
this.price = price;
this.sales = sales;
this.stock = stock;
if (imgPath != null && "".equals(imgPath)) {
this.imgPath = imgPath;
}
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public BigDecimal getPrice() {
return price;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
public Integer getSales() {
return sales;
}
public void setSales(Integer sales) {
this.sales = sales;
}
public Integer getStock() {
return stock;
}
public void setStock(Integer stock) {
this.stock = stock;
}
public String getImgPath() {
return imgPath;
}
public void setImgPath(String imgPath) {
if (imgPath != null && "".equals(imgPath)) {
this.imgPath = imgPath;
}
}
@Override
public String toString() {
return "book{" +
"id=" + id +
", name='" + name + '\'' +
", author='" + author + '\'' +
", price=" + price +
", sales=" + sales +
", stock=" + stock +
", imgPath='" + imgPath + '\'' +
'}';
}
}
Step3:编写图书模块Dao和测试Dao
根据业务需求设置对应的Dao接口(规定有哪些操作需要执行)
无非就是增删改+各种查询
public interface BookDao {
// 设置增删改查的方法
public int addBook(Book book);
// 删除
public int deleteBookById(Integer id);
// 修改
public int updateBook(Book book);
// 各种花式查询
// 通过id查书
public Book queryBookById(Integer id);
// 查询所有书
public List<Book> queryBooks();
}
对应的Dao实现类代码如下:
public class BookDaoImpl extends BaseDao implements BookDao {
@Override
// 添加一本书
public int addBook(Book book) {
// id自增,这里不需要添加
String sql = "insert into t_book(`name` , `author` , `price` , `sales` , `stock` , `img_path`) values(?,?,?,?,?,?)";
int i = update(sql, book.getName(), book.getAuthor(), book.getPrice(), book.getSales(), book.getStock(), book.getImgPath());
return i;
}
// 通过ID删除书
@Override
public int deleteBookById(Integer id) {
String sql = "DELETE FROM t_book WHERE id = ?";
int i = update(sql, id);
System.out.println("已经删除 " + i + " 本书····");
return i;
}
// 修改某本书
@Override
public int updateBook(Book book) {
System.out.println("BookDaoImpl updateBook 程序在[" +Thread.currentThread().getName() + "]中");
String sql = "UPDATE t_book SET `name`=? , `author`=? , `price`=? , `sales`=? , `stock`=? , `img_path` = ? WHERE id = ?";
int i = update(sql, book.getName(), book.getAuthor(), book.getPrice(), book.getSales(), book.getStock(), book.getImgPath(), book.getId());
return i;
}
// 通过ID查询书
@Override
public Book queryBookById(Integer id) {
// 这里由于需要返回查询的对象,需要使用别名来查询,不然无法查询到结果
String sql = "SELECT `id` , `name` , `author` , `price` , `sales` , `stock` , `img_path` imgPath FROM t_book where id = ?";
Book book = queryForOne(Book.class, sql, id);
return book;
}
// 查询所有书
@Override
public List<Book> queryBooks() {
String sql = "SELECT `id` , `name` , `author` , `price` , `sales` , `stock` , `img_path` imgPath FROM t_book";
List<Book> books = queryForList(Book.class, sql);
return books;
}
出现的问题:数据无法插入
趁着记忆鲜活,记录一下刚刚出现的问题:数据无法在idea中通过对应java语句插入,经过debug后也没有发现问题出在哪,进入JDBC.Utils工具类中发现获取连接的代码如下:
public static Connection getConnection3Druid() throws SQLException{
Connection conn = conns.get();
if (conn == null) {
conn = source1.getConnection();//从数据库连接池中获取连接
conns.set(conn); 保存到ThreadLocal对象中,供后面的jdbc操作使用 相对最后一次保存有效!
conn.setAutoCommit(false);// 设置为手动管理事务
}
return conn;
}
同时又发现虽然无法读取到数据,但通过mysql控制台插入的数据id不按照正确的顺序增加,也就是说先前在java中确实成功进入了数据库,并执行了对应的增加操作,但由于某些原因数据没能正确的插入进去。
同时又在网上看到,当设置手动提交数据后,需要记得commit
提交数据,同时结合上述代码成功发现问题所在:
应当将conn.setAutoCommit(false);
语句注释调,让其自动提交才能实现真正将数据插入进去。
同时修改properties中的配置信息,使得访问数据库时采用UTF-8的格式来编码数据。
Step4:编写图书模块的Service和测试Service
书模块Service层的接口规范
public interface BookService {
// 添加一本书
public void addBook(Book book);
// 删除一本书
public void deleteBookById(Integer id);
// 更新书的信息
public void updateBook(Book book);
// 通过ID查书
public Book queryBookById(Integer id);
// 查询所有书
public List<Book> queryBooks();
}
实现类:
public class BookServiceimpl implements BookService {
BookDao bookDao = new BookDaoImpl();
// 添加一本书
@Override
public void addBook(Book book) {
bookDao.addBook(book);
}
// 通过ID删除一本书
@Override
public void deleteBookById(Integer id) {
bookDao.deleteBookById(id);
}
// 更新书的信息
@Override
public void updateBook(Book book) {
bookDao.updateBook(book);
}
// 通过ID查询一本书
@Override
public Book queryBookById(Integer id) {
return bookDao.queryBookById(id);
}
// 查询所有书
@Override
public List<Book> queryBooks() {
return bookDao.queryBooks();
}
}
Step5:编写图书模块的Web层,和页面联调测试
web层的请求响应流程如下:在web层中,用户请求的数据内容并不能直接在jsp中获取,而是需要先访问对应的Servlet程序,Servlet从数据库中得到数据后,将数据保存在request域(同次请求有效),将获取到的数据联通请求一起请求响应给对应jsp页面,jsp页面再从request域中获取Servlet查询到的数据,将数据动态响应至页面上。
整个过程的流程示意图以及对应模块所做的具体内容如下图所示:
先前讲过每一个模块都可以继承BaseServlet
完成请求的分发响应,BookServlet也不例外。在BookServlet中同样只需要定义对应请求的操作即可,代码如下:
public class BookServlet extends BaseServlet {
// 调用Service层的对象实例
private BookService bookService = new BookServiceimpl();
// 增图书操作
protected void add(HttpServletRequest request, HttpServletResponse response){ }
// 删图书操作
protected void delete(HttpServletRequest request, HttpServletResponse response) { }
// 改图书操作
protected void update(HttpServletRequest request, HttpServletResponse response) { }
// 查询一本图书操作
protected void getBook(HttpServletRequest request, HttpServletResponse response) { }
// 查询所有图书操作
protected void list(HttpServletRequest request, HttpServletResponse response)}
}
功能一:列出当前数据库中的全部图书信息
protected void list(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1、通过BookService查询全部图书
List<Book> books = bookService.queryBooks();
// 2、把全部图书保存到Request域中
request.setAttribute("books", books);
// 3、请求转发到/pages/manager/book_manager.jsp页面
request.getRequestDispatcher("/pages/manager/book_manager.jsp").forward(request,response);
}
说明:由于我们最先呈现出的是有所有图书信息的,故最先需要实现的功能是list功能,然后让所有其他功能嵌入其中。
功能二:添加图书
protected void add(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 1、获取请求的参数==封装成为Book对象
Book book = WebUtils.copyParamToBean(request.getParameterMap(), new Book());
// 2、调用BookService.addBook()保存图书
bookService.addBook(book);
// 3、跳到图书列表页面
// /manager/bookServlet?action=list
// request.getRequestDispatcher("/manager/bookServlet?action=list").forward(request, response);
response.sendRedirect(request.getContextPath() + "/manager/bookServlet?action=list");
}
关于添加图书的流程以及注意要点说明:
流程说明:具体地,在07_book/manager/bookServlet?action=list
页面中,点击添加图书按钮后会跳到07_book/pages/manager/book_edit.jsp
页面,然后将需要添加的书的信息传给BookServlet
程序中,并调用add方法将书存入数据库,存入完成后,再次返回07_book/manager/bookServlet?action=list
,刷新显示添加了图书后的所有图书信息。
注意要点:BookServlet添加成功后返还的页面不能使用请求转发来传递信息,即下方代码
request.getRequestDispatcher("/manager/bookServlet?action=list").forward(request, response);
如果使用请求转发,会出现表单重复提交的bug
- 当用户提交完请求,浏览器会记录下最后一次请求的全部信息,当用户按F5刷新页面时,就会发起浏览器记录的最后一次请求(这也是每次进入F12查看Network后点击刷新能看到上次请求的原因)。而最后一次请求内容就是add操作(因为请求转发始终都是一个请求add)
修改方法:将请求转发更改为请求重定向,重新发起进入显示页面的请求,这样当在对应页面再点击刷新时就只会有一个显示当前页面的请求
response.sendRedirect(request.getContextPath() + "/manager/bookServlet?action=list");
- 此外,请求重定向需要当前工程的全部路径
功能三:删除图书
和添加图书模块的过程类似,删除图书的过程可由下图展示
用户在http://localhost:8080/07_book/manager/bookServlet?action=list
页面选择要删除的图书后,在该页面就会通过标签通过get请求访问bookServlet页面,同时带着要删除的书的id信息,请求参数为action=delete
。对应的jsp页面代码如下:
<td><a href="manager/bookServlet?action=delete&id=${book.id}">删除</a> </td>
当bookServlet
收到上述请求参数后就会调用在doget()
方法中调用对应的delete()
方法,从request
域中获取id信息,再调用Service
层中对应的方法完成对数据库的操作。最后再将请求重定向至初始页面。
protected void delete(HttpServletRequest request, HttpServletResponse response) throws IOException {
// 1、获取请求的参数id,图书编程
int id = WebUtils.parseInt(request.getParameter("id"), 0);
// 2、调用bookService.deleteBookById();删除图书
bookService.deleteBookById(id);
// 3、重定向回图书列表管理页面
// /book/manager/bookServlet?action=list
response.sendRedirect(request.getContextPath() + "/book/manager/bookServlet?action=list";
}
注意:BookServlet
类中的delete方法根据id删除对应数据时需要完成String类型向int类型的转换,转换函数在WebUtil
类中定义
public static int parseInt(String strInt, int defaultValue) {
try {
return Integer.parseInt(strInt);
} catch (NumberFormatException e) {
// e.printStackTrace();
}
return defaultValue;
}
此外,为了防止用户误触碰到删除按钮,还需要给用户一个提示框,用于询问是否删除该对象,因此需要在book_manager.jsp
中加入如下内容:
<script type="text/javascript">
$(function () {
// 给删除的a标签绑定单击事件,用于删除的确认提示操作
$("a.deleteClass").click(function () {
// 在事件的function函数中,有一个this对象。这个this对象,是当前正在响应事件的dom对象。
/** * confirm是确认提示框函数 * 参数是它的提示内容 * 它有两个按钮,一个确认,一个是取消。 * 返回true表示点击了,确认,返回false表示点击取消。 */
// 取两次父元素,再找该父元素的第一个元素,就对应到书名来
return confirm("你确定删除出【" + $(this).parent().parent().find("td:first").text() + "】?");
// return false// 阻止元素的默认行为===不提交请求
})
})
</script>
<!-- 对应的td标签如下。-->
<td><a class= "deleteClass" href="manager/bookServlet?action=delete&id=${book.id}">删除</a> </td>
功能四:修改图书信息
修改图书时分为两个步骤
- 把修改的图书信息回显到表单项中
- 提交修改后的数据给服务器保存修改
第一步:数据回显
当我们在显示所有图书的页面07_book/pages/manager/manager.jsp
点击修改时
我们实际上时,我们会进入一个新的修改页面07_book/pages/manager/book_edit.jsp
但我们并非是直接进入,而是先在第一个页面将我们需要修改的请求数据发送给BookServlet
程序,后者通过请求数据的id信息从数据库中获取数据后,将对应的数据回显至后面的页面中供我们修改,这个过程可用如下流程框图实现
因此,在BookServlet类中应当定义一个getBook()
方法,代码如下
protected void getBook(HttpServletRequest request, HttpServletResponse response) {
// 1、获取请求的参数图书编号
int id = WebUtils.parseInt(request.getParameter("i"), 0);
// 2、调用bookService.queryBookById查询图书
Book book = bookService.queryBookById(id);
// 3、保存到图书到Request域中
request.setAttribute("book", book);
// 4、请求转发到。pages/manager/book_edit.jsp页面
request.getRequestDispatcher("/pages/manager/book_edit.jsp").foward(request,response);
}
该方法通过id数据从数据库中获取对应的图书,并将请求响应转发给/pages/manager/book_edit.jsp
,从而实现回显功能
第二步:提交修改
当在上述页面点击提交按钮后,会把表单信息再次通过Post请求发送给BookServlet
程序,并调用update方法。后者会将请求发送的数据封装称为一个完整的JavaBean对象,执行bookService
中的updateBook
方法完成对数据库中数据的修改,最后将此次请求转发给"/manager/bookServlet?action=list"
。对应的源代码如下:
protected void update(HttpServletRequest request, HttpServletResponse response) {
// 1、获取请求的参数==封装成为Book对象
Book book = WebUtils.copyParamToBean(request.getParameterMap(), new Book());
// 2、调用BookService.updateBook( book );修改图书
bookService.updateBook(book);
// 3、重定向回图书列表管理页面
// 地址:/工程名/manager/bookServlet?action=list
request.getRequestDispatcher("/manager/bookServlet?action=list").forward(request,response);
}
然后和添加删除图书一样,用户刷新页面只会保留最后一次请求——显示所有图书
⭐️上述过程会出现的小问题
问题一:页面复用:对于/pages/manager/book_edit.jsp
页面,增加图书和修改图书都会访问该页面。因此需要根据两者访问该页面时的不同之处设计运行逻辑实现复用。
方法一:通过方法名。可以分别给add请求和update增加一个属性值
methos
,分别对应不同的响应,对应的修改如下修改请求:
<td><a href="client/bookServlet?action=getBook&id=${book.id}&method=update">修改</a></td>--%>
添加请求:
<td><a href="pages/manager/book_edit.jsp?method=update">添加图书</a></td>
book_edit.jsp
页面的逻辑判断
方法二:判断是否有book信息(以id值为key)
如果当前请求域中包含有id信息,就说明时update方法,否则就是add方法
<input type="hidden" name="action" value="${empty param.id ? "add" : "update"}" />
问题二:请求id缺失:
注意到,如果是添加书,并不需要id信息,但如果是查询书,还需要将id信息发送给BookServlet。然而修改页面中并没有id信息,因此需要在表单的隐藏域中增加id信息,以供查询使用。
<form action="manager/bookServlet" method="get">
<input type="hidden" name="action" value="${empty param.id ? "add" : "update"}" />
⭐️<input type="hidden" name="id" value="${requestScope.book.id}" />⭐️
<table>
<tr>
<td>名称</td>
<td>价格</td>
<td>作者</td>
<td>销量</td>
<td>库存</td>
<td colspan="2">操作</td>
</tr>
<tr>
<td><input name="name" type="text" value="${requestScope.book.name}"/></td>
<td><input name="price" type="text" value="${requestScope.book.price}"/></td>
<td><input name="author" type="text" value="${requestScope.book.author}"/></td>
<td><input name="sales" type="text" value="${requestScope.book.sales}"/></td>
<td><input name="stock" type="text" value="${requestScope.book.stock}"/></td>
<td><input type="submit" value="提交"/></td>
</tr>
</table>
</form>
几个需要注意的问题:
问题一:通过标签进入的都是get请求,但如果仍然希望通过get得到post请求时应当将post请求嵌入get请求中,同时为了避免出现乱码的问题,需要在获得请求的时候就设置当前请求的编码格式(一定要在最开始设置)
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 解决post请求中文乱码问题
// 一定要在获取请求参数之前调用才有效
request.setCharacterEncoding("UTF-8");
// 解决响应中文乱码
response.setContentType("text/html; charset=UTF-8");
...
}
问题二:为什么bookServlet网页的访问前要加上/manager/
,内容如下:
<servlet>
<servlet-name>BookServlet</servlet-name>
<servlet-class>web.BookServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>BookServlet</servlet-name>
<url-pattern>/manager/bookServlet</url-pattern>
</servlet-mapping>
这是因为在开发中有着前台和后台之分
- 前台:给普通用户使用。一般不需要权限检查,就可以访问的资源,或者功能都属于前台功能。比如:淘宝不需要登陆就可以访问首页,其对应的地址是
/cliednt/bookServlet
- 后台:给管理员使用。一般都会有权限检查,才能访问的资源、页面或功能都是后台。对应的地址是
manager/bookServlet
疑惑点
疑惑点一:在请求重转发中如果不加forward会怎么样
Step6:进阶修改——图书分页
图书分页的流程与说明如下图所示;
思维过程:分页相关数据(构成一个类)—>>思考类中每个数据如何计算—>>>考虑相关类如何在三层架构中实现
构建Page
对象
/** * Page是分页的模型对象 * @param <T> 是具体的模块的javaBean类 */
public class Page<T> {
public static final Integer PAGE_SIZE = 4;
//当前页码
private Integer pageNo;
//总页码
private Integer pageTotal;
//当前页显示的数量
private Integer pageSize = PAGE_SIZE;
//总记录数
private Integer pageTotalCount;
//当前页数据
private List<T> item; // 这里为了该页面
//这是分页条的请求地址
private String url;
...对应的get set toString方法
// 注意这里
public void setPageNo(Integer pageNo) {
//数据边界的有效检查
if (pageNo < 1) {
pageNo = 1;
}
if (pageNo > pageTotal) {
pageNo = pageTotal;
}
this.pageNo = pageNo;
}
}
- 说明:为例提高复用性,这里最好使用泛型
修改BookServlet
程序
首先需要注意到,之前在进入页面的请求是:/manager/bookServlet?action=list
,但要求每次进入该页面都应当使用的page方法,故这里应当修改为action=page
。
根据先前建立的计算模型,当前page()
方法接收到来自jsp的请求(包含PageNo
以及PageSize
);将这些数据作为参数,向下调用BookService
层的page
方法,请求获取当前参数下对应的Item数据;再将得到的数据信息装入request域中;最后再将该request域的数据转发给"/page/manager/book_manager.jsp"
。
对应源代码如下:
protected void page(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//1 获取请求的参数 pageNo 和 pageSize
int pageNo = WebUtils.parseInt(request.getParameter("pageNo"), 1);//如果没有传数值,默认第一页
int pageSize = WebUtils.parseInt(request.getParameter("pageSize"), Page.PAGE_SIZE);//如果传值类就用所传数据,否则用默认值
//2 调用BookService.page(pageNo,pageSize):Page对象
Page<Book> page = bookService.page(pageNo, pageSize);
page.setUrl("manager/book/Servlet?action=page");
//3 保存Page对象到Request域中
request.setAttribute("page", page);
//4 请求转发到pages/manager/book_manager.jsp页面
request.getRequestDispatcher("/page/manager/book_manager.jsp").forward(request, response);
}
修改BookService
程序
根据上层的讨论,首先需要再BookService
接口增加对应的方法规范
public Page<Book> page(int pageNo, int pageSize);
然后再在BookServiceImpl
中实现该方法
@Override
public Page<Book> page(int pageNo, int pageSize) {
Page<Book> page = new Page<>();
// 设置每页显示的数量
page.setPageSize(pageSize);
// 求总记录数
Integer pageTotalCount = bookDao.qureyForPageTotalCount();
// 设置总记录数
page.setPageTotal(pageTotalCount);
// 求总页码
int pageTotal = pageTotalCount / pageSize;
if (pageTotalCount / pageSize > 0) {
pageTotal++;
}
// 设置总页码
page.setPageTotal(pageTotal);
// 设置当前页码
page.setPageNo(pageNo);
// 求当前页数据的开始索引
int begin = (page.getPageNo() - 1) * pageSize;
// 求当前页数据
List<Book> items = bookDao.qureyForPageItems(begin, pageSize);
// 设置当前页数据
page.setItem(items);
return page;
}
说明:这里是对web层的进一步封装,只需要接受web层传来的两个参数pageNo
以及pageSize
,即可得到完整的页面信息类page
类。在该方法中需要完成对Dao层的调用,重点需要获取的是PageTotalCount
,以及pageItems
的内容(调用两个sql语句)。
这里需要提前思考号如何规范地调用sql语句
select from t_book limit begin,size
修改BookDao
程序
在BookDao接口中增加对应的方法规范
public Integer qureyForPageTotalCount();//定义查询页面总数的方法
List<Book> qureyForPageItems(int begin, int pageSize); //定义查询从begin开始到begin+pageSize的数据项的内容
在BookDaoImpl实现类中增加该方法的具体实现方法
@Override
public Integer qureyForPageTotalCount() {
String sql = "select count(*) from t_book";
Number count = (Number) queryForSingleValue(sql);
return count.intValue();
}
@Override
public List<Book> qureyForPageItems(int begin, int pageSize) {
String sql = "SELECT `id` , `name` , `author` , `price` , `sales` , `stock` , `img_path` imgPath FROM t_book limit ?,?";
return queryForList(Book.class, sql, begin, pageSize);
}
代码测试
从小单元向大单元测试,web层内容需要加断点联动web页面测试
因此在显示页面,将不再使用访问list的方式来获取数据,而是指请求当前request域中的page.item中的图书信息
<c:forEach items="${requestScope.page.item}" var="book">
<tr>
<td>${book.name}</td>
<td>${book.price}</td>
<td>${book.author}</td>
<td>${book.sales}</td>
<td>${book.stock}</td>
</tr>
<td><a href="manager/bookServlet?action=getBook&id=${book.id}">修改</a> </td>
<td><a class= "deleteClass" href="manager/bookServlet?action=delete&id=${book.id}">删除</a> </td>
</tr>
</c:forEach>
页面功能的填充
首页、上一页、下一页、末页功能的实现
如果当前页是不是在首页,则可以有首页与上一页
<c:if test="${requestScope.page.pageNo > 1}">
<a href="${requestScope.page.url}&pageNO=1">首页</a>
<a href="${requestScope.page.url}&pageNO=${requestScope.page.pageNo - 1}">上一页</a>
</c:if>
如果当前页不是在末页,则可以有末页与下一页
<c:if test="${requestScope.page.pageNo < requestScope.page.pageTotal}">
<a href="${requestScope.page.url}&pageNO=${requestScope.page.pageNo + 1}">下一页</a>
<a href="${requestScope.page.url}&pageNO=${requestScope.page.pageTotalCount}">末页</a>
</c:if>
跳转到指定页
跳到指定页码的功能实际上是通过location.href
方法重写覆盖当前页面的url地址实现,因此只需要在绑定了的单击事件中根据参数
到第<input value="${param.pagNo}" name="pn" id="pn_input"/>页
<input id="searchPageBtn" type="button" value="确定">
<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-2
到pageNo+2
个页码
根据上述逻辑可以得到如下代码
<c:choose>
<%-- 情况1 : 假如总页码 小于 5 页码的范围 为 1 - 总页码 --%>
<c:when test="${requestScope.page.pageTotal <= 5}">
<c:set var="begin" value="1" />
<c:set var="end" value="${requestScope.page.pageTotal}"/>
<%-- 遍历输出一串数字 --%>
</c:when>
<%-- 情况2 : 假如总页码 大于 5--%>
<c:when test="${requestScope.page.pageTotal > 5}">
<c:choose>
<%-- 小情况1 : 当前页码为1 2 3的情况 页码的范围 为 1 - 5--%>
<c:when test="${requestScope.page.pageNo<3}">
<c:set var="begin" value="${1}"/>
<c:set var="end" value="${5}"/>
</c:when>
<%-- 小情况2 : 当前页码为后3个的情况 页码的范围 总页码-4 - 总页码--%>
<c:when test="${requestScope.page.pageNo>requestScope.page.pageTotal-3}">
<c:set var="begin" value="${requestScope.page.pageTotal-4}"/>
<c:set var="end" value="${requestScope.page.pageTotal}"/>
</c:when>
<%-- 小情况3 : 当前页码为每5个后2个的情况 页码的范围 总页码-2 - 总页码+2--%>
<c:otherwise>
<c:set var="begin" value="${requestScope.page.pageNo-2}"/>
<c:set var="end" value="${requestScope.page.pageNo+2}"/>
</c:otherwise>
</c:choose>
</c:when>
</c:choose>
<%-- 遍历执行模块 --%>
<c:forEach begin="${begin}" end="${end}" var="i">
<c:if test="${i == requestScope.page.pageNo}">
【${i}】
</c:if>
<%-- 不是当前页码--%>
<c:if test="${i != requestScope.page.pageNo}">
<a href="manager/bookServlet?action=page&pageNo=${i}">${i}</a>
</c:if>
</c:forEach>
增加页面后对其他功能的修改
添加模块
添加需求:当添加一条数据后,需要返回当前数据所在页面
需要在BookServlet
中添加如下代码(提前设置了页面溢出检测)
int pageNo = WebUtils.parseInt(request.getParameter("pageNo"), 0);
pageNo++;
// 让请求带着pageNo信息重定向至 page方法
response.sendRedirect(request.getContextPath() + "/manager/bookServlet?action=page&pageNo=" + pageNo);
删除模块
添加需求:当删除数据后,维持在删除数据的页面
//让请求带着pageNo信息 重定向至 page方法
response.sendRedirect(request.getContextPath() + "/manager/bookServlet?action=page&pageNo="+request.getParameter("pageNo"));
修改模块
添加需求:当修改数据后,维持在修改数据的页面
// 让请求带着pageNo信息 转发至 page方法
request.getRequestDispatcher("/manager/bookServlet?action=page&pageNo="+request.getParameter("pageNo")).forward(request,response);
此外,还需要在book_edit.jsp
中添加一个隐藏于存储pageNo
<input type="hidden" name="pageNo" value="${param.pageNo}">
当把信息传递给add
以及update
方法时,同时需要把pageNo信息保存在request
域中供对应方法调用。
出现的问题
忘记在/client/index.jsp
中添加<%@include file="/pages/common/head.jsp" %>
,造成相对路径错误。
Step7:根据价格搜索
需求:根据用户输入的价格区间,得到价格在区间内的全部图书信息
对应执行流程如下图所示,需求分析从web层反向进入Dao层
和实现分页功能时的思考方式相同,结合每层的供鞥综合分析各个层的输入输出
Web层——该层需要获取用户输入的min
,max
,pageNo
,pageSize
信息,得到当前页面应当得到的对应的物品信息(page
类)。
public void pageByPrice(){
1. 请求获取参数的`pageNo`,`pageSize`,`min`,`max`信息
2. 调用`bookService.pageByPrice(pageNo,pageSize,min,max):Page`方法
3. 保存分页对象`page`到`Request`域中
4. 将请求转发到`/page/client/index.jsp`页面
}
Sercive层——进一步抽取Web层的数据,将Web层需要的功能封装成BookService
函数:构建一个Page类,存放了页码,等Page信息
public Page pageByPrice(pageNo,pageSize,min,max){
主要求三个数据:总记录数,总页码,当前页面数据
总记录数:调用 queryForPageTotalCount(min,max) // 获取当前价格区间内页面的总数
当前页面数据:调用 queryForPageItems(begin,size,min,max) //获取当前价格区间的数据
}
DAO层——封装sql语句
public Integer queryForPageTotalCountByPrice(int min,int max){
String sql = "select count(*) from t_book where where price between ? and ?";
Number count = (Number)queryForSingleValue(sql,min,max);
return count.intValue();
}
public List<Book> queryForPageItemsByPrice(int begin,int pageSize,int min,int max){
String sql = "SELECT `id` , `name` , `author` , `price` , `sales` , `stock` , `img_path` imgPath " +
"FROM t_book where price between ? and ? order by price limit ?,?";
return queryForList(Book.class,sql,min,max,begin,pageSize);
}
Service层
public Page pageByPrice(pageNo,pageSize,min,max){
Page page = new Page();
// 设置当前页码
page.setPageNo(pageNo);
// 设置当前页面大小
page.setPageNo(pageSize);
// 设置总记录数
page.setPageTotalCount(bookDao.queryForPageTotalCount(min,max));
// 设置总页码数
int pageTotal = page.PageTotalCount/pageSize;
if(page.PageTotalCount%pageSize>0)
pageTotal++;
// 求当前页面数据开始的索引,如果当前页为1,则查询从0开始的4条数据,如果是2,则查从4开始的4条数据
int begin = (page.getPageNo()-1)*page.Size;
List<Book> = bookDao.queryForPageItems(begin,size,min,max) //获取当前价格区间的数据
//至此,Page中6项数据在这里完成5项的设置,最后一项path在Web层设置
return page;
}
Web层
public void pageByPrice(){
// 1. 请求获取参数的`pageNo`,`pageSize`,`min`,`max`信息
int pageNo = request.getParameter("pageNo");
int pageSize = request.getParameter("pageSize");
int min = WebUtils.parseInt(request.getParameter("min"), 0);
int max = WebUtils.parseInt(request.getParameter("max"), Integer.MAX_VALUE);
// 2. 调用`bookService.pageByPrice(pageNo,pageSize,min,max):Page`方法
Page<page> page = bookService.pageByPrice(pageNo,pageSize,min,max);
//⭐️除了记录page数据,还需要将max和min数据回显给用户因此需要在这里设置url地址
StringBuilder sb = new StringBuilder("client/bookServlet?action=pageByPrice");
if(min!=null){
sb.append("&min=").append(request.getParameter("min"));
}
if(max!=null){
sb.append("&max=").append(request.getParameter("max"));
}
page.setUrl(sb);
// 3. 保存分页对象`page`到`Request`域中
request.setAttribute("page",page);
// 4. 将请求转发到`/page/client/index.jsp`页面
request.getRequestDispatcher("/pages/client/index.jsp").foward(request,response);
⚠️Web层中request.getParameter()
方法是用于获取前台传入后台的数据,request.setAttribute()
方法则是用于后台设置数据发送给前台。
小结:对于某个需求如何实现,需要先从Web层一步步分析到Dao层。然后再反向一步步实现——测试,最后到达Web层。
还没有评论,来说两句吧...