尚硅谷书城项目

文章目录


一、JavaEE 项目的三层架构

在这里插入图片描述

在这里插入图片描述


二、项目——阶段二(已经把静态页面做好)

1、阶段二要求用户注册和登陆的实现

需求 1:用户注册

需求如下:
1)访问注册页面
2)填写注册信息,提交给服务器
3)服务器应该保存用户
4)当用户已经存在----提示用户注册 失败,用户名已存在
5)当用户不存在-----注册成功

需求 2:用户登陆

需求如下:
1)访问登陆页面
2)填写用户名密码后提交
3)服务器判断用户是否存在
4)如果登陆失败 —>>>>返回用户名或者密码错误信息
5)如果登录成功 —>>>> 返回登陆成功 信息

2、搭建环境

在这里插入图片描述

3、创建数据库

DROP DATABASE IF EXISTS book;

CREATE DATABASE book;

USE book;
CREATE TABLE book_user(
	`id` INT PRIMARY KEY AUTO_INCREMENT,
	`username` VARCHAR(100) NOT NULL UNIQUE,
	`password` VARCHAR(32) NOT NULL,
	`email` VARCHAR(200)
);

INSERT INTO book_user(`username`,`password`,`email`) 
VALUE ('cen111','123321123','cencen@qq.com');

SELECT * FROM book_user;

在这里插入图片描述

4、编写JavaBean对象

public class User {
    private Integer id;
    private String username;
    private String password;
    private String email;

    public User() {
    }

    public User(Integer id, String username, String password, String email) {
        this.id = id;
        this.username = username;
        this.password = password;
        this.email = email;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", email='" + email + '\'' +
                '}';
    }
}

5、编写工具类JDBCUtils

用来管理数据库

(1)导入jar包(数据库和连接池需要)

在这里插入图片描述

(2)在 src 源码目录下编写 jdbc.properties 属性配置文件

username=root
password=20020809l
url=jdbc:mysql://localhost:3306/book
driverClassName=com.mysql.jdbc.Driver
initialSize=5
maxActive=10
# 连接池最大连接数量

(3)编写 JdbcUtils 工具类

public class JDBCUtils {

    private static DruidDataSource dataSource;
    static {

        try {
            Properties properties = new Properties();
            //读取 jdbc.properties属性配置文件
            InputStream inputStream = JDBCUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");
            //从流中加载数据
            properties.load(inputStream);
            //创建数据库连接池
            dataSource = (DruidDataSource) DruidDataSourceFactory.createDataSource(properties);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    //获取数据库连接池中的连接
    //如果返回null就是获取连接失败,有值才成功
    public static Connection getconnection(){
        Connection conn = null;
        try {
            conn = dataSource.getConnection();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return conn;

    }
    //关闭连接,放回数据库连接池
    public static void close(Connection conn){
        if (conn!=null){
            try {
                conn.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }
}

(4)JdbcUtils 测试

public class JdbcUtilsTest {
    @Test
    public void testJdbcUtils(){
        for (int i=0;i<30;i++){
            Connection connection = JDBCUtils.getconnection();
            System.out.println(JDBCUtils.getconnection());
            //数据库只有那么大,每次用完记得释放
            JDBCUtils.close(connection);
        }
    }
}

6、编写BaseDao

在这里插入图片描述

(1)编写BaseDao

public class BaseDao {
    //使用DbUtils操作数据库
    private QueryRunner queryRunner = new QueryRunner();

    /**
     * update() 方法用来执行:Insert\Update\Delete语句
     *
     * @return 如果返回-1,说明执行失败<br/>返回其他表示影响的行数
     */
    public int updata(String sql,Object...args){
        Connection connection = JDBCUtils.getconnection();
        try {
            return queryRunner.update(connection,sql,args);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }finally {
            JDBCUtils.close(connection);
        }
        return -1;
    }

    /**
     * @Description 查询返回一个JavaBean的sql语句
     * @Param  type 返回的对象类型
     * @Param  sql 执行的sql语句
     * @Param  args sql对应的参数值
     * @Param  <T>  返回的类型的泛型
     * @return
     */
    public <T> T queryForOne(Class<T> type,String sql,Object ... args){
        Connection con = JDBCUtils.getconnection();
        try {
            return queryRunner.query(con,sql,new BeanHandler<T>(type),args)
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }finally {
            JDBCUtils.close(con);
        }
        return null;
    }

    /**
     * @Description 查询返回多个JavaBean的sql语句
     * @Param  type 返回的对象类型
     * @Param  sql 执行的sql语句
     * @Param  args sql对应的参数值
     * @Param  <T>  返回的类型的泛型
     * @return
     */
    public <T>List<T> queryForList(Class<T> type,String sql,Object ... args){
        Connection con = JDBCUtils.getconnection();
        try {
            return queryRunner.query(con,sql,new BeanListHandler<T>(type),args);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }finally {
            JDBCUtils.close(con);
        }
        return null;
    }

    /**
     * 执行返回一行一列的sql语句
     * @param sql   执行的sql语句
     * @param args  sql对应的参数值
     * @return
     */
    public Object queryForSingleValue(String sql,Object...args){
        Connection conn = JDBCUtils.getconnection();
        try {
            return queryRunner.query(conn,sql,new ScalarHandler(),args);

        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }finally {
            JDBCUtils.close(conn);
        }
        return null;
    }
}

7、编写 UserDao 和测试

UserDao 接口:

public class BaseDao {
    //使用DbUtils操作数据库
    private QueryRunner queryRunner = new QueryRunner();

    /**
     * update() 方法用来执行:Insert\Update\Delete语句
     *
     * @return 如果返回-1,说明执行失败<br/>返回其他表示影响的行数
     */
    public int updata(String sql,Object...args){
        Connection connection = JDBCUtils.getconnection();
        try {
            return queryRunner.update(connection,sql,args);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }finally {
            JDBCUtils.close(connection);
        }
        return -1;
    }

    /**
     * @Description 查询返回一个JavaBean的sql语句
     * @Param  type 返回的对象类型
     * @Param  sql 执行的sql语句
     * @Param  args sql对应的参数值
     * @Param  <T>  返回的类型的泛型
     * @return
     */
    public <T> T queryForOne(Class<T> type,String sql,Object ... args){
        Connection con = JDBCUtils.getconnection();
        try {
            return queryRunner.query(con,sql,new BeanHandler<T>(type),args);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }finally {
            JDBCUtils.close(con);
        }
        return null;
    }

    /**
     * @Description 查询返回多个JavaBean的sql语句
     * @Param  type 返回的对象类型
     * @Param  sql 执行的sql语句
     * @Param  args sql对应的参数值
     * @Param  <T>  返回的类型的泛型
     * @return
     */
    public <T>List<T> queryForList(Class<T> type,String sql,Object ... args){
        Connection con = JDBCUtils.getconnection();
        try {
            return queryRunner.query(con,sql,new BeanListHandler<T>(type),args);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }finally {
            JDBCUtils.close(con);
        }
        return null;
    }

    /**
     * 执行返回一行一列的sql语句
     * @param sql   执行的sql语句
     * @param args  sql对应的参数值
     * @return
     */
    public Object queryForSingleValue(String sql,Object...args){
        Connection conn = JDBCUtils.getconnection();
        try {
            return queryRunner.query(conn,sql,new ScalarHandler(),args);

        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }finally {
            JDBCUtils.close(conn);
        }
        return null;
    }
}

UserDaoImpl 实现类:

public class UserDaoImpl extends BaseDao implements UserDao {
    @Override
    public User queryUserByUsername(String username) {
        String sql = "select `id`,`username`,`password`,`email` from book_user where username = ?";

        return queryForOne(User.class,sql,username);

    }

    @Override
    public User queryUserByUsernameAndPassword(String username, String password) {
        String sql = "select `id`,`username`,`password`,`email` from book_user where username = ? and password = ?";
        return queryForOne(User.class, sql, username,password);
    }

    @Override
    public int saveUser(User user) {
        String sql = "insert into book_user(`username`,`password`,`email`) values(?,?,?)";
        return updata(sql, user.getUsername(),user.getPassword(),user.getEmail());

    }
}

UserDao 测试:

public class UserDaoTest {
    UserDao userDao = new UserDaoImpl();
    @Test
    public void queryUserByUsername() {
        if(userDao.queryUserByUsername("asdcen111")==null){
            System.out.println("用户名可用");
        }else{
            System.out.println("用户名已存在");
        }
    }

    @Test
    public void queryUserByUsernameAndPassword() {
        if (userDao.queryUserByUsernameAndPassword("cen111","123321123") == null){
            System.out.println("用户名或密码错误,登陆失败");
        }else{
            System.out.println("登录成功");
        }
    }

    @Test
    public void saveUser() {
        System.out.println(userDao.saveUser(new User(null,"www", "12341234", "123421@qq.com")));
    }
}

8、编写 UserService 和测试

UserService 接口:

public interface UserService {
    /**
     * 注册用户
     * @Param user
     */
    public void registUser(User user);

    /**
     * 登录
     * @Param user
     * @return 如果返回null,说明登录失败,返回有值,是登录成功
     */
    public User login(User user);

    /**
     * 检查 用户名是否可用
     * @Param username
     * @return 返回true表示用户已存在,返回false表示用户名可用
     * @date 2022/1/16 22:18
     * @auther cencen
     */
    public boolean existsUsername(String username);

}

UserServiceImpl 实现类

public class UseServiceImpl implements UserService {
    private UserDao userDao = new UserDaoImpl();
    @Override
    public void registUser(User user) {
        userDao.saveUser(user);
    }

    @Override
    public User login(User user) {
        return userDao.queryUserByUsernameAndPassword(user.getUsername(),user.getPassword());
    }

    @Override
    public boolean existsUsername(String username) {
        if (userDao.queryUserByUsername(username) == null){
            //等于null,说明没看到,没查到表示可用
            return false;
        }
        return true;
    }
}

UserService 测试

public class UserServiceTest {

    UserService userService = new UseServiceImpl();

    @Test
    public void registUser() {
        userService.registUser(new User(null,"weq","waerwef","1234124@123.com"));
    }

    @Test
    public void login() {
        System.out.println(userService.login(new User(null,"werg","43252",null)));
        System.out.println(userService.login(new User(null,"cen111","123321123",null)));
    }

    @Test
    public void existsUsername() {
        if (userService.existsUsername("fdsae")){
            System.out.println("用户名已存在");
        }else{
            System.out.println("用户名可用");
        }
    }
}

9、编写web层

(1)用户注册流程

在这里插入图片描述

(2)修改 regist.html 和 regist_success.html 页面

在这里插入图片描述

(3)编写 RegistServlet 程序

public class RegistServlet extends HttpServlet {
    private  UserService userService = new UseServiceImpl();

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //  1、获取请求的参数
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        String email = req.getParameter("email");
        String code = req.getParameter("code");

        //  2、检查 验证码是否正确  === 写死,要求验证码为:abcde
        if ("abcdef".equalsIgnoreCase(code)){
//正确
//        3、检查 用户名是否可用
            if (userService.existsUsername(username)){
//            不可=》   跳回注册页面
                System.out.println("用户名【"+username+"】已存在!");
                req.getRequestDispatcher("/pages/user/regist.html").forward(req,resp);//跳回注册页面

            }else {
//            可用=》  调用Sservice保存到数据库
                userService.registUser(new User(null,username,password,email));
                //   跳到注册成功页面 regist_success.html
                req.getRequestDispatcher("/pages/user/regist_success.html").forward(req,resp);//跳回注册页面

            }

       }else {
//不正确
//            跳回注册页面
            System.out.println("验证码错误");
            req.getRequestDispatcher("/pages/user/regist.html").forward(req,resp);
        }
    }
}

10、用户登录功能的实现

(1)图解用户登录

在这里插入图片描述

(2)修改 login.html 页面和 login_success.html 页面

在这里插入图片描述

(3)LoginServlet 程序

public class LoginServlet extends HttpServlet {
    UserService userService = new UseServiceImpl();
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //  1、获取请求的参数
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        // 调用 userService.login()登录处理业务
        User loginUser = userService.login(new User(null,username,password,null));
        //如果等于null,则登录失败
        if (loginUser == null){
            //跳回登录页面
            req.getRequestDispatcher("/pages/user/login.html").forward(req,resp);
        }else {
            //登录成功
            //跳转到成功页面login_success.html
            req.getRequestDispatcher("/pages/user/login_success.html").forward(req,resp);
        }
    }
}

三、项目——阶段三

1、页面 jsp 动态化

1、在 html 页面顶行添加 page 指令。

<%@ page contentType=“text/html;charset=UTF-8” language=“java” %>

2、修改文件后缀名为:.jsp
3、使用 IDEA 搜索替换.html 为.jsp(快捷键:Ctrl+Shift+R)

2、抽取页面中相同的内容

使用格式:在原语句位置替换成

<%@include file=“路径”%>

i. head 中 css、jquery、base 标签

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!-- 动态使用base标签 -->
<%
    String basePath = request.getScheme()
            + "://"
            + request.getServerName()
            + ":"
            + request.getServerPort()
            + request.getContextPath()
            + "/";
%>
<!--写base标签,永远固定相对路径跳转的结果-->
<base href="<%=basePath%>">
<link type="text/css" rel="stylesheet" href="static/css/style.css" >
<script type="text/javascript" src="static/script/jquery-1.7.2.js"></script>

ii. 每个页面的页脚

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<div id="bottom">
		<span>
			尚硅谷书城.Copyright &copy;2015
		</span>
</div>

iii. 登录成功后的菜单

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<div>
    <span>欢迎<span class="um_span">韩总</span>光临尚硅谷书城</span>
    <a href="../order/order.jsp">我的订单</a>
    <a href="../../index.jsp">注销</a>&nbsp;&nbsp;
    <a href="../../index.jsp">返回</a>
</div>

iv. manager 模块的菜单

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

3、登录,注册错误提示,及表单回显

以登录回显为示例:
Servlet 程序端需要添加回显信息到 Request 域中
在这里插入图片描述
jsp 页面,需要输出回显信息
在这里插入图片描述

4、BaseServlet 的抽取(优化)

在实际的项目开发中,一个模块,一般只使用一个 Servlet 程序。

代码优化一:合并 LoginServlet 和 RegistServlet 程序为 UserServlet 程序
在这里插入图片描述

public class UserServlet extends HttpServlet {
	private UserService userService = new UserServiceImpl();
	/**
	* 处理登录的功能
	* @param req
	* @param resp
	* @throws ServletException
	* @throws IOException
	*/
	protected void login(HttpServletRequest req, HttpServletResponse resp) throws ServletException,IOException {
		// 1、获取请求的参数
		String username = req.getParameter("username");
		String password = req.getParameter("password");
		// 调用 userService.login()登录处理业务
		User loginUser = userService.login(new User(null,username, password, null));
		// 如果等于 null,说明登录 失败!
		if (loginUser == null) {
			// 把错误信息,和回显的表单项信息,保存到 Request 域中
			req.setAttribute("msg","用户或密码错误!");
			req.setAttribute("username", username);
			// 跳回登录页面
			req.getRequestDispatcher("/pages/user/login.jsp").forward(req, resp);
		} else {
			// 登录 成功
			//跳到成功页面 login_success.html
			req.getRequestDispatcher("/pages/user/login_success.jsp").forward(req, resp);
		}
	}
	/**
	* 处理注册的功能
	* @param req
	* @param resp
	* @throws ServletException
	* @throws IOException
	*/
	protected void regist(HttpServletRequest req, HttpServletResponse resp) throws ServletException,IOException {
		// 1、获取请求的参数
		String username = req.getParameter("username");
		String password = req.getParameter("password");
		String email = req.getParameter("email");
		String code = req.getParameter("code");
		// 2、检查 验证码是否正确 === 写死,要求验证码为:abcde
		if ("abcde".equalsIgnoreCase(code)) {
		// 3、检查 用户名是否可用
			if (userService.existsUsername(username)) {
				System.out.println("用户名[" + username + "]已存在!");
				// 把回显信息,保存到 Request 域中
				req.setAttribute("msg", "用户名已存在!!");
				req.setAttribute("username", username);
				req.setAttribute("email", email);
				// 跳回注册页面
				req.getRequestDispatcher("/pages/user/regist.jsp").forward(req, resp);
			} else {
				// 可用
				// 调用 Sservice 保存到数据库
				userService.registUser(new User(null, username, password, email));
				// 跳到注册成功页面 regist_success.jsp
				req.getRequestDispatcher("/pages/user/regist_success.jsp").forward(req, resp);
			}
		} else {
			// 把回显信息,保存到 Request 域中
			req.setAttribute("msg", "验证码错误!!");
			req.setAttribute("username", username);
			req.setAttribute("email", email);
			System.out.println("验证码[" + code + "]错误");
			req.getRequestDispatcher("/pages/user/regist.jsp").forward(req, resp);
		}
	}
	protected void doPost(HttpServletRequest req,HttpServletResponse resp) throws ServletException,IOException {
		String action = req.getParameter("action");
		if ("login".equals(action)) {
			login(req, resp);
		} else if ("regist".equals(action)) {
			regist(req, resp);
		}
	}
}

在这里插入图片描述

优化代码二:使用反射优化大量 else if 代码:

	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException,IOException {
		String action = req.getParameter("action");
		try {
			// 获取 action 业务鉴别字符串,获取相应的业务 方法反射对象
			Method method = this.getClass().getDeclaredMethod(action,HttpServletRequest.class,HttpServletResponse.class);
			// System.out.println(method);
			// 调用目标业务 方法
			method.invoke(this, req, resp);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

代码优化三:抽取 BaseServlet 程序。
在这里插入图片描述
BaseServlet 程序代码:

public abstract class BaseServlet extends HttpServlet {

    protected void doPost (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String action = req.getParameter("action");

        try {
            //获取action业务鉴别字符串,获取相应的业务 方法反射对象
            Method method = this.getClass().getDeclaredMethod(action,HttpServletRequest.class,HttpServletResponse.class);
            //System.out.println(method);
            //调用目标业务、方法
            method.invoke(this,req,resp);
        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}

5、数据的封装和抽取 BeanUtils 的使用

BeanUtils 工具类,它可以一次性的把所有请求的参数注入到 JavaBean 中。

BeanUtils 工具类,经常用于把 Map 中的值注入到 JavaBean 中,或者是对象属性值的拷贝操作。
可以省去获取完属性之后还要创建新对象 “new 对象”,然后赋值 "对象.setXxx”

BeanUtils 它不是 Jdk 的类。而是第三方的工具类。所以需要导包。

1、导入需要的 jar 包:
commons-beanutils-1.8.0.jar
commons-logging-1.1.1.jar
2、编写 WebUtils 工具类使用:

public class WebUtils {
    //把第一个参数从HttpServlet的变成Map目的在于,Map适用于dao层、servlet层、web层,但HttpServlet只适用于web层
    public static <T> T copyParamToBean(Map value, T bean){

        try {
            System.out.println("注入之前"+bean);
            //把所有请求的参数都注入到user对象中
            BeanUtils.populate(bean,value);
            System.out.println("注入之后:"+bean);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return bean;
    }
}

3、在UserServlet方法中
在这里插入图片描述


四、项目——阶段四:使用 EL 表达式修改表单回显

以登录为示例:
在这里插入图片描述


五、项目——阶段五:内容介绍

1、MVC 概念

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

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

jsp负责展示 数据处理应该交给servlet

View 视图:只负责数据和界面的显示,不接受任何与显示数据无关的代码,便于程序员和美工的分工合作——JSP/HTML。
Controller 控制器:只负责接收请求,调用业务层的代码处理请求,然后派发页面,是一个“调度者”的角色——Servlet。
转到某个页面。或者是重定向到某个页面。
Model 模型:将与业务逻辑相关的数据封装为具体的 JavaBean 类,其中不掺杂任何与数据处理相关的代码——JavaBean/domain/entity/pojo。

MVC 是一种思想
MVC 的理念是将软件代码拆分成为组件,单独开发,组合使用 (目的还是为了降低耦合度)。
在这里插入图片描述

2、图书模块

(1)编写图书模块的数据库表

create table book_books(
`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 book_books(`id` , `name` , `author` , `price` , `sales` , `stock` , `img_path`)
VALUES(NULL , 'java 从入门到放弃' , '国哥' , 80 , 9999 , 9 , 'static/img/default.jpg');

INSERT INTO book_books(`id` , `name` , `author` , `price` , `sales` , `stock` , `img_path`)
VALUES(NULL , '数据结构与算法' , '严敏君' , 78.5 , 6 , 13 , 'static/img/default.jpg');

INSERT INTO book_books(`id` , `name` , `author` , `price` , `sales` , `stock` , `img_path`)
VALUES(NULL , '怎样拐跑别人的媳妇' , '龙伍' , 68, 99999 , 52 , 'static/img/default.jpg');

INSERT INTO book_books(`id` , `name` , `author` , `price` , `sales` , `stock` , `img_path`)
VALUES(NULL , '木虚肉盖饭' , '小胖' , 16, 1000 , 50 , 'static/img/default.jpg');

INSERT INTO book_books(`id` , `name` , `author` , `price` , `sales` , `stock` , `img_path`)
VALUES(NULL , 'C++编程思想' , '刚哥' , 45.5 , 14 , 95 , 'static/img/default.jpg');

INSERT INTO book_books(`id` , `name` , `author` , `price` , `sales` , `stock` , `img_path`)
VALUES(NULL , '蛋炒饭' , '周星星' , 9.9, 12 , 53 , 'static/img/default.jpg');

INSERT INTO book_books(`id` , `name` , `author` , `price` , `sales` , `stock` , `img_path`)
VALUES(NULL , '赌神' , '龙伍' , 66.5, 125 , 535 , 'static/img/default.jpg');

INSERT INTO book_books(`id` , `name` , `author` , `price` , `sales` , `stock` , `img_path`)
VALUES(NULL , 'Java 编程思想' , '阳哥' , 99.5 , 47 , 36 , 'static/img/default.jpg');

INSERT INTO book_books(`id` , `name` , `author` , `price` , `sales` , `stock` , `img_path`)
VALUES(NULL , 'JavaScript 从入门到精通' , '婷姐' , 9.9 , 85 , 95 , 'static/img/default.jpg');

INSERT INTO book_books(`id` , `name` , `author` , `price` , `sales` , `stock` , `img_path`)
VALUES(NULL , 'cocos2d-x 游戏编程入门' , '国哥' , 49, 52 , 62 , 'static/img/default.jpg');

INSERT INTO book_books(`id` , `name` , `author` , `price` , `sales` , `stock` , `img_path`)
VALUES(NULL , 'C 语言程序设计' , '谭浩强' , 28 , 52 , 74 , 'static/img/default.jpg');

INSERT INTO book_books(`id` , `name` , `author` , `price` , `sales` , `stock` , `img_path`)
VALUES(NULL , 'Lua 语言程序设计' , '雷丰阳' , 51.5 , 48 , 82 , 'static/img/default.jpg');

INSERT INTO book_books(`id` , `name` , `author` , `price` , `sales` , `stock` , `img_path`)
VALUES(NULL , '西游记' , '罗贯中' , 12, 19 , 9999 , 'static/img/default.jpg');

INSERT INTO book_books(`id` , `name` , `author` , `price` , `sales` , `stock` , `img_path`)
VALUES(NULL , '水浒传' , '华仔' , 33.05 , 22 , 88 , 'static/img/default.jpg');

INSERT INTO book_books(`id` , `name` , `author` , `price` , `sales` , `stock` , `img_path`)
VALUES(NULL , '操作系统原理' , '刘优' , 133.05 , 122 , 188 , 'static/img/default.jpg');

INSERT INTO book_books(`id` , `name` , `author` , `price` , `sales` , `stock` , `img_path`)
VALUES(NULL , '数据结构 java 版' , '封大神' , 173.15 , 21 , 81 , 'static/img/default.jpg');

INSERT INTO book_books(`id` , `name` , `author` , `price` , `sales` , `stock` , `img_path`)
VALUES(NULL , 'UNIX 高级环境编程' , '乐天' , 99.15 , 210 , 810 , 'static/img/default.jpg');

INSERT INTO book_books(`id` , `name` , `author` , `price` , `sales` , `stock` , `img_path`)
VALUES(NULL , 'javaScript 高级编程' , '国哥' , 69.15 , 210 , 810 , 'static/img/default.jpg');

INSERT INTO book_books(`id` , `name` , `author` , `price` , `sales` , `stock` , `img_path`)
VALUES(NULL , '大话设计模式' , '国哥' , 89.15 , 20 , 10 , 'static/img/default.jpg');

INSERT INTO book_books(`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 book_books;

(2)编写图书模块的 JavaBean

public class Book {
    private Integer id;
    private String name;
    private String author;
    private BigDecimal price;
    private Integer sales;
    private Integer stock;
    private String img_path = "static/img/default.jpg";

    public Book() {
    }

    public Book(Integer id, String name, String author, BigDecimal price, Integer sales, Integer stock, String img_path) {
        this.id = id;
        this.name = name;
        this.author = author;
        this.price = price;
        this.sales = sales;
        this.stock = stock;
        //要求给定的图书封面图书路径不能为空
        if (img_path != null && "".equals(img_path)){
            this.img_path = img_path;
        }

    }

    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 getImg_path() {
        return img_path;
    }

    public void setImg_path(String img_path) {
        if (img_path != null && "".equals(img_path)){
            this.img_path = img_path;
        }
    }

    @Override
    public String toString() {
        return "Book{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", author='" + author + '\'' +
                ", price=" + price +
                ", sales=" + sales +
                ", stock=" + stock +
                ", img_path='" + img_path + '\'' +
                '}';
    }
}

(3)编写图书模块的 Dao 和测试 Dao

Dao 接口

public interface BookDao {

    //添加书
    public int addBook(Book book);

    //根据id删除书
    public int deleteBookById(Integer id);

    //修改
    public int updateBook(Book book);

    //查询一
    public Book queryBookById(Integer id);

    //查询二
    public List<Book> queryBooks();
}

BookDaoImpl 实现类:

public class BookDaoImpl extends BaseDao implements BookDao {
    @Override
    public int addBook(Book book) {

        String sql = "insert into book_books(`name` , `author` , `price` , `sales` , `stock` , `img_path`) values(?,?,?,?,?,?)";

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

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

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

    }

    @Override
    public Book queryBookById(Integer id) {
        String sql = "select `id` , `name` , `author` , `price` , `sales` , `stock` , `img_path` from book_books where id = ?";

        return queryForOne(Book.class,sql,id);
    }

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

BookDao 的测试

public class BookDaoTest {

    private BookDao bookDao = new BookDaoImpl();

    @Test
    public void addBook() {
        //bookDao.addBook(new Book(null,"侠盗猎车手","191125",new BigDecimal(9999),1100000,0,null));
        bookDao.addBook(new Book(null,"国哥为什么这么帅!", "191125", new BigDecimal(9999),1100000,0,null
        ));
    }

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

    @Test
    public void updateBook() {
        bookDao.updateBook(new Book(21,"大家都可以这么帅!", "国哥", new BigDecimal(9999),1100000,0,null
        ));
    }

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

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

注意url加上 ?useUnicode=true&characterEncoding=UTF-8
否则插入数据库会乱码

(4)编写图书模块的 Service 和测试 Service

BookService 接口

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();
}

BookServiceImpl 实现类:

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();
	}
}

BookService 的测试:

public class BookServiceTest {
	private BookService bookService = new BookServiceImpl();
	@Test
	public void addBook() {
		bookService.addBook(new Book(null,"国哥在手,天下我有!", "1125", new BigDecimal(1000000),100000000, 0, null));
	}
	@Test
	public void deleteBookById() {
		bookService.deleteBookById(22);
	}
	@Test
	public void updateBook() {
		bookService.updateBook(new Book(22,"社会我国哥,人狠话不多!", "1125", new BigDecimal(999999),10, 111110, null));
	}
	@Test
	public void queryBookById() {
		System.out.println(bookService.queryBookById(22));
	}
	@Test
	public void queryBooks() {
		for (Book queryBook : bookService.queryBooks()) {
			System.out.println(queryBook);
		}
	}
}

(5)编写图书模块的 Web 层,和页面联调测试

1.5.1、图书列表功能的实现

1、图解列表功能流程:

jsp负责展示 数据处理应该交给servlet
在这里插入图片描述
2、BookServlet 程序中添加 list 方法

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

3、修改【图书管理】请求地址
在manager_menu.jsp中
在这里插入图片描述
4、修改 pages/manager/book_manager.jsp 页面的数据遍历输出

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>图书管理</title>
	<!-- 静态包含base标签,css样式,jQuery文件 -->
	<%@include file="/pages/common/head.jsp"%></head>
<body>
	
	<div id="header">
			<img class="logo_img" alt="" src="../../static/img/logo.gif" >
			<span class="wel_word">图书管理系统</span>
			<!-- 静态包含manager管理模块的菜单 -->
			<%@include file="/pages/common/manager_menu.jsp"%>

	</div>
	
	<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>
//---------------------------------------------
			<tr>
				<td></td>
				<td></td>
				<td></td>
				<td></td>
				<td></td>
				<td></td>
			    <td><a href="pages/manager/book_edit.jsp">添加图书</a></td>			</tr>

			</tr>	
		</table>
	</div>
	<%@include file="/pages/common/footer.jsp"%>
</body>
</html>
1.5.2、前后台的简单介绍

在这里插入图片描述

1.5.3、添加图书功能的实现

1.5.3.1、添加图书流程细节:

在这里插入图片描述
1.5.3.2、问题说明:表单重复提交:
当用户提交完请求,浏览器会记录下最后一次请求的全部信息。当用户按下功能键 F5,就会发起浏览器记录的最后一次
请求。

1.5.3.3、BookServlet 程序中添加 add 方法

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

1.5.3.4、修改 book_edit.jsp 页面
在这里插入图片描述

1.5.4、删除图书功能的实现

1.5.4.1、图解删除流程:
在这里插入图片描述
1.5.4.2、BookServlet 程序中的 delete 方法:

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

1.5.4.3、给 WebUtils 工具类添加转换 int 类型的工具方法

	/**
	* 将字符串转换成为 int 类型的数据
	* @param strInt
	* @param defaultValue
	* @return
	*/
	public static int parseInt(String strInt,int defaultValue) {
		try {
			return Integer.parseInt(strInt);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return defaultValue;
	}

1.5.4.4、修改删除的连接地址:
在这里插入图片描述
1.5.4.5、给删除添加确认提示操作:
在book_manager.jsp中

	<script type="text/javascript">
		$(function () {
			$("a.deleteClass").click(function () {
				// 在事件的function函数中,有一个this对象。这个this对象,是当前正在响应事件的dom对象。
				/**
				 * confirm是确认提示框函数
				 * 参数是它的提示内容
				 * 它有两个按钮,一个确认,一个是取消。
				 * 返回true表示点击了,确认,返回false表示点击取消。
				 *
				 * td:first  =>表单第一列元素的意思
				 */
				return confirm("你确定要删除【" + $(this).parent().parent().find("td:first").text() + "】?");
				// return false// 阻止元素的默认行为===不提交请求

			})
		})
	</script>
1.5.5、修改图书功能的实现

1.5.5.1:图解修改图书细节:

在这里插入图片描述
1.5.5.2、更新【修改】的请求地址:
在这里插入图片描述
1.5.5.3、BookServlet 程序中添加 getBook 方法:

protected void getBook(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //1.获取请求的参数图书编号
        int id = WebUtils.parseInt(req.getParameter("id"),0);
        //2.调用bookServlet。queryBookById查询图书
        Book book = bookService.queryBookById(id);
        //3.保存图书到Request域中
        req.setAttribute("book",book);
        //4.请求转发到 pages/manager/book_edit.jsp页面
        req.getRequestDispatcher("/pages/manager/book_edit.jsp").forward(req,resp);
    }

1.5.5.4、在 book_edit.jsp 页面中显示修改的数据

		<div id="main">
			<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>
		</div>

1.5.5.5、在 BookServlet 程序中添加 update 方法

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

1.5.5.6、解决 book_edit.jsp 页面,即要实现添加,又要实现修改操作。
在这里插入图片描述


六、项目——阶段五:分页

1、图书分页

(1)分页模块的分析

在这里插入图片描述

(2)分页模型 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;

}

(3)分页的初步实现

BookDao 代码:

    //求总的记录数
	@Override
	public Integer queryForPageTotalCount() {
		String sql = "select count(*) from t_book";
		//Number为所有包装类的父类
		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` imgPathfrom t_book limit ?,?";
	return queryForList(Book.class,sql,begin,pageSize);
	}

BookService 代码

	@Override
	public Page<Book> page(int pageNum, int pageSize) {
		Page<Book> page = new Page<Book>();
		// 设置当前页码
		page.setPageNum(pageNum);
		// 设置每页显示的数量
		page.setPageSize(pageSize);
		// 求总记录数
		Integer pageTotalCount = bookDao.queryForPageTotalCount();
		// 设置总记录数
		page.setPageTotalCount(pageTotalCount);
		// 求总页码
		Integer pageTotal = pageTotalCount / pageSize;
		if (pageTotalCount % pageSize > 0) {
			pageTotal+=1;
		}
		// 设置总页码
		page.setPageTotal(pageTotal);
		// 求当前页数据的开始索引
		int begin = (page.getPageNum() - 1) * pageSize;
		// 求当前页数据
		List<Book> items = bookDao.queryForPageItems(begin,pageSize);
		// 设置当前页数据
		page.setItems(items);
	
		return page;
}

BookServlet 程序的代码:

	/**
	* 处理分页功能
	* @param req
	* @param resp
	* @throws ServletException
	* @throws IOException
	*/
	protected void page(HttpServletRequest req, HttpServletResponse resp) throws ServletException,IOException {
		//1 获取请求的参数 pageNo 和 pageSize
		int pageNum = WebUtils.parseInt(req.getParameter("pageNum"), 1);
		int pageSize = WebUtils.parseInt(req.getParameter("pageSize"), Page.PAGE_SIZE);
		//2 调用 BookService.page(pageNo,pageSize):Page 对象
		Page<Book> page = bookService.page(pageNum,pageSize);
		//3 保存 Page 对象到 Request 域中
		req.setAttribute("page",page);
		//4 请求转发到 pages/manager/book_manager.jsp 页面
		req.getRequestDispatcher("/pages/manager/book_manager.jsp").forward(req,resp);
	}

manager_menu.jsp 中【图书管理】请求地址的修改:
在这里插入图片描述
book_manager.jsp 修改:
在这里插入图片描述

(4)首页、上一页、下一页、末页实现

在book_manager.jsp中

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

(5)分页模块中跳转到指定页数功能实现

<div id="page_nav">
			<%-- 大于首页,才显示 --%>
			<c:if test="${requestScope.page.pageNum>1}">
				<a href="manager/bookServlet?action=page&pageNum=1">首页</a>
				<a href="manager/bookServlet?action=page&pageNum=${requestScope.page.pageNum-1}">上一页</a>
			</c:if>
			<a href="#">3</a>
			【${requestScope.page.pageNum}】
			<a href="#">5</a>
			<%-- 如果已经是最后一页则不显示下一页 --%>
			<c:if test="${requestScope.page.pageNum < requestScope.page.pageTotal}">
				<a href="manager/bookServlet?action=page&pageNum=${requestScope.page.pageNum+1}">下一页</a>
				<a href="manager/bookServlet?action=page&pageNum=${requestScope.page.pageTotal}">末页</a>
			</c:if>
				共${requestScope.page.pageTotal}页,${requestScope.page.pageTotalCount}条记录
				到第<input value="${param.pageNum}" name="pn" id="pn_input"/><input id="searchPageBtn" type="button" value="确定">
				<script type="text/javascript">
					$(function () {
						//
						$("#searchPageBtn").click(function () {
							var pageNum = $("#pn_input").val();

							//javaScript语言中提供了一个Location地址栏对象
							//它有一个属性叫href,它可以获取浏览器地址栏中的地址
							//href属性可读,可写
							location.href = "http://localhost:8088/Book/manager/bookServlet?action=page&pageNum="+pageNum;

							//location.href = "${pageScope.basePath}manager/bookServlet?action=page&pageNum="+pageNum;
						})
					})
				</script>
		</div>

Page 对象中的修改:

public void setPageNo(Integer pageNum) {
	/* 数据边界的有效检查 */
	if (pageNum < 1) {
		pageNum = 1;
	}
	if (pageNum > pageTotal) {
		pageNum = pageTotal;
	}
	this.pageNum = pageNum;
}

BookService 中 page 方法的修改:

 @Override
    public Page<Book> page(int pageNum,int pageSize) {
        Page<Book> page = new Page<Book>();

        //设置每页显示的数量
        page.setPageSize(pageSize);
        //求总记录数
        Integer pageTotalCount = bookDao.queryForPageTotalCount();
        //设置总记录数
        page.setPageTotalCount(pageTotalCount);
        //求总页码
        Integer pageTotal = pageTotalCount / pageSize;
        if (pageTotalCount % pageSize > 0){
            pageTotal+=1;
        }
        //设置总页码
        page.setPageTotal(pageTotal);
        
        //设置当前页码
        page.setPageNum(pageNum);

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

        return page;
    }

(6)分页模块中,页码 1,2,【3】,4,5 的显示,要显示 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:当前页码为最后 3 个,8,9,10
页码范围是:总页码减 4 - 总页码

6,7【8】9,10
6,7,8【9】10
6,7,8,9【10】

小情况 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.pageNum}">
								【${i}】
						</c:if>
						<c:if test="${i != requestScope.page.pageNum}">
							<a href="manager/bookServlet?action=page&pageNum=${i}">${i}</a>
						</c:if>
					</c:forEach>
				</c:when>
				<%--情况 2:总页码大于 5 的情况--%>
				<c:when test="${requestScope.page.pageTotal > 5}">
					<c:choose>
						<%--小情况 1:当前页码为前3个1,2,3的情况,页码范围:1-5。 --%>
						<c:when test="${requestScope.page.pageNum <= 3}">
							<c:forEach begin="1" end="5" var="i">
								<c:if test="${i == requestScope.page.pageNum}">
									【${i}】
								</c:if>
								<c:if test="${i != requestScope.page.pageNum}">
									<a href="manager/bookServlet?action=page&pageNum=${i}">${i}</a>
								</c:if>
							</c:forEach>
						</c:when>

						<%--小情况 2:当前页码为前3个8,9,10的情况,页码范围:总页码减4~总页码。 --%>
						<c:when test="${requestScope.page.pageNum > requestScope.page.pageTotal-3}">
							<c:forEach begin="${requestScope.page.pageTotal-4}" end="${requestScope.page.pageTotal}" var="i">
								<c:if test="${i == requestScope.page.pageNum}">
									【${i}】
								</c:if>
								<c:if test="${i != requestScope.page.pageNum}">
									<a href="manager/bookServlet?action=page&pageNum=${i}">${i}</a>
								</c:if>
							</c:forEach>
						</c:when>

						<%--小情况 3:页码范围:当前页码减2~当前页码加2。 --%>
						<c:otherwise>
							<c:forEach begin="${requestScope.page.pageNum-2}" end="${requestScope.page.pageNum+2}" var="i">
								<c:if test="${i == requestScope.page.pageNum}">
									【${i}】
								</c:if>
								<c:if test="${i != requestScope.page.pageNum}">
									<a href="manager/bookServlet?action=page&pageNum=${i}">${i}</a>
								</c:if>
							</c:forEach>
						</c:otherwise>
					</c:choose>
				</c:when>
			</c:choose>

化简后:

<%--分页条的开始--%>
<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 var="end" value="${requestScope.page.pageTotal}"/>
        </c:when>
        <%--情况2:总页码大于5的情况--%>
        <c:when test="${requestScope.page.pageTotal > 5}">
            <c:choose>
                <%--小情况1:当前页码为前面3个: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个,8,9,10,页码范围是:总页码减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:4,5,6,7,页码范围是:当前页码减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="${ 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 type="text/javascript">

        $(function () {
            // 跳到指定的页码
            $("#searchPageBtn").click(function () {

                var pageNo = $("#pn_input").val();

                <%--var pageTotal = ${requestScope.page.pageTotal};--%>
                <%--alert(pageTotal);--%>

                // javaScript语言中提供了一个location地址栏对象
                // 它有一个属性叫href.它可以获取浏览器地址栏中的地址
                // href属性可读,可写
                location.href = "${pageScope.basePath}${ requestScope.page.url }&pageNo=" + pageNo;

            });
        });

    </script>

</div>
<%--分页条的结束--%>

(7)修改分页后,增加,删除,修改图书信息的回显页面

以修改图书为示例:

1>在修改的请求地址上追加当前页码参数:

在这里插入图片描述

2>在 book_edit.jsp 页面中使用隐藏域记录下 pageNo 参数

在这里插入图片描述

3>在服务器重定向的时候,获取当前页码追加上进行跳转

在这里插入图片描述

2、首页 index.jsp 的跳转

gg

3、分页条的抽取

(1)抽取分页条中请求地址为 url 变量

4.1.1.在 page 对象中添加 url 属性

在这里插入图片描述

4.1.2 在 Servlet 程序的 page 分页方法中设置 url 的分页请求地址

在这里插入图片描述

4.1.3、修改分页条中请求地址为 url 变量输出,并抽取一个单独的 jsp 页面

把manager/bookServlet?action=page换成${ requestScope.page.url }
单独将url拿出来方便改正

<%--分页条的开始--%>
<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 var="end" value="${requestScope.page.pageTotal}"/>
        </c:when>
        <%--情况2:总页码大于5的情况--%>
        <c:when test="${requestScope.page.pageTotal > 5}">
            <c:choose>
                <%--小情况1:当前页码为前面3个: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个,8,9,10,页码范围是:总页码减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:4,5,6,7,页码范围是:当前页码减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="${ 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 type="text/javascript">
        $(function () {
            // 跳到指定的页码
            $("#searchPageBtn").click(function () {
                var pageNo = $("#pn_input").val();
                location.href = "${pageScope.basePath}${ requestScope.page.url }&pageNo=" + pageNo;
            });
        });
    </script>
</div>
<%--分页条的结束--%>

4、首页价格搜索

在这里插入图片描述

5、显示登录用户信息

login_success_menu.jsp中:
在这里插入图片描述

UserServlet中:
在这里插入图片描述
index.jsp中:
在这里插入图片描述

6、登出—注销用户

  1. 销毁Session中用户登录的信息(或者销毁Session)
  2. 重定向到首页或登录界面
   /**
     * 注销
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    protected void loginout(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//        1. 销毁Session中用户登录的信息(或者销毁Session)
        req.getSession().invalidate();
//        2. 重定向到首页或登录界面
        resp.sendRedirect(req.getContextPath());
    }

再替换地址即可

7、表单重复提交 —> 验证码

表单重冥提交有三种常见的情况:
一:提交完表单。服务器使用请求转来进行页面跳转。这个时候,用户按下功能键 F5(刷新),就会发起最后一次的请求。造成表单重复提交问題。
解决方法: 使用重定向来进行跳转

二:用户正常提交服务器,但是由于网络延迟等原因,迟迟未收到服务器的响应,这个时候,用户以为提交失败,
就会着急,然后多点了几次提交探作,也会造成表单重复提交。

三:用户正常提交服务器。服务器也没有延迟,但是提交完成后,用户回退浏览器。重新提交。也会造成表单重复提交。

public class RegistServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //获取用户名
        String username = req.getParameter("username");
        System.out.println("保存到数据库:"+username);
        //1.防止刷新重复到数据库
        //错误:req.getRequestDispatcher("/ok.jsp").forward(req,resp);
        //改正:resp.sendRedirect(req.getContextPath()+"/ok.jsp");

        //2.不停点击提交
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        resp.sendRedirect(req.getContextPath()+"/ok.jsp");

    }
}

所有解决办法:
验证码
在这里插入图片描述

8、谷歌Kaptcha图片验证码的使用

实验步骤:

  1. 导入谷歌验证码jar包------kaptcha-2.3.2.jar
  2. 在web.xml中去配置用于生成验证码的Servlet程序
    <servlet>
        <servlet-name>KaptchaServlet</servlet-name>
        <servlet-class>com.google.code.kaptcha.servlet.KaptchaServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>KaptchaServlet</servlet-name>
        <url-pattern>/kaptchaServlet</url-pattern>
    </servlet-mapping>
  1. 在表单中使用img标签去显示验证码图片并使用它
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    验证码刷新原理:
    在这里插入图片描述

七、项目——阶段六:购物车

在这里插入图片描述

1、购物车模型

(1)购物车商品项

public class CartItem {
    private Integer id;
    private String name;
    private Integer count;
    private BigDecimal price;
    private BigDecimal totalPrice;

    public CartItem() {
    }

    public CartItem(Integer id, String name, Integer count, BigDecimal price, BigDecimal totalPrice) {
        this.id = id;
        this.name = name;
        this.count = count;
        this.price = price;
        this.totalPrice = totalPrice;
    }

    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 Integer getCount() {
        return count;
    }

    public void setCount(Integer count) {
        this.count = count;
    }

    public BigDecimal getPrice() {
        return price;
    }

    public void setPrice(BigDecimal price) {
        this.price = price;
    }

    public BigDecimal getTotalPrice() {
        return totalPrice;
    }

    public void setTotalPrice(BigDecimal totalPrice) {
        this.totalPrice = totalPrice;
    }

    @Override
    public String toString() {
        return "CartItem{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", count=" + count +
                ", price=" + price +
                ", totalPrice=" + totalPrice +
                '}';
    }
}

(1)购物车对象

/**
 * 购物车对象
 * @Classname Cart
 * @Description TODO
 * @Date 2022/2/6 15:15
 * @Created by cencen
 */
public class Cart {
//    private Integer totalCount;
//    private BigDecimal totalCount;
//    private BigDecimal totalPrice;
    //key是商品编号
    //value是商品信息
    private Map<Integer,CartItem> items = new LinkedHashMap<Integer,CartItem>();


    /**
     * 添加商品项
     * @Param cartItem
     */
    public void addItem(CartItem cartItem){
        //先查看购物车中是否已经添加过此商品,如已添加,则数量累加,总金额更新,如果没有添加,则直接放到集合中
        CartItem item = items.get(cartItem.getId());
        if (item == null){
            //之前没有添加过此商品
            items.put(cartItem.getId(),cartItem);
        }else {
            //已经添加过的情况
            item.setCount(item.getCount()+1);//数量累加
            item.setTotalPrice(item.getPrice().multiply(new BigDecimal(item.getCount())));//更新总金额
        }
    }
    /**
     * 删除商品项
     */
    public void deleteItem(Integer id){
        items.remove(id);
    }
    /**
     * 清空购物车
     */
    public void clear(){
        items.clear();
    }
    /**
     * 修改商品数量
     */
    public void updateCount(Integer id,Integer count){
        CartItem cartItem = items.get(id);
        if (cartItem != null){
            cartItem.setCount(count);//修改商品数量
            cartItem.setTotalPrice(cartItem.getPrice().multiply(new BigDecimal(cartItem.getCount())));//更新总金额

        }
    }

    //获取该商品项总数量
    public Integer getTotalCount() {
        Integer totalCount = 0;
        for (Map.Entry<Integer,CartItem>entry:items.entrySet()){
            totalCount += entry.getValue().getCount();
        }
        return totalCount;
    }

    //获取该商品项总价格
    public BigDecimal getTotalPrice() {
        BigDecimal totalPrice = new BigDecimal(0);
        for (Map.Entry<Integer,CartItem>entry:items.entrySet()){
            totalPrice = totalPrice.add(entry.getValue().getTotalPrice());
        }
        return totalPrice;
    }

    public Map<Integer,CartItem> getItems() {
        return items;
    }

    public void setItems(Map<Integer,CartItem> items) {
        this.items = items;
    }

    @Override
    public String toString() {
        return "Cart{" +
                "totalCount=" + getTotalCount() +
                ", totalPrice=" + getTotalPrice() +
                ", items=" + items +
                '}';
    }
}

2、购物车功能的实现

(1)购物车的展示

在这里插入图片描述

(2)添加商品到购物车功能

在这里插入图片描述
在CartServlet中

	/**
     * 加入购物车
     */
    @Override
    protected void addItem(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //获取请求的参数 商品编号
        int id = WebUtils.parseInt(req.getParameter("id"),0);
        //调用bookServlet.qurryBookById(id):Book得到图书的信息
        Book book = bookService.queryBookById(id);
        //把图书信息,转换成CartItem商品项
        CartItem cartItem = new CartItem(book.getId(),book.getName(),1,book.getPrice(),book.getPrice());
        //调用Cart.addItem(CartItem);添加商品项
        Cart cart = (Cart) req.getSession().getAttribute("cart");
        if (cart == null){
            cart = new Cart();
            req.getSession().setAttribute("cart",cart);
        }
        cart.addItem(cartItem);
        //重定向回原来商品所在的地址页面,因为在http中有一个请求头Referer,它可以把请求发起时,浏览器地址栏中的地址发给服务器
        resp.sendRedirect(req.getHeader("Referer"));
    }

index.jsp 页面 js 的代码:
index.jsp 页面 js 的代码:
在这里插入图片描述

(3)清空购物车功能

CartServlet 程序

    /**
     * 清空购物车
     */
    protected void clearcart(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //获取购物车对象
        Cart cart = (Cart) req.getSession().getAttribute("cart");
        //清空购物车
        cart.clear();
        //重定向回购物车展示页面
        resp.sendRedirect(req.getHeader("Referer"));
    }

cart.jsp 页面的内容
给清空购物车添加请求地址,和添加 id 属性:
在这里插入图片描述
清空的确认提示操作
在这里插入图片描述

(4)删除购物车商品数量功能

CartServlet 程序:

	/**
     * 删除商品
     */
    protected void deleteItem(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //获取商品编号
        int id = WebUtils.parseInt(req.getParameter("id"), 0);
        //获取购物车对象
        Cart cart = (Cart) req.getSession().getAttribute("cart");
        if (cart != null) {
            //删除购物车商品项
            cart.deleteItem(id);
            //重定向回原来界面
            resp.sendRedirect(req.getHeader("Referer"));
        }
    }

购物车/pages/cart/cart.jsp 页面的代码:
删除的请求地址
在这里插入图片描述

删除的确认提示操作:
在这里插入图片描述

(5)修改购物车商品数量

CartServlet 程序

 	/**
     * 修改商品数量
     */
    protected void updateCount(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //获取请求的参数,商品编号,商品数量
        int id = WebUtils.parseInt(req.getParameter("id"),0);
        int count = WebUtils.parseInt(req.getParameter("count"),0);
        //获取Cart购物车对象
        Cart cart = (Cart) req.getSession().getAttribute("cart");

        if (cart != null){
            //修改商品数量
            cart.updateCount(id,count);
        }
        //重定向回购物车展示页面
        resp.sendRedirect(req.getHeader("Referer"));
    }

修改 pages/cart/cart.jsp 购物车页面:
在这里插入图片描述
修改商品数量 js 代码:
在这里插入图片描述

(6)把内容显示到首页,购物车数据回显

在这里插入图片描述
在添加商品到购物车的时候,保存最后一个添加的商品名称:
在这里插入图片描述
在 pages/client/index.jsp 页面中输出购物车信息:
在这里插入图片描述

八、项目——阶段七:订单

在这里插入图片描述

1、订单模块的实现

(1)创建订单模块的数据库表

use book;
create table t_order(
	`order_id` varchar(50) primary key,
	`create_time` datetime,
	`price` decimal(11,2),
	`status` int,
	`user_id` int,
	foreign key(`user_id`) references book_user(`id`)
);
create table t_order_item(
	`id` int primary key auto_increment,
	`name` varchar(100),
	`count` int,
	`price` decimal(11,2),
	`total_price` decimal(11,2),
	`order_id` varchar(50),
	foreign key(`order_id`) references t_order(`order_id`)
);

(2)创建订单模块的数据模型

/**
* 订单
*/
public class Order {
	private String orderId;
	private Date createTime;
	private BigDecimal price;
	// 0 未发货,1 已发货,2 表示已签收
	private Integer status = 0;
	private Integer userId;
	
/**
* 订单项
*/
public class OrderItem {
	private Integer id;
	private String name;
	private Integer count;
	private BigDecimal price;
	private BigDecimal totalPrice;
	private String orderId;

(3)编写订单模块的 Dao 程序和测试

OrderDao 接口

public interface OrderDao {
	public int saveOrder(Order order);
}

OrderDao 实现

public class OrderDaoImpl extends BaseDao implements OrderDao {
    @Override
    public int saveOrder(Order order) {
        String sql = "insert into t_order(`order_id`,`create_time`,`price`,`status`,`user_id`) values(?,?,?,?,?)";
        return update(sql,order.getOrderId(),order.getCreateTime(),order.getPrice(),order.getStatus(),order.getUserId());
    }
}

OrderItemDao 接口

public interface OrderItemDao {
	public int saveOrderItem(OrderItem orderItem);
}

OrderItemDao 实现

public class OrderItemDaoImpl extends BaseDao implements OrderItemDao {
    @Override
    public int saveOrderItem(OrderItem orderItem) {
        String sql = "insert into t_order_item(`name`,`count`,`price`,`total_price`,`order_id`) values(?,?,?,?,?)";
        return update(sql,orderItem.getName(),orderItem.getCount(),orderItem.getPrice(),orderItem.getTotalPrice(),orderItem.getOrderId());
    }
}

测试

public class OrderDaoTest {
	@Test
	public void saveOrder() {
		OrderDao orderDao = new OrderDaoImpl();
		orderDao.saveOrder(new Order("1234567891",new Date(),new BigDecimal(100),0, 1));
	}
}



public class OrderItemDaoTest {
	@Test
	public void saveOrderItem() {
		OrderItemDao orderItemDao = new OrderItemDaoImpl();
		orderItemDao.saveOrderItem(new OrderItem(null,"java 从入门到精通", 1,newBigDecimal(100),newBigDecimal(100),"1234567890"));
		orderItemDao.saveOrderItem(new OrderItem(null,"javaScript 从入门到精通", 2,newBigDecimal(100),newBigDecimal(200),"1234567890"));
		orderItemDao.saveOrderItem(new OrderItem(null,"Netty 入门", 1,new BigDecimal(100),newBigDecimal(100),"1234567890"));
	}
}

(4)编写订单模块的 Service 和测试

OrderService 接口

public interface OrderService {
	public String createOrder(Cart cart,Integer userId);
}

OrderService 实现类

public class OrderServiceImpl implements OrderService {
    private OrderDao orderDao = new OrderDaoImpl();
    private OrderItemDao orderItemDao = new OrderItemDaoImpl();
    @Override
    public String createOrder(Cart cart, Integer userId) {
        //订单号===唯一性
        String orderId = System.currentTimeMillis()+""+userId;
        //创建一个订单对象
        Order order = new Order(orderId,new Date(),cart.getTotalPrice(),0,userId);
        //保存订单
        orderDao.saveOrder(order);
        //遍历购物车中每一个商品项转换成为订单项保存到数据库
        for (Map.Entry<Integer, CartItem>entry:cart.getItems().entrySet()){
            //获取每一个购物车中的商品项
            CartItem cartItem = entry.getValue();
            //转换为每一个订单项
            OrderItem orderItem = new OrderItem(null,cartItem.getName(),cartItem.getCount(),cartItem.getPrice(),cartItem.getTotalPrice(),orderId);
            //保存每一个订单项到数据库
            orderItemDao.saveOrderItem(orderItem);
        }
        //清空购物车
        cart.clear();
        return orderId;
    }
}

测试

public class OrderServiceTest {
    @Test
    public void createOrder() {

        Cart cart = new Cart();

        cart.addItem(new CartItem(1, "java从入门到精通", 1, new BigDecimal(1000),new BigDecimal(1000)));
        cart.addItem(new CartItem(1, "java从入门到精通", 1, new BigDecimal(1000),new BigDecimal(1000)));
        cart.addItem(new CartItem(2, "数据结构与算法", 1, new BigDecimal(100),new BigDecimal(100)));

        OrderService orderService = new OrderServiceImpl();

        System.out.println( "订单号是:" + orderService.createOrder(cart, 1) );
    }
}

(5)编写订单模块的 web 层和页面联调

修改 OrderService 程序:

public class OrderServiceImpl implements OrderService {
    private OrderDao orderDao = new OrderDaoImpl();
    private OrderItemDao orderItemDao = new OrderItemDaoImpl();
    private BookDao bookDao = new BookDaoImpl();

    @Override
    public String createOrder(Cart cart, Integer userId) {
        //订单号===唯一性
        String orderId = System.currentTimeMillis()+""+userId;
        //创建一个订单对象
        Order order = new Order(orderId,new Date(),cart.getTotalPrice(),0,userId);
        //保存订单
        orderDao.saveOrder(order);
        //遍历购物车中每一个商品项转换成为订单项保存到数据库
        for (Map.Entry<Integer, CartItem>entry:cart.getItems().entrySet()){
            //获取每一个购物车中的商品项
            CartItem cartItem = entry.getValue();
            //转换为每一个订单项
            OrderItem orderItem = new OrderItem(null,cartItem.getName(),cartItem.getCount(),cartItem.getPrice(),cartItem.getTotalPrice(),orderId);
            //保存每一个订单项到数据库
            orderItemDao.saveOrderItem(orderItem);

            //更新库存和销量
            Book book = bookDao.queryBookById(cartItem.getId());
            book.setSales(book.getSales()+cartItem.getCount());
            book.setStock(book.getStock()-cartItem.getCount());
            bookDao.updateBook(book);
        }
        //清空购物车
        cart.clear();
        return orderId;
    }
}

OrderServlet 程序:

public class OrderServlet extends BaseServlet{

    OrderService orderService = new OrderServiceImpl();
    /*
    * 生成订单
    * */
    protected void createOrder(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //获取Cart购物车对象
        Cart cart = (Cart) req.getSession().getAttribute("cart");
        //获取Userid
        User loginUser = (User) req.getSession().getAttribute("user");
        //如果没登陆,跳转到登录页面
        if (loginUser == null){
            req.getRequestDispatcher("/pages/user/login.jsp").forward(req,resp);
            return;

        }

        Integer userId = loginUser.getId();
        //调用orderService.createOrder(Cart,Userid);生成订单
        String orderId = orderService.createOrder(cart,userId);

        //以下方法当用户刷新时会造成重复提交表单,因此使用重定向方法,但重定向方法只支持Session域
        //req.setAttribute("orderId",orderId);
        //请求转发到/pages/cart/checkout.jsp
        //req.getRequestDispatcher("/pages/cart/checkout.jsp").forward(req,resp);

        req.getSession().setAttribute("orderId",orderId);
        resp.sendRedirect(req.getContextPath()+"/pages/cart/checkout.jsp");
    }
}

修改 pages/cart/cart.jsp 页面,结账的请求地址:
在这里插入图片描述

修改 pages/cart/checkout.jsp 页面,输出订单号:
在这里插入图片描述

九、项目——阶段八:拦截请求Filter

1、使用 Filter 过滤器拦截/pages/manager/所有内容,实现权限检查

即登入后台前需要认证

public class ManagerFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
        Object user = httpServletRequest.getSession().getAttribute("user");
        if (user == null){
            httpServletRequest.getRequestDispatcher("/pages/user/login.jsp").forward(servletRequest,servletResponse);
        }else {
            filterChain.doFilter(servletRequest,servletResponse);
        }
    }
}

web.xml配置

    <filter>
        <filter-name>ManagerFilter</filter-name>
        <filter-class>com.cen.filter.ManagerFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>ManagerFilter</filter-name>
        <url-pattern>/pages/manager/*</url-pattern>
        <url-pattern>/manager/bookServlet</url-pattern>

2、ThreadLocal 的使用

  • ThreadLocal 的作用,它可以解决多线程的数据安全问题。
  • ThreadLocal 它可以给当前线程关联一个数据(可以是普通变量,可以是对象,也可以是数组,集合)

ThreadLocal 的特点:

  1. ThreadLocal 可以为当前线程关联一个数据。(它可以像 Map 一样存取数据,key 为当前线程)
  2. 每一个 ThreadLocal 对象,只能为当前线程关联一个数据,如果要为当前线程关联多个数据,就需要使用多个ThreadLocal 对象实例。
  3. 每个 ThreadLocal 对象实例定义的时候,一般都是 static 类型
  4. ThreadLocal 中保存数据,在线程销毁后。会由 JVM 虚拟自动释放。

测试类

public class OrderService {
	public void createOrder(){
		String name = Thread.currentThread().getName();
		System.out.println("OrderService 当前线程[" + name + "]中保存的数据是:" +ThreadLocalTest.threadLocal.get());
		new OrderDao().saveOrder();
	}
}


public class OrderDao {
	public void saveOrder(){
		String name = Thread.currentThread().getName();
		System.out.println("OrderDao 当前线程[" + name + "]中保存的数据是:" +ThreadLocalTest.threadLocal.get());
	}
}

public class ThreadLocalTest {
	// public static Map<String,Object> data = new Hashtable<String,Object>();
	public static ThreadLocal<Object> threadLocal = new ThreadLocal<Object>();
	private static Random random = new Random();

	public static class Task implements Runnable {
		@Override
		public void run() {
		// 在 Run 方法中,随机生成一个变量(线程要关联的数据),然后以当前线程名为 key 保存到 map 中
			Integer i = random.nextInt(1000);
			// 获取当前线程名
			String name = Thread.currentThread().getName();
			System.out.println("线程["+name+"]生成的随机数是:" + i);
			// data.put(name,i);
			threadLocal.set(i);
			try {
				Thread.sleep(3000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			new OrderService().createOrder();
	// 在 Run 方法结束之前,以当前线程名获取出数据并打印。查看是否可以取出操作
	// Object o = data.get(name);
			Object o = threadLocal.get();
			System.out.println("在线程["+name+"]快结束时取出关联的数据是:" + o);
		}
	}
	public static void main(String[] args) {
		for (int i = 0; i < 3; i++){
			new Thread(new Task()).start();
		}
	}
}

3、使用 Filter 和 ThreadLocal 组合管理事务

3.1、使用 ThreadLocal 来确保所有 dao 操作都在同一个 Connection 连接对象中完成

生成订单和订单项是需要同时完成的,因此使用到事务
在这里插入图片描述
JDBCUtils 工具类的修改:

public class JDBCUtils {

    private static DruidDataSource dataSource;
    private static ThreadLocal<Connection> conns = new ThreadLocal<Connection>();
    static {
        try {
            Properties properties = new Properties();
            //读取 jdbc.properties属性配置文件
            InputStream inputStream = JDBCUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");
            //从流中加载数据
            properties.load(inputStream);
            dataSource = (DruidDataSource) DruidDataSourceFactory.createDataSource(properties);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //获取数据库连接池中的连接
    //如果返回null就是获取连接失败,有值才成功
    public static Connection getconnection(){
        Connection conn = conns.get();
        //如果不为空,则可确保每次都使用同一个连接
        if (conn == null){
            try {
                conn = dataSource.getConnection();//从数据库连接池中获取连接
                conns.set(conn);//保存在ThreadLocal对象中,供后面的JDBC操作使用
                conn.setAutoCommit(false);//设置为手动管理事务
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        return conn;
    }

    /*
    * 提交事务,并关闭释放连接
    * */
    public static void commitAndClose(){
        Connection connection = conns.get();
        if (connection != null){//如果不等于null,说明之前使用过连接,操作过数据库
            try {
                connection.commit();//提交事务
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }finally {
                try {
                    connection.close();//关闭连接,释放资源
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
        }
        //  一定要执行remove操作,否则会出错(因为Tomcat服务器底层使用了线程池技术)
        conns.remove();
    }

    /*
    * 回滚事务,并关闭释放连接
    * */
    public static void rollbackAndClose(){
        Connection connection = conns.get();
        if (connection != null){//如果不等于null,说明之前使用过连接,操作过数据库
            try {
                connection.rollback();//回滚事务
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }finally {
                try {
                    connection.close();//关闭连接,释放资源
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
        }
        //  一定要执行remove操作,否则会出错(因为Tomcat服务器底层使用了线程池技术)
        conns.remove();
    }

//    //关闭连接,放回数据库连接池
//    public static void close(Connection conn){
//        if (conn!=null){
//            try {
//                conn.close();
//            } catch (SQLException throwables) {
//                throwables.printStackTrace();
//            }
//        }
//    }
}

修改 BaseDao
主要是把close()方法改成
在这里插入图片描述

public class BaseDao {
    //使用DbUtils操作数据库
    private QueryRunner queryRunner = new QueryRunner();

    /**
     * update() 方法用来执行:Insert\Update\Delete语句
     *
     * @return 如果返回-1,说明执行失败<br/>返回其他表示影响的行数
     */
    public int update(String sql,Object...args){
        Connection connection = JDBCUtils.getconnection();
        try {
            return queryRunner.update(connection,sql,args);
        }  catch (SQLException throwables) {
            throwables.printStackTrace();
            throw new RuntimeException(throwables);
        }
    }

    /**
     * @Description 查询返回一个JavaBean的sql语句
     * @Param  type 返回的对象类型
     * @Param  sql 执行的sql语句
     * @Param  args sql对应的参数值
     * @Param  <T>  返回的类型的泛型
     * @return
     */
    public <T> T queryForOne(Class<T> type,String sql,Object ... args){
        Connection con = JDBCUtils.getconnection();
        try {
            return queryRunner.query(con,sql,new BeanHandler<T>(type),args);
        }  catch (SQLException throwables) {
            throwables.printStackTrace();
            throw new RuntimeException(throwables);
        }
    }

    /**
     * @Description 查询返回多个JavaBean的sql语句
     * @Param  type 返回的对象类型
     * @Param  sql 执行的sql语句
     * @Param  args sql对应的参数值
     * @Param  <T>  返回的类型的泛型
     * @return
     */
    public <T>List<T> queryForList(Class<T> type,String sql,Object ... args){
        Connection con = JDBCUtils.getconnection();
        try {
            return queryRunner.query(con,sql,new BeanListHandler<T>(type),args);
        }  catch (SQLException throwables) {
            throwables.printStackTrace();
            throw new RuntimeException(throwables);
        }
    }

    /**
     * 执行返回一行一列的sql语句
     * @param sql   执行的sql语句
     * @param args  sql对应的参数值
     * @return
     */
    public Object queryForSingleValue(String sql,Object...args){
        Connection conn = JDBCUtils.getconnection();
        try {
            return queryRunner.query(conn,sql,new ScalarHandler(),args);

        } catch (SQLException throwables) {
            throwables.printStackTrace();
            throw new RuntimeException(throwables);
        }
    }
}

OrderService中加上
在这里插入图片描述
即在OrderServiceImpl类中的createOrder方法执行时,遇到错误跳出在OrderService类中捕获并处理,处理中加上回滚事务,则可达到事务操作的目的,使订单和订单项共存亡。

3.2、使用 Filter 过滤器统一给所有的 Service 方法都加上 try-catch。来进行实现的管理。

因为每一个Service方法都有用到事务的情况
在这里插入图片描述

在这里插入图片描述
中的异常一定要抛出,不能捕获,这样才会出到该类中捕获错误从而回滚事务。

3.3、将所有异常都统一交给 Tomcat,让 Tomcat 展示友好的错误信息页面。

在 web.xml 中我们可以通过错误页面配置来进行管理。
在这里插入图片描述
错误页面自行配置
error404.jsp
error500.jsp

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值