【尚硅谷_书城项目第五阶段】【学习笔记】寒假javaweb学习之旅结束

开始上课了,下面我宣布,寒假正式结束😭

🤗1、MVC概念

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

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

3、View视图:只负责数据和界面的显示,不接受任何与显示数据无关的代码,便于程序员和美工的分工合作---------JSP/HTML

4、Controller控制器:只负责接收请求,调用业务层的代码处理请求,然后派发页面,是一个“调度者”的角色一Servlet,转到某个页面。或者是重定向到某个页面

5、Model模型 :将与业务逻辑相关的数据封装为具体的JavaBean类,其中不掺杂任何与数据处理相关的代码一JavaBean/domain/entity

6、MVC是一种思想,MVC的理念是将软件代码拆分成为组件,单独开发,组合使用(目的还是为了解耦合)

概念图例

在这里插入图片描述

🤗2、图书模块

2.1、编写图书模块的数据库表

我们根据下图的内容,创建数据库

在这里插入图片描述

不难看出,我们需要 id、用户名、价格、作者、销量、库存、还有书本图片url🤔🤔

## 创建图书表
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)
);

现在,我们引入一些测试数据看看吧😄!!

在这里插入图片描述

## 插入初始化测试数据
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 , '西游记' , '罗贯中' , 12, 19 , 9999 , '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 , '操作系统原理' , '刘优' , 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 , 'UNIX高级环境编程' , '乐天' , 99.15 , 210 , 810 , 'static/img/default.jpg');
 
INSERT INTO t_book(`id` , `name` , `author` , `price` , `sales` , `stock` , `img_path`) 
VALUES(NULL , 'javaScript高级编程' , '国哥' , 69.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;

2.2、编写图书模块的JavaBean

我们写的javaBean和刚刚创建的t_book表是对应的
我们需要 id、用户名、价格、作者、销量、库存、还有书本图片url🤔🤔

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";  // 给个默认值O(∩_∩)O
}
// Contruct、getXXX、setXXX 和 toString 省略

// 这里多说一下,我们希望图片路径如果为空,就不给它赋值,所以我们改改构造器和Set方法
if(imgPath != null && !"".equals(imgPath)) {
    this.imgPath = imgPath;
}

2.3、编写图书模块的Dao和测试Dao

现在我们编写BookDao,我们发现,它需要增删改查的功能

在这里插入图片描述

2.3.1、编写BookDao

根据,我们之前在书城第二阶段的学习经验,先写个BookDao接口,然后再写个BookDaoImpl去实现它🐾

ublic interface BookDao {
    public int addBook(Book book);
    
    public int deleteBookById(Integer id);
    
    public int updateBook(Book book);
    
    public Book queryBookById(Integer id);
    
    public List<Book> queryBooks();
}
public class BookDaoImpl extends BaseDao implements BookDao {

    @Override
    public int addBook(Book book) {
        String sql = "insert into t_book(`name`,`author`,`price`,`sales`,`stock`,`img_path`) values(?,?,?,?,?,?)";

        return update(sql,book.getName(),book.getAuthor(),book.getPrice(),book.getSales(),book.getStock(),book.getImgPath());
    }

    @Override
    public int deleteBookById(Integer id) {
        String sql = "delete from t_book where id = ?";
        return update(sql,id);
    }

    @Override
    public int updateBook(Book book) {
        String sql = "update t_book set `name`=?,`author`=?,`price`=?,`price`=?,`stock`=?,`img_path`=? where id = ?";
        return update(sql,book.getName(),book.getAuthor(),book.getPrice(),book.getSales(),book.getStock(),book.getImgPath(),book.getId());
    }

    @Override
    public Book queryBookById(Integer id) {
        String sql = "select `id`,`name`,`author`,`price`,`sales`,`stock`,`img_path` imgPath from t_book where id = ?";
        return queryForOne(Book.class,sql,id);
    }

    @Override
    public List<Book> queryBooks() {
        String sql = "select `id`,`name`,`author`,`price`,`sales`,`stock`,`img_path` imgPath from t_book";
        return queryForList(Book.class,sql);
    }
}

2.3.2、测试BookDao

在BookDao的接口下,CTRL + SHIFT + T快捷生成测试

在这里插入图片描述

在这里插入图片描述

public int addBook(Book book)

在这里插入图片描述

@Test
public void addBook() {
    bookDao.addBook(new Book(null,"BibleBlackⅠ","dark",new BigDecimal(9999),1,1,null));
}

public int updateBook(Book book)

在这里插入图片描述

@Test
public void updateBook() {
    bookDao.updateBook(new Book(21,"BibleBlackⅡ","darkⅡ",new BigDecimal(9999),1,1,null));
}

public Book queryBookById(Integer id)

在这里插入图片描述

@Test
public void queryBookById() {
    System.out.println(bookDao.queryBookById(22));
}

public List<Book> queryBooks()

在这里插入图片描述

@Test
public void queryBooks() {
    for (Book queryBook: bookDao.queryBooks()) {
        System.out.println(queryBook);
    }
}

public int deleteBookById(Integer id)

在这里插入图片描述

@Test
public void deleteBookById() {
    bookDao.deleteBookById(22);
}

2.4、编写图书模块的Service和测试Service

和Dao层一样

2.4.1、编写Service

public interface BookService {
    public void addBook(Book book);

    public void deleteBookById(Integer id);

    public void updateBook(Book book);

    public Book queryBookById(Integer id);

    public List<Book> queryBooks();
}
public class BookServiceImpl implements BookService {
    private BookDao bookDao = new BookDaoImpl();
    @Override
    public void addBook(Book book) {
        bookDao.addBook(book);
    }

    @Override
    public void deleteBookById(Integer id) {
        bookDao.deleteBookById(id);
    }

    @Override
    public void updateBook(Book book) {
        bookDao.updateBook(book);
    }

    @Override
    public Book queryBookById(Integer id) {
        return bookDao.queryBookById(id);
    }

    @Override
    public List<Book> queryBooks() {
        return bookDao.queryBooks();
    }
}

2.4.2、测试Service

CTRL + SHIFT + T

public class BookServiceTest {
    BookService bookService = new BookServiceImpl();
    
    @Test
    public void addBook() {
        bookService.addBook(new Book(null,"BibleBlackⅠ","dark",new BigDecimal(9999),1,1,null));
    }

    @Test
    public void deleteBookById() {
        bookService.deleteBookById(21);
    }

    @Test
    public void updateBook() {
        bookService.updateBook(new Book(21,"BibleBlackⅡ","darkⅡ",new BigDecimal(9999),1,1,null));
    }

    @Test
    public void queryBookById() {
        System.out.println(bookService.queryBookById(21));;
    }

    @Test
    public void queryBooks() {
        for (Book queryBook : bookService.queryBooks()) {
            System.out.println(queryBook);
        }
    }
}

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

我们也需要对CRUD功能做出相应的效果
在这里插入图片描述

2.5.1、查询图书

在这里插入图片描述

图解

在这里插入图片描述

代码实现

配置servlet,这里的url路径,前面多加了/mange/不是,在mange目录下的意思嗷,是为了方便我们后面权限管理
在这里插入图片描述

<servlet-mapping>
    <servlet-name>BookServlet</servlet-name>
    <url-pattern>/manager/bookServlet</url-pattern>
</servlet-mapping>

回到我们之前写的common\manager_menu.jsp,改改a标签的路径,这里action表示服务器调用,哪一个方法

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<div>
    <a href="mange/bookServlet?action=list">图书管理</a>
    <a href="order_manager.jsp">订单管理</a>
    <a href="../../index.jsp">返回商城</a>
</div>

下面是BookServlet,我们只要完成List方法就行

public class BookServlet extends BaseServlet{

    private BookService bookService = new BookServiceImpl();

    protected void add(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }

    protected void delete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }

    protected void update(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }

    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/manger/book_manager.jsp页面
        request.getRequestDispatcher("/pages/manager/book_manager.jsp").forward(request,response);
    }
}
...
<div id="main">
    <table>
        <tr>
            <td>名称</td>
            <td>价格</td>
            <td>作者</td>
            <td>销量</td>
            <td>库存</td>
            <td colspan="2">操作</td>
        </tr>		
        <c:forEach items="${requestScope.books}" var="book">
            <tr>
                <td>${book.name}</td>
                <td>${book.price}</td>
                <td>${book.author}</td>
                <td>${book.sales}</td>
                <td>${book.stock}</td>
                <td><a href="book_edit.jsp">修改</a></td>
                <td><a href="#">删除</a></td>
            </tr>
        </c:forEach>
...

因为a标签,点击是get请求,所以我们要去BaseServlet对get请求做1个聪明的处理,使得它为post请求😁

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    doPost(req,resp);
}

2.5.2、添加图书

在这里插入图片描述

图解

在这里插入图片描述

代码实现

添加页面这里,我们改改😤,增加隐藏域,修改标签名,和JavaBean对应

<div id="main">
    <form action="manager/bookServlet" method="get">
        <input type="hidden" name="action" value="add">
        <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="时间简史"/></td>
                <td><input name="price" type="text" value="30.00"/></td>
                <td><input name="author" type="text" value="霍金"/></td>
                <td><input name="sales" type="text" value="200"/></td>
                <td><input name="stock" type="text" value="300"/></td>
                <td><input type="submit" value="提交"/></td>
            </tr>	
        </table>
    </form>
</div>

完成BookServlet的add方法,这里不用请求转发的原因,是因为这样写会有个bug😰

表单重复提交:

当用户提交完请求,浏览器会记录下最后一次请求的全部信息。当用户按下功能键F5,就会发起浏览器记录的最后一次请求。下面代码使用请求重定向解决该问题

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
    response.sendRedirect(request.getContextPath()+"/manager/bookServlet?action=list");
}

2.5.3、删除图书

在这里插入图片描述

图解

在这里插入图片描述

代码实现

修改删除的标签

<c:forEach items="${requestScope.books}" var="book">
    <tr>
        <td>${book.name}</td>
        <td>${book.price}</td>
        <td>${book.author}</td>
        <td>${book.sales}</td>
        <td>${book.stock}</td>
        <td><a href="book_edit.jsp">修改</a></td>
        <td><a href="manager/bookServlet?action=delete&id=${book.id}">删除</a></td>
    </tr>
</c:forEach>

完成BookServlet的delet方法

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

这里方便让String转int,我们特意在WebUtils下加了parseInt()方法

public static int parseInt(String strInt,int defaultValue){
    try {
        return Integer.parseInt(strInt);
    } catch (Exception e) {
        e.printStackTrace();
    }
    return defaultValue;
}

还有一件事,我们这样直接删除,不给用户提示,属于是有点nt设计了🤗,所以我们要给用户提示(❌)jQuery复习时间(✔),先给a标签加个class

<td><a class="deleteClass" href="manager/bookServlet?action=delete&id=${book.id}">删除</a></td>
<script>
    $(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>

2.5.4、修改图书

在这里插入图片描述

图解

在这里插入图片描述

代码实现

数据回显

修改修改的a标签(嗨呀,有一种叠词词的感觉捏🤗),使它跳转到Servlet程序,调用getBook方法,传递id

<td><a href="manager/bookServlet?action=getBook&id=${book.id}">修改</a></td>

实现getBook方法

protected void getBook(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //        1、获取图书编号
    String id = request.getParameter("id");
    //        2、调用BookService. queryBookById(id):Book;得到修改的图书信息
    Book book = bookService.queryBookById(WebUtils.parseInt(id,0));
    //        3、把图书保存到Request域中
    request.setAttribute("book",book);
    //        4、请求转发到/pages/manager/book_edit. jsp页面
    request.getRequestDispatcher("/pages/manager/book_edit.jsp").forward(request,response);
}

修改book_edit页面中显示的书本信息

<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>	

修改

我们这里用方案二,解决动态修改book_edit.jsp的功能

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

完成update()方法

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

🤗 3、图书分页

图解

在这里插入图片描述

3.1、编写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> items;
	//get、set 和 toString 省略
}

3.2、分页功能初步实现

在这里插入图片描述

老师上课时从web层一直写到dao层,其实我觉得应该从Dao层写到Web层,测试也是从最小的Dao层,测到Web层

首先修改我们之前用静态包含的manager_menu的a标签,不调用list()方法了,我们调用page()方法😁

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<div>
    <a href="/book/manager/bookServlet?action=page">图书管理</a>
    <a href="order_manager.jsp">订单管理</a>
    <a href="../../index.jsp">返回商城</a>
</div>

还有在book_manager.jsp修改,我们现在,需要在Requset域page.items里取值,而不是books里

<c:forEach items="${requestScope.page.items}" var="book">
    <tr>
        <td>${book.name}</td>
        <td>${book.price}</td>
        <td>${book.author}</td>
        <td>${book.sales}</td>
        <td>${book.stock}</td>
        <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>

同时在</table>下面,加上页码栏

<div id="page_nav">
    <a href="#">首页</a>
    <a href="#">上一页</a>
    <a href="#">3</a>
    【${requestScope.page.pageNo}<a href="#">5</a>
    <a href="#">下一页</a>
    <a href="#">末页</a>
    共${requestScope.page.pageTotal}页,${requestScope.page.pageTotalCount}条记录
    到第<input value="4" name="pn" id="pn_input"/><input type="button" value="确定">
</div>

🐛Web层BookServlet的page()方法

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);
    //3保存age对象到Request域中
    request.setAttribute("page",page);
    //4请求转发到/pages/manager/book_manager.jsp页面
    request.getRequestDispatcher("/pages/manager/book_manager.jsp").forward(request,response);
    }

🐛Service层BookServiceImpl实现page()方法

@Override
public Page<Book> page(int pageNo, int pageSize) {
    Page<Book> page = new Page<Book>();
    // 设置当前页码
    page.setPageNo(pageNo);

    // 设置每页显示的数量
    page.setPageSize(pageSize);

    // 求总记录数
    Integer pageTotalCount = bookDao.queryForPageTotalCount();
    // 设置总记录数
    page.setPageTotalCount(pageTotalCount);

    // 求总页码
    Integer pageTotal = pageTotalCount / pageSize ;
    if(pageTotalCount % pageSize > 0){
        pageTotal++;
    }
    // 设置总页码
    page.setPageTotal(pageTotal);

    // 求当前页开始索引
    int begin = (page.getPageNo() - 1) * pageSize;
    // 求当前页数据
    List<Book> items = bookDao.queryForPageItems(begin,pageSize);
    // 设置当前页数据
    page.setItems(items);

    return page;
}

🐛Dao层BookDaoImpl实现queryForPageTotalCount()方法和queryForPageItems()方法

@Override
public Integer queryForPageTotalCount() {
    String sql = "select count(*) from t_book";
    Number count =  (Number)queryForSingleValue(sql);
    return count.intValue();
}

@Override
public List<Book> queryForPageItems(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);
}

测试

最小单元优先测试,我们从Dao层测到Web层

🐛 测试Dao层

@Test
public void queryForPageTotalCount() {
    System.out.println(bookDao.queryForPageTotalCount());
}

@Test
public void queryForPageItems() {
    for (Book book : bookDao.queryForPageItems(8, 4)) {
        System.out.println(book);
    }
}

🐛 测试Service层

@Test
public void page() {
    System.out.println(bookService.page(1,Page.PAGE_SIZE));
}

🐛 测试Web层

直接运行服务器,马上到我们的图书管理页面康康

在这里插入图片描述

3.3、首页、上一页、下一页、末页

在这里插入图片描述

我们会发现一个问题,如果我们在第1页,那么我们点击首页和上一页,没什么用,如果我们在最后一页,那么我们点击下一页和末页,也没什么用,所以我们要解决这个问题。因此,我们只要在第一页和最后一页不显示出那个选项就行了👌

<div id="page_nav">
    <%--大于首页页码才显示--%>
    <c:if test="${requestScope.page.pageNo > 1}">
        <a href="manager/bookServlet?action=page&pageNo=1">首页</a>
        <a href="manager/bookServlet?action=page&pageNo=${requestScope.page.pageNo-1}">上一页</a>
    </c:if>
    <a href="#">3</a>
    【${requestScope.page.pageNo}<a href="#">5</a>
    <%--	如果已经是最后一页,就不会显示下一页,末页		--%>
    <c:if test="${requestScope.page.pageNo > 1}">
        <a href="manager/bookServlet?action=page&pageNo=${requestScope.page.pageNo+1}">下一页</a>
        <a href="manager/bookServlet?action=page&pageNo=${requestScope.page.pageTotal}">末页</a>
    </c:if>
    共${requestScope.page.pageTotal}页,${requestScope.page.pageTotalCount}条记录
    到第<input value="4" name="pn" id="pn_input"/><input type="button" value="确定">
</div>
</div>

3.4、跳转到指定页码

在这里插入图片描述

为了使location.href 跳转更加精确,我们可以去common/head.jsp,把basePath的路径存到pageContext域中

<%
    // http://localhost:8080/工程路径/
    String basePath = request.getScheme()
        + "://"
        + request.getServerName()
        + ":"
        + request.getServerPort()
        + request.getContextPath()     //request.getContextPath()返回 /工程路径,所以这里不需要加/
        + "/";
   pageContext.setAttribute("basePath",basePath);
%>

下面搜索到指定页面的小改

...
到第<input value="${param.pageNo}" name="pn" id="pn_input"/><input id="searchPageBtn" type="button" value="确定">
<script>
    $(function (){
        $("#searchPageBtn").click(function (){
            let pageNo = $("#pn_input").val();
            // js 提供了一个location地址栏对象
            // 它有一个属性href 它可以获取浏览器地址栏的地址
            // href属性可读,可写
            location.href = "${pageScope.basePath}manager/bookServlet?action=page&pageNo="+pageNo;
        });
    });
</script>

为了避免出现有人无聊查-100页或者1000页这种不存在的页数,我们可以去Set页码的方法里这里直接进行判断

public void setPageNo(Integer pageNo) {
    if(pageNo < 1){
        pageNo = 1;
    }else if(pageNo > pageTotal){
    pageNo = pageTotal;
	}
    this.pageNo = pageNo;
}

这里一定要小♥,pageTotal的空指针异常,所以我们在BookServiceImpl中,一定要把setPageTotal()方法,放到setPageNo()方法前面(我因为这个bug,卡了亿点点时间

3.5、分页条页码的输出

在这里插入图片描述

要求 : 分页模块中,页码1,2,【3】 ,4,5 的显示,要显示5个页码,并且页码可以点击跳转

分析

情况1: 如果总页码小于等于5的情况,页码的范围是:1~总页码

1 页 1

2 页 1 ,2

3 页 1,2,3

4 页 1,2,3,4

5 页 1,2,3,4,5

情况2 : 总页码大于5的情况。假设一共10页

小情况1: 当前页码为前3个:1,2,3的情况,页码的范围是:1 - 5

【1】,2,3,4,5

1,【2】,3,4,5

1,2,【3】,4,5

小情况2: 当前页码为最后3个,8,9,10的情况,页码范围是:总页码-4 ~ 总页码

6,7,【8】,9,10

6,7,8,【9】,10

6,7,8,9,【10】

小情况3: 3,4,5,6,7的情况,页码范围是:当前页码-2 ~ 当前页码+2

2,3,【4】,5,6

3,4,【5】,6,7

4,5,【6】,7,8

5,6,【7】,8,9

代码实现

<%--页码输出的开始--%>
<c:choose>
    <%-- 情况1:如果总页码小于等于5的情况,页码的范围是:1~总页码		--%>
    <c:when test="${requestScope.page.pageTotal <= 5}">
        <c:forEach begin="1" end="${requestScope.page.pageTotal}" 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>
    </c:when>
    <%--情况2:总页码大于5的情况。假设一共10--%>
    <c:when test="${requestScope.page.pageTotal > 5}">
        <c:choose>
            <%--小情况1:当前页码为前3个:123的情况,页码的范围是:1 - 5				--%>
            <c:when test="${requestScope.page.pageNo <= 3}">
                <c:forEach begin="1" end="5" 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>
            </c:when>
            <%--小情况2:当前页码为最后3个,8910的情况,页码范围是:总页码-4 ~ 总页码--%>
            <c:when test="${requestScope.page.pageNo > requestScope.page.pageTotal-3}">
                <c:forEach begin="${requestScope.page.pageTotal-4}" end="${requestScope.page.pageTotal}" 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>
            </c:when>
            <%--小情况334567的情况,页码范围是:当前页码-2 ~ 当前页码+2--%>
            <c:otherwise>
                <c:forEach begin="${requestScope.page.pageNo-2}" end="${requestScope.page.pageNo + 2}" 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>
            </c:otherwise>
        </c:choose>
    </c:when>
</c:choose>
<%--页码输出的结束--%>

发现代码重复,我们可以用set优化

<%--页码输出的开始--%>
<c:choose>
    <%-- 情况1:如果总页码小于等于5的情况,页码的范围是:1~总页码		--%>
    <c:when test="${requestScope.page.pageTotal <= 5}">
        <c:set var="begin" value="1"></c:set>
        <c:set var="end" value="${requestScope.page.pageTotal}"></c:set>
    </c:when>
    <%--情况2:总页码大于5的情况。假设一共10--%>
    <c:when test="${requestScope.page.pageTotal > 5}">
        <c:choose>
            <%--小情况1:当前页码为前3个:123的情况,页码的范围是:1 - 5				--%>
            <c:when test="${requestScope.page.pageNo <= 3}">
                <c:set var="begin" value="1"></c:set>
                <c:set var="end" value="5"></c:set>
            </c:when>
            <%--小情况2:当前页码为最后3个,8910的情况,页码范围是:总页码-4 ~ 总页码--%>
            <c:when test="${requestScope.page.pageNo > requestScope.page.pageTotal-3}">
                <c:set var="begin" value="${requestScope.page.pageTotal-4}"></c:set>
                <c:set var="end" value="${requestScope.page.pageTotal}"></c:set>
            </c:when>
            <%--小情况334567的情况,页码范围是:当前页码-2 ~ 当前页码+2--%>
            <c:otherwise>
                <c:set var="begin" value="${requestScope.page.pageNo-2}"></c:set>
                <c:set var="end" value="${requestScope.page.pageNo+2}"></c:set>
            </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>
<%--页码输出的结束--%>

3.6、解决分页对添、删、改的影响

在这里插入图片描述

添加

我们需要添加到最后一页,并且添加后需要跳转到最后一页

book_manager.jsp 中的a标签链接改改,并带参数

<td><a  href="pages/manager/book_edit.jsp?action=page&pageNo=${requestScope.page.pageTotal}"+>添加图书</a></td>

传给book_edit.jsp,当写好要添加的图书信息,提交表单的时候,启动bookServlet程序,调用add()方法

...
<form action="manager/bookServlet" method="get">
    <input type="hidden" name="pageNo" value="${param.pageNo}">
    <input type="hidden" name="action" value="${empty param.id ? "add" : "update"}">
    <input type="hidden" name="id" value="${requestScope.book.id}">
    <table>
...

add()方法也需要,在重定向的地方改改

protected void add(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    // 解决添加导致页面加1页,但是跳转没有加1页
    int pageNo = WebUtils.parseInt(request.getParameter("pageNo"),0);
    pageNo ++;
    //        1、获取请求的参数==封装成为Book对象
    Book book = WebUtils.copyParamToBean(request.getParameterMap(), new Book());

    //        2、调用BookService. addBook()保存图书
    bookService.addBook(book);

    //        3、跳到图书列表页面 /manager/bookServlet?action=list
    response.sendRedirect(request.getContextPath()+"/manager/bookServlet?action=page&pageNo="+pageNo);
}

删除

book_manager.jsp 中的a标签链接改改,并带当前页面参数

<td><a class="deleteClass" href="manager/bookServlet?action=delete&id=${book.id}&pageNo=${requestScope.page.pageNo}">删除</a></td>

delete()方法也需小改,重定向位置

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

修改

<td><a href="manager/bookServlet?action=getBook&id=${book.id}&pageNo=${requestScope.page.pageNo}">修改</a></td>

update()方法也需小改,重定向位置

protected void update(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //        1、获取请求的参数==封装成为Book对象
    Book book = WebUtils.copyParamToBean(request.getParameterMap(), new Book());
    //        2、调用BookService.updateBook(book) ;修改图书
    bookService.updateBook(book);
    //        3、重定向回图书列表管理页面 地址: /工程名/manager /bookServlet?action=list
    response.sendRedirect(request.getContextPath()+"/manager/bookServlet?action=page&pageNo="+request.getParameter("pageNo"));
}

🤗4、前台数据的展示

小小解释下:

前台页面是

在这里插入图片描述

后台页面是

在这里插入图片描述

4.1、初步实现

在这里插入图片描述

我们需要现在page/clinet 下创建一个和 index.jsp一模一样的index.jsp

写一个ClientBookServlet程序

public class ClientBookServlet extends BaseServlet{
    private BookService bookService = new BookServiceImpl();

    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);
        //3保存age对象到Request域中
        request.setAttribute("page",page);
        //4请求转发到/pages/manager/book_manager.jsp页面
        request.getRequestDispatcher("/pages/client/book_manager.jsp").forward(request,response);
    }

}

配置XML

<servlet>
    <servlet-name>ClientBookServlet</servlet-name>
    <servlet-class>com.flzj.web.ClientBookServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>ClientBookServlet</servlet-name>
    <url-pattern>/client/bookServlet</url-pattern>
</servlet-mapping>

遍历显示数据库显示,页面

<c:forEach items="${requestScope.page.items}" var="book">
    <div class="b_list">
        <div class="img_div">
            <img class="book_img" alt="" src="${book.imgPath}" />
        </div>
        <div class="book_info">
            <div class="book_name">
                <span class="sp1">书名:</span>
                <span class="sp2">${book.name}</span>
            </div>
            <div class="book_author">
                <span class="sp1">作者:</span>
                <span class="sp2">${book.author}</span>
            </div>
            <div class="book_price">
                <span class="sp1">价格:</span>
                <span class="sp2">¥${book.price}</span>
            </div>
            <div class="book_sales">
                <span class="sp1">销量:</span>
                <span class="sp2">${book.sales}</span>
            </div>
            <div class="book_amount">
                <span class="sp1">库存:</span>
                <span class="sp2">${book.stock}</span>
            </div>
            <div class="book_add">
                <button>加入购物车</button>
            </div>
        </div>
    </div>
</c:forEach>

前台的分页效果,和后台的一样,只是路径需要换一换

在这里插入图片描述

4.2、分页的抽取

前台的分页条和后台的分页条完全一致,说以我们可以抽取分页条,

可以作为page的url属性

// 分页条的请求地址
private  String url;
//get、set 省略

前台页面

index.jsp页面(前台页面)中替换

在这里插入图片描述

在前台的Servlet(ClinetBookServlet)中加上

page.setUrl("client/bookServlet?action=page");

后台页面

book_manager.jsp(后台页面)也需要替换

在这里插入图片描述

在后台的Servlet(BookServlet)中加上

page.setUrl("manager/bookServlet?action=page");

这时候我们会惊奇的发现,前台的分页和后台的分页一样,所以我们可以把它弄成一个静态包含,内容放到page_nav.jsp

<div id="page_nav">
    <%--大于首页页码才显示--%>
    <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:choose>
        <%-- 情况1:如果总页码小于等于5的情况,页码的范围是:1~总页码		--%>
        <c:when test="${requestScope.page.pageTotal <= 5}">
            <c:set var="begin" value="1"></c:set>
            <c:set var="end" value="${requestScope.page.pageTotal}"></c:set>
        </c:when>
        <%--情况2:总页码大于5的情况。假设一共10--%>
        <c:when test="${requestScope.page.pageTotal > 5}">
            <c:choose>
                <%--小情况1:当前页码为前3个:123的情况,页码的范围是:1 - 5				--%>
                <c:when test="${requestScope.page.pageNo <= 3}">
                    <c:set var="begin" value="1"></c:set>
                    <c:set var="end" value="5"></c:set>
                </c:when>
                <%--小情况2:当前页码为最后3个,8910的情况,页码范围是:总页码-4 ~ 总页码--%>
                <c:when test="${requestScope.page.pageNo > requestScope.page.pageTotal-3}">
                    <c:set var="begin" value="${requestScope.page.pageTotal-4}"></c:set>
                    <c:set var="end" value="${requestScope.page.pageTotal}"></c:set>
                </c:when>
                <%--小情况334567的情况,页码范围是:当前页码-2 ~ 当前页码+2--%>
                <c:otherwise>
                    <c:set var="begin" value="${requestScope.page.pageNo-2}"></c:set>
                    <c:set var="end" value="${requestScope.page.pageNo+2}"></c:set>
                </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="${requestScope.page.url}&pageNo=${i}">${i}</a>
        </c:if>
    </c:forEach>
    <%--页码输出的结束--%>

    <%--	如果已经是最后一页,就不会显示下一页,末页	--%>
    <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.pageTotal}">末页</a>
    </c:if>
    共${requestScope.page.pageTotal}页,${requestScope.page.pageTotalCount}条记录
    到第<input value="${param.pageNo}" name="pn" id="pn_input"/><input id="searchPageBtn" type="button" value="确定">
    <script>
        $(function (){
            $("#searchPageBtn").click(function (){
                let pageNo = $("#pn_input").val();
                // js 提供了一个location地址栏对象
                // 它有一个属性href 它可以获取浏览器地址栏的地址
                // href属性可读,可写
                location.href = "${pageScope.basePath}${requestScope.page.url}&pageNo="+pageNo;
            });
        });
    </script>
</div>

静态包含

<%@ include file="/pages/common/page_nav.jsp"%>

🤗5、前台价格搜索

在这里插入图片描述

图解

在这里插入图片描述

编写代码

ClientBookServlet程序

protected void pageByPrice(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    int pageNo = WebUtils.parseInt(request.getParameter("pageNo"),1);
    int pageSize = WebUtils.parseInt(request.getParameter("pageSize"), Page.PAGE_SIZE);
    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<Book> page = bookService.pageByPrice(pageNo,pageSize,min,max);
    page.setUrl("client/bookServlet?action=pageByPrice");
    //3保存age对象到Request域中
    request.setAttribute("page",page);
    //4请求转发到/pages/manager/book_manager.jsp页面
    request.getRequestDispatcher("/pages/client/index.jsp").forward(request,response);

}

BookServiceImpl 修改这两处

@Override
public Page pageByPrice(int pageNo, int pageSize, int min, int max) {
    Page<Book> page = new Page<Book>();

    // 设置每页显示的数量
    page.setPageSize(pageSize);

    // 求总记录数
    Integer pageTotalCount = bookDao.queryForPageTotalCountByPrice(min,max);
    // 设置总记录数
    page.setPageTotalCount(pageTotalCount);

    // 求总页码
    Integer pageTotal = pageTotalCount / pageSize ;
    if(pageTotalCount % pageSize > 0){
        pageTotal++;
    }
    // 设置总页码
    page.setPageTotal(pageTotal);


    // 设置当前页码
    page.setPageNo(pageNo);

    // 求当前页开始索引
    int begin = (page.getPageNo() - 1) * pageSize;
    // 求当前页数据
    List<Book> items = bookDao.queryForPageItemsByPrice(begin,pageSize,min,max);
    // 设置当前页数据
    page.setItems(items);

    return page;
}

BookDao程序

@Override
public Integer queryForPageTotalCountByPrice(int min, int max) {
    String sql = "select count(*) from t_book where price between ? and ?";
    Number count = (Number) queryForSingleValue(sql, min, max);
    return count.intValue();
}
@Override
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 ? limit ?,?";
    return queryForList(Book.class,sql,min,max,begin,pageSize);
}

测试代码

Dao层

@Test
public void queryForPageTotalCountByPrice() {
    System.out.println(bookDao.queryForPageTotalCountByPrice(10,50));
}
@Test
public void queryForPageItemsByPrice() {
    for (Book book : bookDao.queryForPageItemsByPrice(1,4,1, 50)) {
        System.out.println(book);
    }
}

Service层

@Test
public void pageByPrice(){
    System.out.println(bookService.pageByPrice(1,4,1,50));
}

Web层

在这里插入图片描述

效果优化

价格回显

价格:<input id="min" type="text" name="min" value="${param.min}">-
<input id="max" type="text" name="max" value="${param.max}">

分页中不带价格区间的bug

StringBuilder sb = new StringBuilder("client/bookServlet?action=pageByPrice");
// 如果有最小价格的参数,追加到分页条的地址栏参数中
if(request.getParameter("min") != null){
    sb.append("&min=").append(request.getParameter("min"));
}
// 如果有最小价格的参数,追加到分页的地址栏参数中
if(request.getParameter("max") != null){
    sb.append("&max=").append(request.getParameter("max"));
}
page.setUrl(sb.toString());

我的寒假结束了,奠😭

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值