尚硅谷书城项目:自己整理的笔记以及全部实现过程,原理。

链接: 点击获取资源

提取码: ih2c 

再次感谢尚硅谷,我爱尚硅谷!!!!


 

目录

第一阶段:对注册页面的信息进行验证:

第二阶段:用户管理模块

        2.1、创建数据库表

        2.2、编写User实体类

        2.3、工具类——JdbcUtils工具类:用于连接数据库。

        2.4、提供BaseDao:操作数据库的方法【可继承BaseDao实现数据库操作】

       2.5、提供DAO接口以及实现类【针对于某一张表的操作】

        2.6、编写Service层和实现

第三阶段:jsp页面动态化

1、将所有的 html 页面 替换成 jsp 页面。并增加头部标签。

2、使用静态包含替换掉所有的 公共部分。

3、在页面上回显错误信息。

4、优化Servlet 

5、请求参数的封装和BeanUtils工具类的使用

第四阶段:图书管理模块

 4.1、构建数据库

4.2、创建 JavaBean 实体类

4.3、编写图书模块的Dao接口,以及实现类

4.4、编写图书模块的 service 接口 以及 实现类

4.5、编写图书模块的Web层

第五阶段:图书管理模块的分页管理

5.1、分页初步查询

5.2、上一页,下一页,首页,末页 的实现

5.3、跳到指定页码

 5.4、页码边界检查

5.5、页码显示

5.6、分页对删除、修改、增加的影响

5.7、前台的分页管理

5.8、分页条的抽取

5.9、价格区间搜索 

第六阶段:登录用户信息显示

6.1、登录后显示 欢迎信息 

 6.2、注销登录

6.3、表单重复提交问题 

6.4、将验证码增加到书城项目中

第七阶段:购物车模块 

7.1、购物车模型创建

7.2、 购物车功能的实现

7.3、增加购物车功能的实现

 7.4、展示购物车

7.5、删除商品

7.6、清空购物车

7.7、修改购物车商品数量

7.8、首页购物车信息回显

第八阶段:订单模块

8.1、订单模型创建分析

 8.2、订单与订单项数据库创建

8.3、订单项与订单的实体类创建

8.4、生成订单功能的实现

第九阶段:使用 Filter 和 ThreadLocal 完善系统

         9.1、使用Filter拦截器:

         9.2、使用 ThreadLocal:

         9.3、使用 FIlter 统一给所有的 service 方法加上 try。。catch

         9.4、使用 Tomcat 统一管理异常,展示友好的错误页面

第十阶段:使用 Ajax 验证用户名是否可用 

使用 Ajax 修改增加购物车


一、第一阶段:对注册页面的信息进行验证:

 要求:

验证用户名:必须由字母,数字下划线组成,并且长度为 5 12

验证密码:必须由字母,数字下划线组成,并且长度为 5 12

验证确认密码:和密码相同

邮箱验证:xxxxx@xxx.com

验证码:现在只需要验证用户已输入。因为还没讲到服务器。验证码生成
 <script type="text/javascript">
        // 页面加载完成之后
        $(function(){

            // 给注册按钮添加事件
            $("#sub_btn").click(function(){

                // 获取用户名
                var usernameValue = $("#username").val();
                // 验证用户名是否合法,规则如下:必须由字母,数字,下划线组成,并且长度为5到15位。
                var usernameReg = /^\w{5,15}$/;
                // 验证用户信息
                if (!usernameReg.test(usernameValue)) {
                    // 提示用户
                    $("span.errorMsg").text("用户名不合法!");
                    return false;
                }

                // 获取密码
                var passwordValue = $("#password").val();
                // 验证密码是否合法,规则如下:必须由字母,数字,下划线组成,并且长度为5到15位。
                var passwordReg = /^\w{5,15}$/;
                // 验证用户信息
                if (!passwordReg.test(passwordValue)) {
                    // 提示用户
                    $("span.errorMsg").text("密码不合法!");
                    return false;
                }

                // 获取确认密码
                var repwdValue = $("#repwd").val();
                // 验证确认密码和密码一致
                if (passwordValue != repwdValue) {
                    // 提示用户
                    $("span.errorMsg").text("确认密码和密码不一致!");
                    return false;
                }

                // 获取用户名
                var emailValue = $("#email").val();
                // 验证邮件输入是否合法。
                var emailReg = /^([a-z0-9_\.-]+)@([\da-z\.-]+)\.([a-z\.]{2,6})$/;

                if (!emailReg.test(emailValue)) {
                    // 提示用户
                    $("span.errorMsg").text("邮件输入不合法!");
                    return false;
                }


                // 获取验证码信息
                var codeValue = $("#code").val();
                // 验证验证码不为空!
                if (codeValue == "") {
                    $("span.errorMsg").text("验证码不能为空!");
                    return false;
                }
                /*当信息正确的时候去掉信息*/
                $("span.errorMsg").text("");
                return true;
            });
        });
    </script>

二、第二阶段:用户管理模块

项目的三层架构:

Web 层 无法直接访问 Dao 层,需要借助 Service 层。

无论多复杂的项目都基本符合 javaEE 三层架构:

Dao层: 负责与数据库进行交互,一般都是有一个 BaseDao,为不同的模块提供 操作 数据库的方法【增删改查】,每一个模块或者一个业务都需要继承 BaseDao,并且 需要一个 dao接口和实现类。

Service 层:负责处理各种业务。一般是一个 service 接口 和 实现类。

Web : 服务与页面联调

 项目环境搭建:

        配置 Tomcat 服务器,将静态页面都放到web目录下。

包名设置:

web层                项目名.web/servlet/controller
service层           项目名.service                                          Service 接口包:实现业务功能
                          项目名.service.impl                                  Service接口实现类:通过 dao 中的方法,实现业务功能。
dao持久层          项目名.dao                                              Dao接口包:针对于某一张表的操作 
                           项目名.dao.impl                                      Dao接口实现类:实现接口的操作,一般需要继承BaseDao,通过调用里面的方法,实现接口中的方法,
实体bean对象     项目名.pojo/entity/domain/bean      JavaBean类
测试包               项目名.test/junit                            用于测试【需要导入harmcrest和Junit的jar包】

工具类              项目名.utils

       

DAO持久层:

        2.1、创建数据库表



DROP DATABASE IF EXISTS book;
CREATE DATABASE book;
USE book;


CREATE TABLE `t_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(20) NOT NULL,
  `password` varchar(32) NOT NULL,
  `email` varchar(200) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

insert  into `t_user`(`id`,`username`,`password`,`email`) values (1,'admin','admin','admin@atguigu.com');

        2.2、编写User实体类

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

    public User() {
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", 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;
    }
}

        2.3、工具类——JdbcUtils工具类:用于连接数据库。

(1)将连接数据库的 jar 包导入 WEB—INF的lib目录下

(2)事先准备好配置文件【德鲁伊连接池】:用于保存 url ,user,password,driver等信息。

#驱动
driver=com.mysql.jdbc.Driver
#url
url=jdbc:mysql://127.0.0.1:3306/test
#账户
user=root
#密码
password=root

(3)编写JdbcUtils工具类【提前将德鲁伊连接池 jar 包导入lib目录下】

public class JdbcUtils {

    private static DruidDataSource source;

    static {
        try {
            /*加载流*/
            Properties pros = new Properties();
            InputStream is = JdbcUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");
            pros.load(is);
            source = (DruidDataSource) DruidDataSourceFactory.createDataSource(pros);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    public static Connection getConnection()  {
        Connection conn = null;
        try {
            conn = source.getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return conn;
    }

    /*释放资源*/
    public static void closeConnection(Connection conn) {
        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

        2.4、提供BaseDao:操作数据库的方法【可继承BaseDao实现数据库操作】

/*使用DBUtils操作数据库增删改查*/
public class BaseDao {
    private static QueryRunner runner = new QueryRunner();

    /*通用的增删改*/
    public static int update(Connection conn, String sql, Object... args) {
        int count = -1;
        try {
            /*影响数据库的条数*/
            count = runner.update(conn, sql, args);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return count;
    }

    /*查询:只返回一条数据*/
    public static <T> T queryForOne(Connection conn, Class<T> tClass, String sql, Object... args) {
        BeanHandler<T> beanHandler = new BeanHandler<T>(tClass);
        T t = null;
        try {
            t = runner.query(conn, sql, beanHandler, args);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return t;
    }

    /*返回多条记录*/
    public static <T> List<T> queryForList(Connection conn, Class<T> tClass, String sql, Object... args) {
        BeanListHandler<T> beanList = new BeanListHandler<>(tClass);
        List<T> list = new ArrayList<>();
        try {
            list = runner.query(conn, sql, beanList, args);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return list;
    }

    /*查询特殊值*/
    public static Object queryForSingle(Connection conn, String sql, Object... args) {
        ScalarHandler<Object> handler = new ScalarHandler<>();
        Object data = null;
        try {
            data = runner.query(conn, sql, handler);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return data;
    }
}

       2.5、提供DAO接口以及实现类【针对于某一张表的操作】

public interface UserDao {
    /**
     * 返回 1 表示注册成功。
     * @param user 用户注册的信息
     * @return 影响数据库条数
     */
    int saveInfo(User user);
    /**
     * 注册使用
     * @param userName 根据用户名在数据库中进行查询
     * @return 返回 user 信息表示用户已存在,返回 null 表示用户名可用
     */
    User queryByUserName(String userName);

    /**
     * 登录使用
     * @param userName 用户名
     * @param password 密码
     * @return 返回 user 信息表示用户名密码正确-登录成功,返回 null 表示登陆失败
     */
    User queryByUserNameAndPassword(String userName,String password);
}
public class UserImplDao extends BaseDao implements UserDao {
    @Override
    public int saveInfo(User user) {
        Connection conn = JdbcUtils.getConnection();
        String sql = "insert into t_user(username,password,email) values(?,?,?)";
        return BaseDao.update(conn, sql, user.getUsername(), user.getPassword(), user.getEmail());

    }

    @Override
    public User queryByUserName(String userName) {
        Connection conn = JdbcUtils.getConnection();
        String sql = "select `id`, `userName` ,`password`, `email` from t_user where userName=?";
        return BaseDao.queryForOne(conn, User.class, sql, userName);
    }

    @Override
    public User queryByUserNameAndPassword(String userName, String password) {
        Connection conn = JdbcUtils.getConnection();
        String sql = "select `id`, `userName` ,`password`, `email` from t_user where userName=? and password=?";
        return BaseDao.queryForOne(conn, User.class, sql, userName, password);
    }
}

        2.6、编写Service层和实现

接口:

public interface UserService {
    /**
     * 注册信息
     * @param user 注册的信息
     */
    void saveInfo(User user);

    /**
     * 判断用户名是否重复
     * @param userName 用户名
     * @return
     */
    boolean queryByUserName(String userName);

    /**
     * 登录
     * @param userName 用户名
     * @param password 密码
     * @return
     */
    User login(String userName,String password);
}

 接口实现类:

public class UserServiceImpl implements UserService {

    private UserImplDao userDao = new UserImplDao();

    /**
     *
     * @param user 注册的信息
     */
    @Override
    public void saveInfo(User user) {
        userDao.saveInfo(user);
    }

    /**
     *
     * @param userName 用户名
     * @return true:用户名重复 false:用户名可用
     */
    @Override
    public boolean queryByUserName(String userName) {
        User user = userDao.queryByUserName(userName);
        if (user != null){
            //不等于 null,说明查到了用户名,则用户名重复不可用。
            return true ;
        }
        //相反,没有查找说明用户名可用
        return false;
    }

    /**
     *
     * @param userName 用户名
     * @param password 密码
     * @return 返回 User 信息
     */
    @Override
    public User login(String userName, String password) {
       return  userDao.queryByUserNameAndPassword(userName, password);
    }
}

Web 层:

注册功能的流程图:

 页面路径问题:

web: base标签 + 相对路径

    <!--永远固定相对路径跳转的结果-->
    <base href="http://localhost:8080/book/">

更改 register.html 和 register_success.html 中的所有路径。

实现注册功能:创建 RegisterServlet【注册】 业务类 ,部署Tomcat 并且配置 web.xml 文件。 

注意:不要忘记引入 Servlet.jar 包

public class RegisterServlet extends HttpServlet {
    //Web 层不能直接访问Dao层,通过 Service 层访问。
   private UserServiceImpl userService = new UserServiceImpl();
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        // 1、获取参数
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        String email = request.getParameter("email");
        String code = request.getParameter("code");
        // 2、判断验证码是否正确:还没有学动态获取验证码,所以这里就写死了
        if ("6n6np".equalsIgnoreCase(code)){
            //     验证码正确
            //         用户名是否重复
            if(userService.existsUsername(username)){
                //             用户名重复:返回注册页面
                System.out.println("用户名["+username+"]重复");
                request.getRequestDispatcher("/pages/user/regist.html").forward(request,response);

            }else{
                //             用户名不重复:跳转到注册成功界面,并向数据库中保存user信息
                userService.registUser(new User(null,username,password,email));
                request.getRequestDispatcher("/pages/user/regist_success.html").forward(request,response);

            }

        }else{
            //      验证码错误:返回注册页面
            System.out.println("验证码错误");
            request.getRequestDispatcher("/pages/user/regist.html").forward(request,response);
        }
    }
}

实现登录功能:

public class LoginServlet extends HttpServlet {
    private UserService userService = new UserServiceImpl();
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        /*获取参数*/
        String username = request.getParameter("username");
        String password = request.getParameter("password");

        User user = userService.login(new User(null, username, password, null));
        if (user != null){
            /*用户名密码正确,跳转到登陆成功页面*/
            System.out.println("登陆成功");
            request.getRequestDispatcher("/pages/user/login_success.html").forward(request,response);
        }else{
            /*不正确,返回登录界面打印错误信息*/
            System.out.println("用户名或密码错误");
            request.getRequestDispatcher("/pages/user/login.html").forward(request,response);
        }
    }
}

三、第三阶段:jsp页面动态化

1、将所有的 html 页面 替换成 jsp 页面。并增加头部标签。

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

        更改之后检查页面的路径是否正确。加上 base 标签后,所有的路径都以这个路径为起始路径

<base href="http://localhost:8080/book/">

2、使用静态包含替换掉所有的 公共部分。

 登录成功的菜单:

替换:

			<%--使用静态包含,替换登陆成功菜单--%>
			<%@include file="/pages/common/login_succss_menu.jsp"%>

 页脚:

替换:

		<%--使用静态包含替换页脚--%>
		<%@include file="/pages/common/footer.jsp"%>

jQuery,css,Bas 标签:

 替换:

	<%--使用静态包含替换所有的 jQuery,css,Base 标签--%>
	<%@include file="/pages/common/head.jsp"%>

图书管理菜单:

 替换:

			<%--使用静态包含替换图书管理菜单--%>
			<%@include file="/pages/common/manager_menu.jsp"%>

 动态获取 base 路径:

<%--动态获取base标签中的路径--%>
<%
    String basePath = request.getScheme() // http协议
            +"://"
            +request.getServerName() //服务器ip
            +":"
            +request.getServerPort() // 端口号
            +request.getContextPath() // 根路径
            +"/";
%>

<!--永远固定相对路径跳转的结果-->
<base href="<%=basePath%>">

3、在页面上回显错误信息。

登录时 输错用户名或者密码,要求显示在页面上,注册时:用户名存在或者验证码错误显示在 页面上,并且回显用户名和邮箱。

3.1 、登录时的错误回显,并回显用户名。

思路:

在 LoginServlet 程序端接受浏览器发送的请求信息【用户名和密码】,与数据库进行判断。

信息错误时,将 回显的信息保存到 request 域中。转发到 jsp 页面,使用 EL 表达式取出数据,回显到 jsp 页面上。

回显得数据存在 request 域中:

            //将错误信息保存到 request域中。在 jsp 页面访问。并且回显username
            request.setAttribute("msg","输入的用户名或者密码错误");
            request.setAttribute("username",username);

3.2、注册时回显错误信息和用户名,邮箱。 

        想要回显什么数据就往  域中保存数据即可【不一定都是request域】。在 jsp 页面取出。

 

4、优化Servlet 

第一个问题:其实我们知道  注册和登录功能  都属于用户模块,那么我们可不可以将他们合并成一个 Servlet呢?

解决方法:是可以解决的,通过代码可以发现,注册和登录功能其实和请求参数是一样的,请求参数是 login执行登录方法,regist 执行注册方法。那么我们可以在登录,注册页面的表单当中增加隐藏域,使用 if...else 用来区分登录还是注册。

第二个问题:在实际的项目开发当中,并不是有一个简单业务注册和登录,包括修改密码,增加删除各种各样的业务,每一个业务都用 if....else 判断太繁琐了。有没有一种方法,可以自动判断执行哪个方法?不使用 if...else

解决办法:使用反射机制,根据 隐藏域中的请求参数,来执行不同的方法。

第三个问题:每个项目中不仅仅有一个模块,对于这个项目来说还有图书管理模块.....每个模块都需要通过反射机制调用方法,所以我们可以将 反射机制调用方法  这部分重复的代码封装到 BaseServlet 类中。BaseServlet 继承 HttpServlet ,UserServlet 继承 BaseServlet。

解决第一个问题:

增加隐藏域:

<%--增加隐藏域。方便区分登录还是注册--%>
<input type="hidden" name="action" value="login">

<%--增加隐藏域。方便区分登录还是注册--%>
<input type="hidden" name="action" value="regist">

合并成一个UserServlet : 

public class UserServlet extends HttpServlet {
    private UserService userService = new UserServiceImpl();
    //处理登录请求
    protected void login(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        /*获取参数*/
        String username = request.getParameter("username");
        String password = request.getParameter("password");

        User user = userService.login(new User(null, username, password, null));
        if (user != null){
            /*用户名密码正确,跳转到登陆成功页面*/
            request.getRequestDispatcher("/pages/user/login_success.jsp").forward(request,response);
        }else{
            /*不正确,返回登录界面打印错误信息*/
            //将错误信息保存到 request域中。在 jsp 页面访问。并且回显username
            request.setAttribute("msg","输入的用户名或者密码错误");
            request.setAttribute("username",username);

            request.getRequestDispatcher("/pages/user/login.jsp").forward(request,response);
        }

    }
    //处理注册请求
    protected void regist(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 1、获取参数
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        String email = request.getParameter("email");
        String code = request.getParameter("code");
        // 2、判断验证码是否正确:还没有学动态获取验证码,所以这里就写死了
        if ("6n6np".equalsIgnoreCase(code)){
            //     验证码正确
            //         用户名是否重复
            boolean b = userService.existsUsername(username);
            if(b){
                //             用户名重复:返回注册页面
                //回显数据
                request.setAttribute("msg","用户名重复");
                request.setAttribute("username",username);
                request.setAttribute("email",email);

                request.getRequestDispatcher("/pages/user/regist.jsp").forward(request,response);
            }else{
                //             用户名不重复:跳转到注册成功界面,并向数据库中保存user信息
                userService.registUser(new User(null,username,password,email));
                request.getRequestDispatcher("/pages/user/regist_success.jsp").forward(request,response);
            }
        }else{
            //      验证码错误:返回注册页面
            //回显数据
            request.setAttribute("msg","验证码错误");
            request.setAttribute("username",username);
            request.setAttribute("email",email);

            request.getRequestDispatcher("/pages/user/regist.jsp").forward(request,response);
        }

    }


    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String action= request.getParameter("action");
        if ("login".equals(action)){
            login(request,response);
        }else if ("regist".equals(action)){
            regist(request,response);
        }
    }
}

解决第二个问题: 

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        /*获取请求参数*/
        String action = request.getParameter("action");
        /*通过反射机制调用方法*/
        try {
            /*("方法名","参数.class","参数.class"...)*/
            /*this:表示 UserServlet */
            Method method = this.getClass().getDeclaredMethod(action,HttpServletRequest.class,HttpServletResponse.class);
            method.invoke(this,request,response);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

解决第三个问题:

public class BaseServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        /*获取请求参数*/
        String action = request.getParameter("action");
        /*通过反射机制调用方法*/
        try {
            /*("方法名","参数.class","参数.class"...)*/
            /*this:表示 UserServlet */
            Method method = this.getClass().getDeclaredMethod(action,HttpServletRequest.class,HttpServletResponse.class);
            method.invoke(this,request,response);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

5、请求参数的封装和BeanUtils工具类的使用

BeanUtils 工具类,可以一次性将所有的参数注入到 Bean 对象中。

BeanUtils 工具类,经常用于把 Map 中的值注入到 JavaBean 中,或者是对象属性值的拷贝操
作。
提前导入 俩个 jar 包:

问题:每个项目中每块业务中的请求参数是不一样的,有的项目可能有十几,几十个请求参数,如果一个个都 request.getParameter() 是非常麻烦的。
解决方法: BeanUtils工具类中的  BeanUtils.copyProperties(注入的对象,Map集合) 方法可以将一个 map 集合 注入 到一个对象中。正好 请求参数 也是一个 map 集合,我们可以利用这一特征将 请求参数注入到 JavaBean 对象中

 BeanUtils.copyProperties() 方法 测试:

 相同的道理:每块业务可能都需要获取请求参数,所以将 BeanUtils.copyProperties() 方法 封装到一个父类当中。

//一次性获取请求参数
public class WebUtils {
    /*使用泛型代替具体的某个对象*/
    public static <T> T copyParamToBean(Map<String, String[]> map, T bean){
        try {
            /*(对象,Map集合)*/
            BeanUtils.copyProperties(bean,map);
        }  catch (Exception e) {
            e.printStackTrace();
        }
        return bean ;
    }
}

 使用  BeanUtils.copyProperties(注入的对象,Map集合) 取代 request.getParameter() 。

        /*使用封装好的 BeanUtils ,一次性获取所有的请求参数*/
        User user = WebUtils.copyParamToBean(request.getParameterMap(), new User());

还有一点需要注意的是:

BeanUtils 底层使用的也是反射机制,通过调用 name 的 set 方法去注入的,如果你的 User实体类的 set 方法 和 你的 name 不一致,他是注入不了的。

我修改了 User实体类中 setUsername 方法:

 我们可以看到 username 是 null 的,并没有注入进去。

第四阶段:图书管理模块

 4.1、构建数据库



USE bookbook;					## 切换到数据库


##创建图书表
CREATE TABLE t_book(
	`id` INT(11) PRIMARY KEY AUTO_INCREMENT, 	## 主键
	`name` VARCHAR(50) NOT NULL,				## 书名 
	`author` VARCHAR(50) NOT NULL,				## 作者
	`price` DECIMAL(11,2) NOT NULL,				## 价格
	`sales` INT(11) NOT NULL,					## 销量
	`stock` INT(11) NOT NULL,					## 库存
	`img_path` VARCHAR(200) NOT NULL			## 书的图片路径
);


## 插入初始化测试数据
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;

4.2、创建 JavaBean 实体类

public class Book {
    /*Integer可以接收  null 值 */
    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";

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

    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) {
        this.img_path = img_path;
    }
}

对 img_path 赋值 时,需判断是不是 null 或 "" ,是的话保留原来的值,不是才能赋值。

4.3、编写图书模块的Dao接口,以及实现类

public interface BookDao {
    /**
     *
     * @param book 增加书籍
     * @return
     */
    int addBook(Book book);

    /**
     *
     * @param id 根据 ID 删除
     */
    void delBook(int id);

    /**
     *
     * @param book 修改
     */
    void updateBook(Book book);

    /**
     *
     * @param id 根据ID 查询
     * @return
     */
    Book queryForByID(int id);

    /**
     *
     * @return 查询所有的书
     */
    List<Book> queryForList();
}
public class BookImplDao 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 BaseDao.update(sql, book.getName(), book.getAuthor(), book.getPrice(), 
                book.getSales(), book.getStock(), book.getImg_path());
    }

    @Override
    public void delBook(int id) {
        String sql = "delete from t_book where id=?";
        BaseDao.update(sql, id);
    }

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

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

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

4.4、编写图书模块的 service 接口 以及 实现类

public interface BookService {

    int addBook(Book book);

    void update(Book book);

    void delBook(int id);

    Book queryForByID(int id);

    List<Book> queryForList();
}
public  class BookServiceImpl implements BookService {
    private BookDao bookDao = new BookImplDao();
    @Override
    public int addBook(Book book) {
        return bookDao.addBook(book);
    }

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

    @Override
    public void delBook(int id) {
        bookDao.delBook(id);
    }

    @Override
    public Book queryForByID(int id) {
        return bookDao.queryForByID(id);
    }

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

4.5、编写图书模块的Web层

创建 BookServlet 继承 BaseServlet ,实现 图书的增删改查。

配置好 xml 文件:

    <servlet>
        <servlet-name>BookServlet</servlet-name>
        <servlet-class>yangzhaoguang.web.BookServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>BookServlet</servlet-name>
        <url-pattern>/manager/bookServlet</url-pattern>
    </servlet-mapping>

 BookServlet 中增加 list 方法,用于查询 t_book表中的所有数据:

public class BookServlet extends BaseServlet {
    private BookService bookService = new BookServiceImpl();
    //查询所有的数据,并显示到 book_manager.jsp 页面上
    protected void list(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        List<Book> list = bookService.queryForList();
        request.setAttribute("list",list);
        /*请求转发到:book_manager.jsp页面 */
        request.getRequestDispatcher("/pages/manager/book_manager.jsp").forward(request,response);
    }
}

修改 manager_menu 中的  图书管理 连接,能够 执行  BookServlet 中的 list 方法:

action?list : 写的参数,使我们写的BaseServlet中的反射机制反射到,能够执行 list 方法。

 修改  book_manager.jsp 页面,从 request 域中取出数据能够动态的显示到 jsp 页面中:

增加功能

首先修改  book_manager.jsp 页面中  增加图书  的连接,跳转到  book_edit.jsp 页面进行修改:

<td><a href="pages/manager/book_edit.jsp">添加图书</a></td>

修改 book_edit.jsp 页面中表单提交的地址,使其能够执行到 add 方法:

<form action="manager/bookServlet?action=add" method="post">

一定要修改 book_edit.jsp 页面中表单的 name 属性 和自己写的 Book 实体类的名字一致,不然使用 BeanUtils 工具类获取请求参数时获取不到:

向 BookServlet 中 增加 add方法,向数据库中增加图书:

public class BookServlet extends BaseServlet {
    private BookService bookService = new BookServiceImpl();
    //查询所有的数据,并显示到 book_manager.jsp 页面上
    protected void list(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        List<Book> list = bookService.queryForList();
        request.setAttribute("list",list);
        /*请求转发到:book_manager.jsp页面 */
        request.getRequestDispatcher("/pages/manager/book_manager.jsp").forward(request,response);
    }
    //增加图书功能
    protected void add(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
      /*获取请求参数*/
        Book book = WebUtils.copyParamToBean(request.getParameterMap(),new Book());
        int i = bookService.addBook(book);
        if (i != 0){
            /*重定向
            * 一定要重定向:不要使用转发,使用转发有 bug,刷新一次页面,数据库数据就会增加一条。
            * 或者直接调用 list 方法 也是可以的。
            * */
           response.sendRedirect(""+request.getContextPath()+"/manager/bookServlet?action=list");
        }
    }
}

删除功能: 

修改  book_manager.jsp 页面中的 删除  链接:

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

 并且为这个链接 增加 删除提示功能:

$(this).parent().parent().find("td:first").text()

$(this):表示某个 id 正在操作 <a></a>标签

$(this).parent() :表示 a 标签的父标签 td

$(this).parent().parent(). :表示 td 标签的父标签 tr 

$(this).parent().parent().find("td:first").text() : 表示 tr 中 第一个 td 标签里面的内容。

<script type="text/javascript">
	//删除标签绑定单击事件
	$(function () {
		$(".del").click(function () {
			/*
			* confirm:确认框,点击确认返回true,点击取消 返回 false;
			* return false ; 阻止页面跳转。
			*
			* */
			return confirm("确定删除《"+($(this).parent().parent().find("td:first").text())+"》这本书吗?");

		});
	});

</script>

在 BookServlet 中 提供一个 删除 的方法 :

public class BookServlet extends BaseServlet {
    private BookService bookService = new BookServiceImpl();
    //查询所有的数据,并显示到 book_manager.jsp 页面上
    protected void list(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        List<Book> list = bookService.queryForList();
        request.setAttribute("list",list);
        /*请求转发到:book_manager.jsp页面 */
        request.getRequestDispatcher("/pages/manager/book_manager.jsp").forward(request,response);
    }
    //增加图书功能
    protected void add(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
      /*获取请求参数*/
        Book book = WebUtils.copyParamToBean(request.getParameterMap(),new Book());
        int i = bookService.addBook(book);
        if (i != 0){
            /*重定向
            * 一定要重定向:不要使用转发,使用转发有 bug,刷新一次页面,数据库数据就会增加一条。
            * 或者直接调用 list 方法 也是可以的。
            * */
           response.sendRedirect(""+request.getContextPath()+"/manager/bookServlet?action=list");
        }
    }
    protected void delete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        /*获取参数*/
        Book book = WebUtils.copyParamToBean(request.getParameterMap(), new Book());
        bookService.delBook(book.getId());
        //删除完数据,重新刷新
        response.sendRedirect(""+request.getContextPath()+"/manager/bookServlet?action=list");

    }
}

修改功能:

 修改功能分为俩部分:1、回显修改图书的数据   2、进行修改

1、回显修改图书的数据 【根据ID查询数据】

修改  book_manager.jsp 页面中 修改 的链接:

				<%--先获取修改图书的数据--%>
					<td><a href="manager/bookServlet?action=getBookInfo&id=${c.id}">修改</a></td>

在 BookServlet 中 提供一个 getBookInfo 的方法,用于回显数据:

public class BookServlet extends BaseServlet {
    private BookService bookService = new BookServiceImpl();
    //查询所有的数据,并显示到 book_manager.jsp 页面上
    protected void list(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        List<Book> list = bookService.queryForList();
        request.setAttribute("list",list);
        /*请求转发到:book_manager.jsp页面 */
        request.getRequestDispatcher("/pages/manager/book_manager.jsp").forward(request,response);
    }
    //增加图书功能
    protected void add(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
      /*获取请求参数*/
        Book book = WebUtils.copyParamToBean(request.getParameterMap(),new Book());
        int i = bookService.addBook(book);
        if (i != 0){
            /*重定向
            * 一定要重定向:不要使用转发,使用转发有 bug,刷新一次页面,数据库数据就会增加一条。
            * 或者直接调用 list 方法 也是可以的。
            * */
           response.sendRedirect(""+request.getContextPath()+"/manager/bookServlet?action=list");
        }
    }
    protected void delete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        /*获取参数*/
        Book book = WebUtils.copyParamToBean(request.getParameterMap(), new Book());
        bookService.delBook(book.getId());
        //删除完数据,重新刷新
        response.sendRedirect(""+request.getContextPath()+"/manager/bookServlet?action=list");

    }

    //回显修改图书数据
    protected void getBookInfo(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        Book book = WebUtils.copyParamToBean(request.getParameterMap(), new Book());
        Book forByID = bookService.queryForByID(book.getId());
        /*将查到的数据存到 request 域中*/
        request.setAttribute("book",forByID);
        /*转发到 修改页面*/
        request.getRequestDispatcher("/pages/manager/book_edit.jsp").forward(request,response);
    }


}

修改 book_edit.jsp 页面中表单的 value 值,从 request 域中获取数据:

  2、进行修改

在进行修改时注意:由于修改和增加在一个 book_edit.jsp 页面中,并且只有一个隐藏域,该如何区分是修改还是增加呢?

解决方法:判断请求参数中是否有 ID,有ID 是修改,没有是增加。

向 BookServlet 中增加 update 方法: 

public class BookServlet extends BaseServlet {
    private BookService bookService = new BookServiceImpl();
    //查询所有的数据,并显示到 book_manager.jsp 页面上
    protected void list(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        List<Book> list = bookService.queryForList();
        request.setAttribute("list",list);
        /*请求转发到:book_manager.jsp页面 */
        request.getRequestDispatcher("/pages/manager/book_manager.jsp").forward(request,response);
    }
    //增加图书功能
    protected void add(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
      /*获取请求参数*/
        Book book = WebUtils.copyParamToBean(request.getParameterMap(),new Book());
        int i = bookService.addBook(book);
        if (i != 0){
            /*重定向
            * 一定要重定向:不要使用转发,使用转发有 bug,刷新一次页面,数据库数据就会增加一条。
            * 或者直接调用 list 方法 也是可以的。
            * */
           response.sendRedirect(""+request.getContextPath()+"/manager/bookServlet?action=list");
        }
    }
    protected void delete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        /*获取参数*/
        Book book = WebUtils.copyParamToBean(request.getParameterMap(), new Book());
        bookService.delBook(book.getId());
        //删除完数据,重新刷新
        response.sendRedirect(""+request.getContextPath()+"/manager/bookServlet?action=list");

    }

    //回显修改图书数据
    protected void getBookInfo(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        Book book = WebUtils.copyParamToBean(request.getParameterMap(), new Book());
        Book forByID = bookService.queryForByID(book.getId());
        /*将查到的数据存到 request 域中*/
        request.setAttribute("book",forByID);
        /*转发到 修改页面*/
        request.getRequestDispatcher("/pages/manager/book_edit.jsp").forward(request,response);
    }

    protected void update(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        Book book = WebUtils.copyParamToBean(request.getParameterMap(), new Book());
        //进行修改
        bookService.update(book);
        //修改完,重新刷新数据
        response.sendRedirect(""+request.getContextPath()+"/manager/bookServlet?action=list");

    }
}

第五阶段:图书管理模块的分页管理

5.1、分页初步查询

 首先需要获取的数据:

pageNo :当前页码。

pageSize:每页显示数据条数。

pageNo 和 pageSize 都需要客户端当做参数传递给 BookServlet ,因为要根据这俩个参数求下面三个的参数。

pageTotal:总页码数

pageTotalCount / pageSize ,如果有余数 pageTotal 还需要+1.

pageTotalCount:总记录条数

需要经过 Dao层在数据库进行查询:select count(*) from t_book ;

items:每页显示的数据

需要经过 Dao层在数据库进行查询:select * from t_book limit begin,pageSize ;

begin :  开始查询的索引    begin = (pageNo -1)* pageSize

 Page 类 :

// Page 对象

/**
 *
 * @param <T> 分页对象使用 泛型
 */
public class Page<T> {

    public static final  Integer PAGE_SIZE = 4 ;

    //当前页码
    private Integer pageNo ;
    // 总页码
    private Integer pageTotal ;
    //总记录数
    private Integer pageTotalCount;
    //每页显示数量
    private Integer pageSize = PAGE_SIZE ;
    //每页数据
    private List<T> item;

    public Page(Integer pageNo, Integer pageTotal, Integer pageTotalCount, Integer pageSize, List<T> item) {
        this.pageNo = pageNo;
        this.pageTotal = pageTotal;
        this.pageTotalCount = pageTotalCount;
        this.pageSize = pageSize;
        this.item = item;
    }

    public Page() {
    }

    public Integer getPageNo() {
        return pageNo;
    }

    public void setPageNo(Integer pageNo) {
        this.pageNo = pageNo;
    }

    public Integer getPageTotal() {
        return pageTotal;
    }

    public void setPageTotal(Integer pageTotal) {
        this.pageTotal = pageTotal;
    }

    public Integer getPageTotalCount() {
        return pageTotalCount;
    }

    public void setPageTotalCount(Integer pageTotalCount) {
        this.pageTotalCount = pageTotalCount;
    }

    public Integer getPageSize() {
        return pageSize;
    }

    public void setPageSize(Integer pageSize) {
        this.pageSize = pageSize;
    }

    public List<T> getItem() {
        return item;
    }

    public void setItem(List<T> item) {
        this.item = item;
    }

    @Override
    public String toString() {
        return "Page{" +
                "pageNo=" + pageNo +
                ", pageTotal=" + pageTotal +
                ", pageTotalCount=" + pageTotalCount +
                ", pageSize=" + pageSize +
                ", item=" + item +
                '}';
    }
}

BookDao :

  /**
         * 获取总记录数
         * @return
         */
        Integer queryForPageTotalCount();

        /**
         *
         * @param begin 开始的索引
         * @param pageSize 显示条数
         * @return 获取每页显示的数据
         */
        List<Book> queryForItems(int begin, int pageSize);

BookImplDao:

 /**
     *
     * @return 返回总记录条数
     */
    @Override
    public Integer queryForPageTotalCount() {
        String sql = "select count(*) from t_book";
        Number count = (Number) BaseDao.queryForSingle(sql);
        return count.intValue();
    }

    /**
     * 
     * @param begin 开始的索引
     * @param pageSize 显示条数
     * @return 返回每页的数据
     */
    @Override
    public List<Book> queryForItems(int begin, int pageSize) {
        String sql = "select `id` , `name` , `author` , `price` , `sales` , `stock` , `img_path` from t_book limit ?,?";
        return BaseDao.queryForList(Book.class,sql,begin,pageSize);
    }

BookService :


    /**
     *
     * @return 获取总记录数
     */
    int queryForPageTotalCount();


    /**
     *
     * @param pageNo
     * @param pageSize
     * @return 获取 page 对象
     */
    Page<Book> page(int pageNo, int pageSize);

BookServiceImpl :

 /**
     *
     * @return 获取总记录数
     */
    @Override
    public int queryForPageTotalCount() {
        int count = bookDao.queryForPageTotalCount();
        return count;
    }

    /**
     * 在 Service 层为 page对象中的属性赋值
     * @param pageNo
     * @param pageSize
     * @return page 对象
     */
    @Override
    public Page<Book> page(int pageNo, int pageSize) {
        Page<Book> page = new Page<>();
        //设置当前页码
        page.setPageNo(pageNo);
        //设置每页显示数量
        page.setPageSize(pageSize);
        //获取总记录数
        int pageTotalCount = bookDao.queryForPageTotalCount();
        //设置总记录数
        page.setPageTotalCount(pageTotalCount);

        //获取 总页码 pageTotal
        int pageTotal = pageTotalCount / pageSize ;
        //如果 还有余数,总页码要进行加一
        if (pageTotalCount % pageSize > 0){
            pageTotal++;
        }
        //设置总页码
        page.setPageTotal(pageTotal);

        //获取每页显示数据
        int begin = (page.getPageNo() -1) * pageSize ;
        List<Book> items = bookDao.queryForItems(begin,pageSize);
        //设置每页显示的数据
        page.setItem(items);

        return page;
    }

BookServlet :

 //分页处理
    protected void page(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);
        //获取 page 对象
        Page<Book> page = bookService.page(pageNo,pageSize);
        //将 page 对象保存到 request 域中
        request.setAttribute("page",page);
        //转发 到book_manager.jsp页面
        request.getRequestDispatcher("/pages/manager/book_manager.jsp").forward(request,response);

    }

在 book_manager.jsp 页面中获取 request 域中 page 对象中的 item 数据:

注意:不要写错:page 类中的 item 才是从数据库中查询出来的数据。不要写成 page.name。。。

 动态获取 页码数据:

5.2、上一页,下一页,首页,末页 的实现

首页: 将 pageNo 设置为 1 

上一页:将 pageNo -1 .

下一页:将 pageNo +1 

末页:将 pageNo 设置为总页码数  pageNo = pageTotal

到 首页的时候,上一页和首页应该设置为不可见,或者无法使用的状态,到末页的时候,下一页和末页应该设置为不可见,或者无法使用的状态

解决方法:只需要 判断 pageNo 的值,如果大于1,就显示上一页和首页。小于 pageTotal 时就显示下一页和末页

5.3、跳到指定页码

 

跳到指定页码步骤:

为 确定 按钮绑上点击事件

获取文本框中的val

 将 pageNo = val 作为参数,使用 location 跳转到 BookServlet ,执行 page 方法。

 5.4、页码边界检查

 为了防止输入的页码数超过总页码数:

其实在 BookServiceImpl 中设置 pageNo 值的时候,加入一个判断就好了,如果 pageNo < 1 , 就让 pageNo =1 自动跳转到第一页,如果 pageNo > pageTotal ,就让 pageNo = pageTotal,

自动跳到最后一页、

        //页码边界检查
        if (pageNo < 1){
            pageNo = 1 ;
        }
        if (pageNo > pageTotal){
            pageNo = pageTotal ;
        }
        //设置当前页码
        page.setPageNo(pageNo);

5.5、页码显示

需求:显示连续的页码,而且当前页码在中间。除了当前页码之外,每个页码都可以点击跳到指定页。

一共有俩种情况: 【具体情况和页面上显示的页码数有关系,比如页面上显示 7 个页码,那么 就分小于 7 和 大于 7 】

一、总页码小于等于5

1 2 3 4 5   无论点击哪个页码,5个页码总是在页面上显示。

二、总页码大于5。假设一共有 10 页,那么又会分三种情况。

1、当前页码属于前三页【1,2,3】,那么页面显示为:

1 2 3 4 5         页码范围就是: 1 ~ 5

2、当前页码属于后三页【8,9,10】,那么页面显示为:

6 7 8 9 10      页码范围是:pageTotal -4 ~ pageTotal

3、当前页码属于中间页【4,5,6,7】,页面显示为:

2  3 【4】 5  6

3  4 【5】 6  7

4  5 【6】 7  8

5  6 【7】  8 9

页码范围是: pageNo -2  ~ pageNo +2 

<%--页码显示--%>
				<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="${requestScope.page.pageNo == i}">
								【${i}】
							</c:if>
							<c:if test="${requestScope.page.pageNo != i}">
								<%--点击页码跳转到对应的页码上--%>
								<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 个:1,2,3 的情况,页码范围是:1-5.--%>
							<c:when test="${requestScope.page.pageNo <= 3}">
								<c:forEach  begin="1" end="5" var="i">
									<%--当前页码加个标记--%>
									<c:if test="${requestScope.page.pageNo == i}">
										【${i}】
									</c:if>
									<c:if test="${requestScope.page.pageNo != i}">
										<%--点击页码跳转到对应的页码上--%>
										<a href="manager/bookServlet?action=page&pageNo=${i}">${i}</a>
									</c:if>
								</c:forEach>
							</c:when>
							<%--小情况 2:当前页码为最后 3 个,8,9,10,页码范围是:总页码减 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="${requestScope.page.pageNo == i}">
										【${i}】
									</c:if>
									<c:if test="${requestScope.page.pageNo != i}">
										<%--点击页码跳转到对应的页码上--%>
										<a href="manager/bookServlet?action=page&pageNo=${i}">${i}</a>
									</c:if>
								</c:forEach>
							</c:when>
							<c:otherwise>
								<%--小情况 3:4,5,6,7,页码范围是:当前页码减 2 - 当前页码加 2--%>
								<c:forEach  begin="${requestScope.page.pageNo-2}" end="${requestScope.page.pageNo+2}" var="i">
									<%--当前页码加个标记--%>
									<c:if test="${requestScope.page.pageNo == i}">
										【${i}】
									</c:if>
									<c:if test="${requestScope.page.pageNo != i}">
										<%--点击页码跳转到对应的页码上--%>
										<a href="manager/bookServlet?action=page&pageNo=${i}">${i}</a>
									</c:if>
								</c:forEach>
							</c:otherwise>
						</c:choose>
					</c:when>
				</c:choose>

 代码优化:

每种判断都需要 forEach 遍历,代码重复太多,其实改变的也就是  遍历 的范围 begin 和 end ,我们只需要记录 begin 和 end 的范围,在判断完毕之后进行遍历即可。

<%--页码显示--%>
				<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="${requestScope.page.pageNo == i}">
								【${i}】
							</c:if>
							<c:if test="${requestScope.page.pageNo != i}">
								<%--点击页码跳转到对应的页码上--%>
								<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 个:1,2,3 的情况,页码范围是:1-5.--%>
							<c:when test="${requestScope.page.pageNo <= 3}">
								<%--记录 begin 和 end--%>
								<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}">
								<%--记录 begin 和 end--%>
								<c:set var="begin" value="${requestScope.page.pageTotal-4}" />
								<c:set var="end" value="${requestScope.page.pageTotal}" />
							</c:when>
							<c:otherwise>
								<%--小情况 3:4,5,6,7,页码范围是:当前页码减 2 - 当前页码加 2--%>
								<%--记录 begin 和 end--%>
								<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="${requestScope.page.pageNo == i}">
						【${i}】
					</c:if>
					<c:if test="${requestScope.page.pageNo != i}">
						<%--点击页码跳转到对应的页码上--%>
						<a href="manager/bookServlet?action=page&pageNo=${i}">${i}</a>
					</c:if>
				</c:forEach>

5.6、分页对删除、修改、增加的影响

 由于我们做了分页查询之后,修改、删除、增加完在跳到 list 就不行了,这是因为我们在页面上获取的数据是从 page 属性里得到的,我们需要跳转到 page。 

增加: 

当我们增加完之后,我们希望跳转到最后一页,能够看到我们增加的数据。

只需要将  总页码  当做参数传给 BookServlet 中。

修改:

删除:

5.7、前台的分页管理

 我们可以在/pages/client 目录下 新建一个 index.jsp,之前的 index.jsp 页面 只负责转发到Servlet 中。Servlet 转发到 新的 index.jsp 页面中、 

 

 

 /pages/client/index.jsp : 只留一个 <div class="b_list"> , 并且循环遍历。

<c:forEach items="${requestScope.page.item}" var="book">
			<div class="b_list">
				<div class="img_div">
					<img class="book_img" alt="" src="static/img/default.jpg" />
				</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>

将之前做好的 分页条  拷贝到 index.jsp 页面中,并把  /manager/bookServlet  修改为:/client/bookServlet 

5.8、分页条的抽取

 

 在前台和后台进行分页处理的时候,我们发现只有这个 请求地址不同。其他的都一样,那么我们就可以将 这个请求地址  在 Servlet 中设置好。然后将分页条的代码封装起来。

首先在 Page实体类中 设置一个 url 属性,用来设置 请求地址。

 分别在 BookServlet 和 ClientServlet 中设置 前台和后台的 请求地址:

 

使用  ${requestScope.page.url } 代替请求地址。并且将 前台和后台的分页条代码封装到  /common/page_ngv.jsp 中: 

 在页面中 分别使用静态包含引入:

5.9、价格区间搜索 

修改  index.jsp 页面 价格搜索的表单。

点击查询之后,会将 min,max 请求参数发送给 ClientServlet

x向ClientServlet 中增加 getPagePrice 方法,处理用户价格搜索并且分页:

 //价格搜索分页
    protected void getPageByPrice(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);

        //获取 page 对象
        Page<Book> page = bookService.pageByPrice(pageNo, pageSize, min, max);

        //将 min.max 追加到分页条的 请求地址 中.防止价格搜索分页时,点击页码会出现错乱的情况.
        String minStr = request.getParameter("min");
        String maxStr = request.getParameter("max");
        String url = "client/bookServlet?action=getPageByPrice&min=" + minStr + "&max=" + maxStr;
        //设置前台分页的请求地址
        page.setUrl(url);
        //将 page 对象保存到 request 域中
        request.setAttribute("page", page);
        //转发 到book_manager.jsp页面
        request.getRequestDispatcher("/pages/client/index.jsp").forward(request, response);

    }

 BookService 接口:

    /**
     *  根据价格区间 获取数据并进行分页
     * @param pageNo
     * @param pageSize
     * @param min
     * @param max
     * @return 返回 page 对象
     */
    Page<Book> pageByPrice(int pageNo, int pageSize, int min, int max);

BookService 实现类:

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

        //设置每页显示数量
        page.setPageSize(pageSize);
        //根据价格区间,获取总记录数.
        int pageTotalCount = bookDao.queryForPageTotalCountByPrice(min,max);
        //设置总记录数
        page.setPageTotalCount(pageTotalCount);

        //获取 总页码 pageTotal
        int pageTotal = pageTotalCount / pageSize ;
        //如果 还有余数,总页码要进行加一
        if (pageTotalCount % pageSize > 0){
            pageTotal++;
        }
        //设置总页码
        page.setPageTotal(pageTotal);

        //页码边界检查
        if (pageNo < 1){
            pageNo = 1 ;
        }
        if (pageNo > pageTotal){
            pageNo = pageTotal ;
        }
        //设置当前页码
        page.setPageNo(pageNo);

        //获取每页显示数据
        int begin = (page.getPageNo() -1) * pageSize ;
        List<Book> items = bookDao.queryForItemsByPrice(begin,pageSize,min,max);
        //设置每页显示的数据
        page.setItem(items);

        return page;
    }

BookDao:

 /**
     * 根据价格区间,获取总记录数
     * @param min
     * @param max
     * @return
     */
    int queryForPageTotalCountByPrice(int min, int max);

    /**
     * 根据价格区间搜索,并进行分页处理
     * @param begin
     * @param pageSize
     * @param min
     * @param max
     * @return
     */
    List<Book> queryForItemsByPrice(int begin, int pageSize, int min, int max);

BookDao实现类:

 public int queryForPageTotalCountByPrice(int min, int max) {
        String sql = "select count(*) from t_book where price between ? and ?";
        Number count = (Number)BaseDao.queryForSingle(sql, min, max);
        return count.intValue();
    }

    /**
     * 根据价格区间搜索,并进行分页处理
     * @param begin
     * @param pageSize
     * @param min
     * @param max
     * @return
     * 为了用户体验,查询完使用 order by根据价格高低进行排序。
     */
    @Override
    public List<Book> queryForItemsByPrice(int begin, int pageSize, int min, int max) {
        String sql = "select `id` , `name` , `author` , `price` , `sales` , `stock` , `img_path`  " +
                "from t_book where price between ? and ? order by price limit ?,?";
        return BaseDao.queryForList(Book.class,sql,min,max,begin,pageSize);
    }

第六阶段:登录用户信息显示

6.1、登录后显示 欢迎信息 

在登录成功后,显示 : 欢迎 用户名。 

只需要在登陆成功的时候,将用户名保存到 session 域中。在 jsp 页面中取出来即可。

 取出session中的数据:

​​​​​​

 在登录成功 和 不登录的时候,主页面的不同:

登陆成功后:

未登录时:

 在 index.jsp 页面处,判断 session域中是否有 username的值:

 6.2、注销登录

1、清除session域

2、重定向到 主页 或者 登录页面 

    /*处理注销业务*/
    protected void loginOut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 删除 session
        request.getSession().invalidate();
        // 重定向到 主页
        response.sendRedirect(request.getContextPath());
    }

修改注销请求地址:

<a href="user?action=loginOut">注销</a>&nbsp;&nbsp;

6.3、表单重复提交问题 

表单重复提交有三种常见的情况:
一:提交完表单。服务器使用请求转来进行页面跳转。这个时候,用户按下功能键 F5 ,就会发起最后一次的请求。 造成表单重复提交问题。 解决方法:使用重定向来进行跳转
二:用户正常提交服务器,但是由于网络延迟等原因,迟迟未收到服务器的响应,这个时候,用户以为提交失败, 就会着急,然后多点了几次提交操作,也会造成表单重复提交。
三:用户正常提交服务器。服务器也没有延迟,但是提交完成后,用户回退浏览器。重新提交。也会造成表单重复 提交。

 

  这三种问题都会重复提交表单情况

 解决办法:使用验证码

 使用谷歌验证码:

1、增加 jar 包

 2、在 web.xml文件中配置  KaptchaServlet 类 。这个类是jar包中人家写好的类。

    <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>/kaptcha.jpg</url-pattern>
    </servlet-mapping>

3、在表单中使用 img 标签,使用验证码。

4、在 loginServlet 中 获取session中的验证码并用变量保存起来,删除 session 中的验证码,并验证 表单中的验证码 与 session 中是否一致。

 protected void login(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String username = req.getParameter("username");
        // 获取到 session 中的验证码
        // key:是 jar 包中设置好的变量。
        //com.google.code.kaptcha 下的 KAPTCHA_SESSION_KEY 变量;
        String token = (String) req.getSession().getAttribute(KAPTCHA_SESSION_KEY);
        // 删除 session 中的验证码,防止重复提交
        req.getSession().removeAttribute(KAPTCHA_SESSION_KEY);
        //获取 表单中的验证码
        String code = req.getParameter("code");
        //判断
        if (token != null && token.equals(code)){
            System.out.println("登录成功。");
            //第一种问题:转发, 会造成重复提交表单的问题。使用重定向可解决问题
            // req.getRequestDispatcher("login_success.jsp").forward(req,resp);

/*            try {
                //第二种问题:当服务器出现延迟时,不断点击 登录  也会重复提交
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }*/

            resp.sendRedirect("login_success.jsp");
        }else{
            System.out.println("不要重复提交表单");
        }
    }

 session 域中 的 KEY :com.google.code.kaptcha.Constants类中的KAPTCHA_SESSION_KEY 变量

session 域中取出验证码后,一定要删除。千万别忘记!!!!!!

6.4、将验证码增加到书城项目中

1、引进 jar 包

2、配置 web.xml 文件

3、使用 img 标签并使用验证码

<%--使用 谷歌验证码--%>
<img alt="" src="kaptcha.jpg" style="float: right; margin-right: 45px ;width: 120px;height: 40px"
id="code_img" >

4、Servlet 中获取 验证码并进行判断。

动态获取验证码,。每点击一次验证码图片就更换验证码:

给 验证码图片增加一个点击事件,每次单击都发送 验证码 链接:

由于浏览器中都有缓存机制,访问相同的路径会增加到缓存当中。缓存一般由:请求地址+参数 决定,所以我们可以增加一个 可变的参数。每次点击图片都更改参数。

            /*给图片一个点击事件*/
            $("#code_img").click(function () {
                // src : 是 指当前正在响应的DOM对象。这个对象可读,可写。
                // 设置动态的验证码,谷歌浏览器可以用,但是火狐和IE会把相同地址的路径增加到缓存里去
                //所以并不会动态显示。
                //解决办法:可以增加一个参数,每次点击都会改变参数
                this.src = "http://localhost:8080/book/kaptcha.jpg?a="+new Date() ;
            });

第七阶段:购物车模块 

7.1、购物车模型创建

/*购物车商品模型*/
public class CartItems {
    //商品id
    private Integer id ;
    //商品名
    private String name ;
    //商品数量
    private Integer count ;
    //商品价格
    private BigDecimal price;
    //总价格
    private BigDecimal totalPrice ;

    public CartItems() {
    }

    public CartItems(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;
    }

    @Override
    public String toString() {
        return "CartItems{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", count=" + count +
                ", price=" + price +
                ", 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;
    }
}
/*购物车模型*/
public class Cart {
    //总商品数量
    private Integer totalCount ;
    //总商品价格
    private BigDecimal totalPrice ;
    // 购物车商品
    private List<CartItems> items = new ArrayList<CartItems>() ;

    public Cart() {
    }

    public Cart(Integer totalCount, BigDecimal totalPrice, List<CartItems> items) {
        this.totalCount = totalCount;
        this.totalPrice = totalPrice;
        this.items = items;
    }

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

    public Integer getTotalCount() {
        return totalCount;
    }

    public void setTotalCount(Integer totalCount) {
        this.totalCount = totalCount;
    }

    public BigDecimal getTotalPrice() {
        return totalPrice;
    }

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

    public List<CartItems> getItems() {
        return items;
    }

    public void setItems(List<CartItems> items) {
        this.items = items;
    }
}

7.2、 购物车功能的实现

由于购物车 是使用 session 实现的,不需要与数据库交互,不用写Dao和Service 层。

/*购物车模型*/
public class Cart {
    //总商品数量
    //由于不需要人为更改,不需要使用全局变量
    // private Integer totalCount ;
    //总商品价格
    // private BigDecimal totalPrice ;
    // 购物车商品
    //使用 map 集合。 key:商品id, value:商品信息。
    private Map<Integer,CartItems> items = new LinkedHashMap<>() ;


    // 往购物车增加商品
    public void addItem(CartItems cartItem){
        // 直接增加是不行的,因为如果相同ID时,想增加的是商品的数量以及价格。而不是又增加一个商品
        // items.put();
        // 所以我们需要先判断 购物车里面是否有和 cartItem 相同的商品。ID 是唯一的。
        CartItems item = this.items.get(cartItem.getId());
        if (item == null){
            // 说明没有相同的商品,直接增加到购物车
            items.put(cartItem.getId(),cartItem);
        }else{
            //说明有相同的商品,让商品的数量+1,并且增加商品的总价格
            item.setCount(item.getCount()+1);
            item.setTotalPrice(item.getTotalPrice().add(cartItem.getTotalPrice()));
        }
    }

    // 删除商品
    public void deleteItem(int id){
        items.remove(id);
    }

    // 清空购物车
    public void clear(){
        items.clear();
    }

    /**
     * 根据id修改商品的数量
     * @param id 修改商品的id
     * @param count 修改的数量
     */
    public void updateCount(int id,int count){
        // 首先判断购物车里面是否有相同id的商品
        CartItems item = this.items.get(id);
        if (item != null){
            // 有相同 id 的商品,直接修改 count。
            item.setCount(count);
            //并且修改商品的总价格.商品数量 * 商品价格
            //multiply : BigDecimal 中的乘法。
            item.setTotalPrice(item.getPrice().multiply(new BigDecimal(count)));
        }
    }



    // 动态获取商品总数量
    public Integer getTotalCount() {
      Integer  totalCount = 0;
        // 遍历 购物车 中商品信息,累加每种商品的数量
        //values:获取map集合中所有的value
        for (CartItems item : items.values()) {
            //累加
            totalCount += item.getCount();
        }

        return totalCount;
    }

    //由于商品数量不能人为修改,他是根据每个商品的数量动态获取的,所以不需要这个set方法
    // public void setTotalCount(Integer totalCount) {
    //     this.totalCount = totalCount;
    // }
    public BigDecimal getTotalPrice() {
        //初始化
        BigDecimal totalPrice = new BigDecimal(0) ;
        for (CartItems item : items.values()) {
            //遍历 购物车中的商品,获取每种商品的总价格并进行累加
           // add:BigDecimal 类型中的加法。
           totalPrice = totalPrice.add(item.getTotalPrice());
        }
        return totalPrice;
    }

    //由于商品总价格不能人为修改,他是根据每个商品的数量及价格动态获取的,所以不需要这个set方法
    // public void setTotalPrice(Integer totalPrice) {
    //     this.totalPrice = totalPrice;
    // }

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

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


    public Cart() {
    }

    public Cart( Map<Integer,CartItems> items) {
        // this.totalCount = totalCount;
        // this.totalPrice = totalPrice;
        this.items = items;
    }

    @Override
    public String toString() {
        return "Cart{" +
                // 遍历时,动态获取购物车中所有商品的总数量
                "totalCount=" + getTotalCount() +
                //便利时,动态获取购物车中所有商品的价格
                ", totalPrice=" + getTotalPrice() +
                ", items=" + items +
                '}';
    }
}

7.3、增加购物车功能的实现

 为 加入购物车  按钮 绑上点击事件,只要单击它就发送请求信息到 CartServlet  中。

注意:这里不要加 ID 绑上点击事件,因为这个按钮再forEach循环里,ID是唯一的,class可以为多个按钮榜单击击事件

	<script type="text/javascript">
		$(function () {
			//为加入购物车按钮绑上点击事件。
			$(".addItem").click(function () {
				//获取到 图书的id
				//this 表示正在响应的DOM对象。就是 button 标签对象
				var id = $(this).attr("bookId");
				var bookname = $(this).attr("bookname");
				//跳转到 cartServlet
				window.location = "http://localhost:8080/book/cartServlet?action=addItem&id="+id;
				confirm("已经将【"+bookname+"】这本书增加到了购物车")
			});
		});
	</script>

在 CartServlet 中创建 addItem 方法:增加图书到购物车。

1、获取图书的ID

2、根据ID,bookService.queryById 查询图书的信息。

3、将 book 转换为 CartItem 。

4、创建购物车,。并且自始至终只用这一个购物车。

5、将 图书 增加到购物车

6、重定向。

//实现购物车模块的一些功能
public class CartServlet extends BaseServlet {
    private BookService bookService = new BookServiceImpl();
    //增加购物车
    protected void addItem(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //1、获取图书的id
        int id = WebUtils.parseInt(request.getParameter("id"),0);
        //2、根据 id 查询图书的信息
        Book book = bookService.queryForByID(id);
        //3、将图书信息转换为 CartItems
        CartItems cartItems = new CartItems(book.getId(),book.getName(),1,book.getPrice(),book.getPrice());
        //4、将 购物车cart  放到 session域中,购物车只能有一个。
        //以下 代码保证了:自始至终只用了一个购物车。这样才能对相同的图书进行计数。
         Cart cart = (Cart) request.getSession().getAttribute("cart");
         if (cart == null){
             //创建购物车
             cart = new Cart();
             //放到 session 域中
             request.getSession().setAttribute("cart",cart);
         }
         //5、增加到购物车
         cart.addItem(cartItems);
        System.out.println(cart);
       
        /**  response.sendRedirect("index.jsp");
         * 这样重定向还有一个 bug,他跳转的并不是原来的页面。无论在第几页增加的购物车。他跳转的总是 首页。
         * 那么我们希望在第二页增加的购物车还跳转到第二页。
         * HTTP协议中请求头中有一个 Referer 参数:他保存了 页面 的地址。我们只需要将跳转的地址给成 Referer 的地址即可。
         */
        System.out.println(request.getHeader("Referer"));
        response.sendRedirect("Referer 保存的地址:" +request.getHeader("Referer"));
    }
}

 7.4、展示购物车

购物车中的信息都保存在了 session域中,所以直接从 session 域中取出数据就行了。 

cart.jsp 页面:

<div id="main">
	
		<table>
			<tr>
				<td>商品名称</td>
				<td>数量</td>
				<td>单价</td>
				<td>金额</td>
				<td>操作</td>
			</tr>
			<%--如果购物车为空,显示下面信息--%>
			<c:if test="${ empty sessionScope.cart.items}">
				<tr>
					<td colspan="5"><a href="index.jsp">购物车竟然是空的~</a> </td>
				</tr>
			</c:if>
			<%--从 session 域中取出数据
				cart.items:是个map集合,value才是商品信息
			--%>
			<c:forEach items="${sessionScope.cart.items}" var="cart">
			<tr>
				<td>${cart.value.name}</td>
				<td>${cart.value.count}</td>
				<td>${cart.value.price}</td>
				<td>${cart.value.totalPrice}</td>
				<td><a href="#">删除</a></td>
			</tr>
			</c:forEach>
		</table>

		<%--如果购物车不为空,显示下面信息--%>
		<c:if test="${not empty sessionScope.cart.items}">
		<div class="cart_info">
			<span class="cart_span">购物车中共有<span class="b_count">${sessionScope.cart.totalCount}</span>件商品</span>
			<span class="cart_span">总金额<span class="b_price">${sessionScope.cart.totalPrice}</span>元</span>
			<span class="cart_span"><a href="#">清空购物车</a></span>
			<span class="cart_span"><a href="pages/cart/checkout.jsp">去结账</a></span>
		</div>
		</c:if>
	
	</div>

7.5、删除商品

<%--发送删除请求--%>
<td><a href="cartServlet?action=deleteItem&id=${cart.value.id}" class="deleteItem">删除</a></td>
	<script type="text/javascript">
		$(function () {
			$(".deleteItem").click(function () {
				/*删除提示信息*/
					return confirm("确定删除【"+$(this).parent().parent().find("td:first").text()+"】这本书码?");
			});
		});
	</script>

 CartServlet 中增加删除商品的方法:

 //删除商品操作
    protected void deleteItem(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        int id = WebUtils.parseInt(request.getParameter("id"), 0);
        Cart cart = (Cart) request.getSession().getAttribute("cart");

        if (cart != null){
            //删除
            cart.deleteItem(id);
            System.out.println(cart);
            //重定向
            response.sendRedirect(request.getHeader("Referer"));
        }

    }

7.6、清空购物车

点击 清空购物车 时要进行提示:确定要清除购物车吗?

cart.jsp 页面:

<span class="cart_span"><a href="cartServlet?action=clear" class="clear">清空购物车</a></span>
			$(".clear").click(function () {
					return confirm("清空购物车吗?");
			});

CartServlet :


    //清空购物车
    protected void clear(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //1、 获取购物车
        Cart cart = (Cart) request.getSession().getAttribute("cart");
        if (cart != null){
            // 清空购物车
            cart.clear();
            //重定向到原来的页面
            response.sendRedirect(request.getHeader("Referer"));
        }
    }

7.7、修改购物车商品数量

为 购物车中的 商品数量 加一个文本框:

使用 change 事件,当文本框中 value 发生变化时,发送请求给 CartServlet 

<td>
	<%--bookId:自定义的标签。--%>
	<input type="text"   class="updateCount"
	 bookId="${cart.value.id}" value="${cart.value.count}" style="width: 60px">
</td>
<script>
			// 为 数量框 绑定 change 事件,只要文本框的value发生变化,就会执行这个事件
			$(".updateCount").change(function () {
				//获取修改商品的名字
				var  name = $(this).parent().parent().find("td:first").text();
				//获取修改的数量
				var count = this.value;
				//获取修改商品的id
				var id = $(this).attr("bookId");
				if (confirm("确定将【"+name+"】这本书的商品的数量修改为:【"+count+"】吗?")) {
					//确认修改后,将 id 和 count 的参数发送给 Servlet
					window.location = "http://localhost:8080/book/cartServlet?action=updateCount&id="+id+"&count="+count;
				}else{
					// 点击取消后,修改为原来的值。
					this.value = this.defaultValue ;
				}

			});
</script>

CartServlet: 

    //修改商品数量
    protected void updateCount(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //获取参数
        int id = WebUtils.parseInt(request.getParameter("id"),0);
        int count = WebUtils.parseInt(request.getParameter("count"),1);
        //获取购物车
        Cart cart = (Cart) request.getSession().getAttribute("cart");
        if (cart != null){
            //修改
            cart.updateCount(id,count);
            //重定向到原来的页面
            response.sendRedirect(request.getHeader("Referer"));
        }
    }

7.8、首页购物车信息回显

 在增加图书到购物车时,往session域中保存最后一个增加商品的信息。

index.jsp 页面:

  <div style="text-align: center">
            <%--如果购物车商品信息为空,显示下列信息--%>
            <c:if test="${empty sessionScope.cart.items}">
            <div>
                <span style="color: red">购物车竟然是空的!</span>
            </div>
        </div>
        </c:if>
        <%--如果购物车商品信息不为空,显示下列信息--%>
        <c:if test="${not empty sessionScope.cart.items}">
            <%--获取商品数量--%>
        <span>您的购物车中有${sessionScope.cart.totalCount}件商品</span>
        <div>
                <%--从 sessio 域中取出最后一次增加的商品名称--%>
            您刚刚将<span style="color: red" id="tishi">${sessionScope.lastItem}</span>加入到了购物车中
        </div>
    </div>
    </c:if>

在 CartServlet 的 addItem 方法中增加:

        // 将最后一次放到购物车中的商品 增加到 session 域中
        request.getSession().setAttribute("lastItem",cartItems.getName());

第八阶段:订单模块

8.1、订单模型创建分析

 订单:就是页面中的内容。包日期,金额,状态等信息。用户编号是用于区分该订单是属于哪个用户的。

订单项:当我们点击 查看详情  时,会出现类似于购物车中的内容,有商品名称,数量...等等一些属性,orderId:用于区分查看的哪个订单。 

订单中有些功能管理员和用户是不一样的。管理员:查看所有订单,负责发货,查看订单详情。用户:查看订单详情,查看我的订单,签收订单。

 8.2、订单与订单项数据库创建

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 t_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`) )

8.3、订单项与订单的实体类创建

 订单:

public class Order {
    private String orderId ;
    private Date createTime ;
    private BigDecimal price ;
    //订单状态,0表示未发货,1 表示已发货,2 表示 已签收
    private Integer status = 0;
    private String userId ;

    public Order() {
    }

    public Order(String orderId, Date createTime, BigDecimal price, Integer status, String userId) {
        this.orderId = orderId;
        this.createTime = createTime;
        this.price = price;
        this.status = status;
        this.userId = userId;
    }

    @Override
    public String toString() {
        return "Order{" +
                "orderId='" + orderId + '\'' +
                ", createTime=" + createTime +
                ", price=" + price +
                ", status=" + status +
                ", userId='" + userId + '\'' +
                '}';
    }

    public String getOrderId() {
        return orderId;
    }

    public void setOrderId(String orderId) {
        this.orderId = orderId;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    public BigDecimal getPrice() {
        return price;
    }

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

    public Integer getStatus() {
        return status;
    }

    public void setStatus(Integer status) {
        this.status = status;
    }

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }
}

订单项:

//订单项
public class OrderItem {
    private Integer id ;
    private String name ;
    private Integer count ;
    private BigDecimal price ;
    private BigDecimal totalPrice ;
    //订单编号
    private String orderId;

    public OrderItem() {
    }

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

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

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

    public String getOrderId() {
        return orderId;
    }

    public void setOrderId(String orderId) {
        this.orderId = orderId;
    }
}

8.4、生成订单功能的实现

Dao 层:

OrderDao 和 OrderItemDao:一个实体类对应一个Dao

public interface OrderDao {
    //保存订单
    int saveOrder(Order order);
}
public interface OrderItemDao {
    //保存订单项
    int saveOrderItem(OrderItem orderItem);
}

OrderDaoImpl 和 OrderItemImpl :

public class OrderDaoImpl extends BaseDao implements OrderDao {

    /**
     * 由于 user_id 有外键约束,一定与t_user表中的id,相对应。
     * @param order 生成用户订单
     * @return
     */
    @Override
    public int saveOrder(Order order) {
        String sql = "insert into t_order(order_id,create_time,price,status,user_id) values(?,?,?,?,?)";
        return BaseDao.update(sql,order.getOrderId(),order.getCreateTime(),order.getPrice(),
                order.getStatus(),order.getUserId());
    }
}
public class OrderItemDaoImpl extends BaseDao implements OrderItemDao {
    /**
     * 生成订单项
     * @param orderItem
     * @return
     */
    @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());
    }
}

Service 层:

OrderService :负责生成订单。一个模块对应一个 service ,模块中有几个功能,,模块就对应几个实现的方法。

public interface OrderService {
    /**
     * 生成订单
     * @param cart 购物车
     * @param userId 用户id
     * @return 返回订单号
     */
    String createOrder(Cart cart,Integer userId);

}

Service 中不仅要实现生成订单的功能,还要实现对商品的销量和库存动态的修改。 

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

    @Override
    /**
     * 生成订单方法实现
     */
    public String createOrder(Cart cart, Integer userId) {
        //获取订单号
        //订单号的特定===唯一性。
        //我们可以加上  时间戳+用户id 生成订单号
        String orderId = System.currentTimeMillis() + "" + userId;
        //生成订单
        Order order = new Order(orderId, new Date(), cart.getTotalPrice(), 0, userId);
        // 保存到数据库中
        orderDao.saveOrder(order);

        //生成订单项
        //将 购物车中 items 的所有商品项 转换为订单项
        Map<Integer, CartItems> items = cart.getItems();
        //遍历商品信息
        for (CartItems item : items.values()) {
            //将 CartItems 转换为 OrderItem
            OrderItem orderItem = new OrderItem(null, item.getName(), item.getCount(), item.getPrice(), item.getTotalPrice(), orderId);

            //将订单项保存到数据库中
            orderItemDao.saveOrderItem(orderItem);

            //通过商品id查找商品信息
            Book book = bookDao.queryForByID(item.getId());
            //修改库存和销量
            // 现有销量 = 原有销量 + 订单中商品的数量
            book.setSales(book.getSales() + item.getCount());
            // 现有库存 = 原有库存 - 订单中商品的数量
            book.setStock(book.getStock() - item.getCount());
            //在数据库进行修改
            bookDao.updateBook(book);
        }

        //生成订单后,要清空购物车。
        cart.clear();
        return orderId;
    }
}

Web 层:

OrderServlet :

public class OrderServlet extends BaseServlet {
    private OrderService orderService = new OrderServiceImpl();
    protected void createOrder(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //从 session 域中取出购物车和userid
        Cart cart = (Cart) request.getSession().getAttribute("cart");
        User loginUser = (User) request.getSession().getAttribute("user");
        //如果 loginUser 为空,说明还没有进行登录,跳转到 登录界面
        if (loginUser == null){
            //重定向记得加上 根路径
            response.sendRedirect(request.getContextPath()+"/pages/user/login.jsp");
            //一般跳转,重定向之后,不要再执行代码了。
            return;
        }
        Integer userId = loginUser.getId();
        //生成订单
        String orderId = orderService.createOrder(cart, userId);
        //将 订单号 放到 session域中
        request.getSession().setAttribute("orderId",orderId);
        //重定向到  /pages/cart/checkout.jsp 页面
        response.sendRedirect(request.getContextPath() + "/pages/cart/checkout.jsp");
    }
}

 修改页面跳转的路径:点击  去结账,生成订单,。

<span class="cart_span"><a href="orderServlet?action=createOrder">去结账</a></span>

在 checkout.jsp 页面显示订单号:

<h1>你的订单已结算,订单号为${sessionScope.orderId}</h1>

第九阶段:使用 Filter 和 ThreadLocal 完善系统

9.1、使用Filter拦截器:

使用拦截器对 manager目录下的所有页面进行拦截,只有登录后才能访问。 

拦截操作:

public class ManagerFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @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){
            //没有登录的情况下,跳转到登录界面
            servletRequest.getRequestDispatcher("/pages/user/login.jsp").forward(servletRequest,servletResponse);
        }else{
            filterChain.doFilter(servletRequest,servletResponse);
        }
    }

    @Override
    public void destroy() {

    }
}

 配置文件:

    <filter>
        <filter-name>ManagerFilter</filter-name>
        <filter-class>yangzhaoguang.filter.ManagerFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>ManagerFilter</filter-name>
        <!--配置多个拦截路径-->
        <url-pattern>/pages/manager/*</url-pattern>
        <url-pattern>/manager/bookServlet</url-pattern>
    </filter-mapping>

9.2、使用 ThreadLocal:

目前程序中的bug:

 假设我们在生成订单的时候,正好在生成完订单,即将保存订单项的时候。出现了一个异常,那么在数据库中有订单的信息,但是并没有订单项的信息。 所以我们希望使用JDBC手动提交的方式操作数据库。

分析如下:

 我们的编写程序时,并没有创建线程,所以整个项目都是在一个线程中完成的。

下面进行修改:

JdbcUtils :

确保我们整个项目中的 Jdbc 操作都使用一个 Connection 连接对象。

public class JdbcUtils {

    private static DruidDataSource source;
    // 使用 ThreadLocal
    private static ThreadLocal<Connection> conns = new ThreadLocal<>();

    static {
        try {
            /*加载流*/
            Properties pros = new Properties();
            InputStream is = JdbcUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");
            pros.load(is);
            source = (DruidDataSource) DruidDataSourceFactory.createDataSource(pros);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    public static Connection getConnection()  {
        Connection conn = null;
        //从 ThreadLocal 中取出 conn
        conn = conns.get();

        if (conn == null){
            try {
                //从数据库连接池中获取连接
                conn = source.getConnection();
                //将 Connection 保存到 ThreadLocal 中
                conns.set(conn);
                // 设置手动提交事务
                conn.setAutoCommit(false);
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return conn ;
        // try {
        //     conn = source.getConnection();
        // } catch (SQLException e) {
        //     e.printStackTrace();
        // }
        // return conn;
    }

    // 提交事务并且释放资源
    public static void commitAndClose(){
        Connection conn = conns.get() ;
        if (conn != null){
            try {
                conn.commit(); //提交事务
            } catch (SQLException e) {
                e.printStackTrace();
            }finally {
                try {
                    conn.close(); //释放资源
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
        //最终使用完,要清除 线程本地变量池
        conns.remove();
    }

    //回滚事务并且释放资源
    public static void rollbackAndClose(){
        Connection conn = conns.get() ;
        if (conn != null){
            try {
                conn.rollback(); //回滚事务
            } catch (SQLException e) {
                e.printStackTrace();
            }finally {
                try {
                    conn.close(); //释放资源
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
        //最终使用完,要清除 线程本地变量池
        conns.remove();
    }

    /*释放资源*/
    // public static void closeConnection(Connection conn) {
    //     if (conn != null) {
    //         try {
    //             conn.close();
    //         } catch (SQLException e) {
    //             e.printStackTrace();
    //         }
    //     }
    // }
}

 BaseDao:

 BaseDao 中 一定要记得抛出异常,将异常抛给 Servlet 中处理。

/*使用DBUtils操作数据库增删改查*/
public class BaseDao {
    private static QueryRunner runner = new QueryRunner();

    /*通用的增删改*/
    public static int update(String sql, Object... args) {
        Connection conn = JdbcUtils.getConnection();
        int count = -1;
        try {
            /*影响数据库的条数*/
            count = runner.update(conn, sql, args);
        } catch (SQLException e) {
            e.printStackTrace();
            //这里一定要抛出异常。在 Service 层处理提交事务或者回滚。
            //如果不抛出异常,在 OrderServlet 中 不会进行提交事务或者回滚事务的处理,就会报错。
            throw new RuntimeException(e);

        //    在使用 ThreadLocal 后,就不需要在关闭了,因为关闭是和提交或者回滚事务一起的。
        // }finally {
        //     JdbcUtils.closeConnection(conn);
        }
        return count;
    }

    /*查询:只返回一条数据*/
    public static <T> T queryForOne(Class<T> tClass, String sql, Object... args) {
        BeanHandler<T> beanHandler = new BeanHandler<T>(tClass);
        Connection conn = JdbcUtils.getConnection();
        T t = null;
        try {
            t = runner.query(conn, sql, beanHandler, args);
        } catch (SQLException e) {
            e.printStackTrace();
            //并且在这里要往外面抛出异常
            throw new RuntimeException(e);
        // }finally {
        //     JdbcUtils.closeConnection(conn);
        }
        return t;
    }

    /*返回多条记录*/
    public static <T> List<T> queryForList(Class<T> tClass, String sql, Object... args) {
        BeanListHandler<T> beanList = new BeanListHandler<>(tClass);
        Connection conn = JdbcUtils.getConnection();
        List<T> list = new ArrayList<>();
        try {
            list = runner.query(conn, sql, beanList, args);
        } catch (SQLException e) {
            e.printStackTrace();
            //并且在这里要往外面抛出异常
            throw new RuntimeException(e);
        // }finally {
        //     JdbcUtils.closeConnection(conn);
        }
        return list;
    }

    /*查询特殊值*/
    public static Object queryForSingle(String sql, Object... args) {
        ScalarHandler<Object> handler = new ScalarHandler<>();
        Connection conn = JdbcUtils.getConnection();
        Object data = null;
        try {
            data = runner.query(conn, sql, handler,args);
        } catch (SQLException e) {
            e.printStackTrace();
            //并且在这里要往外面抛出异常
            throw new RuntimeException(e);
        // }finally {
        //     JdbcUtils.closeConnection(conn);
        }
        return data;
    }
}

 OrderServlet:

public class OrderServlet extends BaseServlet {
    private OrderService orderService = new OrderServiceImpl();
    protected void createOrder(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //从 session 域中取出购物车和userid
        Cart cart = (Cart) request.getSession().getAttribute("cart");
        User loginUser = (User) request.getSession().getAttribute("user");
        //如果 loginUser 为空,说明还没有进行登录,跳转到 登录界面
        if (loginUser == null){
            //重定向记得加上 根路径
            response.sendRedirect(request.getContextPath()+"/pages/user/login.jsp");
            //一般跳转,重定向之后,不要再执行代码了。
            return;
        }
        Integer userId = loginUser.getId();

        //在生成订单时,处理 BaseDao 中抛过来的异常。这也是为什么在BaseDao中需要抛出异常。
        String orderId = null;
        try {
            //生成订单
            orderId = orderService.createOrder(cart, userId);
            JdbcUtils.commitAndClose();//提交事务并且释放资源
        } catch (Exception e) {
            //如果出现异常,就回滚事务并且释放资源
            JdbcUtils.rollbackAndClose();
            e.printStackTrace();
        }

        //将 订单号 放到 session域中
        request.getSession().setAttribute("orderId",orderId);
        //重定向到  /pages/cart/checkout.jsp 页面
        response.sendRedirect(request.getContextPath() + "/pages/cart/checkout.jsp");
    }
}

9.3、使用 FIlter 统一给所有的 service 方法加上 try。。catch

 我们不仅仅是给订单的业务加上try...catch ,我们希望给所有的业务都加上 try'...catch,那么有没有一种方法可以一次性的给所有的业务都加上Servlet ?

可以的,使用 Filter 过滤器。我们知道 Filter 的作用就是:调用下一个Filter 或者 资源,【Servlet 也是资源】,那么如果在 Servlet 中出现异常的话,他会继续抛给它的上级 Filter,所以我们就可以在 Filter 中统一捕捉异常。

 当我们在 Filter 中加上try..catch后, 当 service 层出现异常后,并没有回滚事务,而是提交事务,这就出现问题了。这是为什么呢?

 原因是:在 Servlet 中继承了 BaseServlet,当出现异常后,他会抛给BaseServlet,但是呢,BaseServlet 却是可以捕捉异常,那么在 Filter  中就会捕捉不到异常了。当然会出问题了,所以我们在 BaseServlet 中也要把异常抛给 Filter。

现在出现异常就会回滚事务,不会提交了。

 Filter :

public class TransactionFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        // Filter 中统一处理异常。
        try {
            filterChain.doFilter(servletRequest,servletResponse);
            JdbcUtils.commitAndClose(); //提交事务
        } catch (Exception e) {
            JdbcUtils.rollbackAndClose(); //回滚事务
            e.printStackTrace();
        }
    }

    @Override
    public void destroy() {

    }
}

web.xml:

    <filter>
        <filter-name>TransactionFilter</filter-name>
        <filter-class>yangzhaoguang.filter.TransactionFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>TransactionFilter</filter-name>
        <!--/*  表示拦截本工程下所有的资源-->
        <url-pattern>/*</url-pattern>
    </filter-mapping>

 9.4、使用 Tomcat 统一管理异常,展示友好的错误页面

 出现异常时,我们虽然在后台处理了,不会影响数据。但是在页面中,并没有展示出来,还是一片空白,用户看见了根本不知道是啥,所以我们希望能够展示错误页面提示用户。

准备好俩个错误页面,一个用于500提示,一个用于 404提示。

错误页面可以再web.xml 文件中配置:

 <error-page>
        <!--错误类型-->
        <error-code>500</error-code>
        <!--错误页面路径-->
        <location>/pages/error/error500.jsp</location>
    </error-page>

    <error-page>
        <!--错误类型-->
        <error-code>404</error-code>
        <!--错误页面路径-->
        <location>/pages/error/error404.jsp</location>
    </error-page>

最后一定要在 TransactionFilter  中把异常抛出去,不然服务器接收不到异常,就不会跳转错误页面。

第十阶段:使用 Ajax 验证用户名是否可用 

 

 UserServlet :

    // 使用 Ajax 验证用户名是否可用
    protected void ajaxExistUsername(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //获取参数
        String username = request.getParameter("username");
        boolean existsUsername = userService.existsUsername(username);
        // 将结果封装成 Map 集合
        Map<String, Object> jsonMap = new HashMap<>();
        jsonMap.put("existsUsername",existsUsername);
        Gson gson = new Gson();
        // 将 map 转换成 json 字符串
        String json = gson.toJson(jsonMap);
        //响应到浏览器中
        response.getWriter().write(json);

    }

regist.jsp 页面:

            // 使用 Ajax 验证用户名是否可用
            $("#username").blur(function () {
                //获取参数
                var username = this.value;
                // 发送请求
                $.getJSON("http://localhost:8080/book/user","action=ajaxExistUsername&username="+username,function (data) {
                    if (data.existsUsername){
                        $("span.errorMsg").text("用户名已存在");
                    } else{
                        $("span.errorMsg").text(" √ 用户名可用");
                    }
                });
            });

使用 Ajax 修改增加购物车: 

CartServlet :

 //使用 Ajax 增加购物车
    protected void ajaxAddItem(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //1、获取图书的id
        int id = WebUtils.parseInt(request.getParameter("id"), 0);
        //2、根据 id 查询图书的信息
        Book book = bookService.queryForByID(id);
        //3、将图书信息转换为 CartItems
        CartItems cartItems = new CartItems(book.getId(), book.getName(), 1, book.getPrice(), book.getPrice());
        //4、将 购物车cart  放到 session域中,购物车只能有一个。
        //以下 代码保证了:自始至终只用了一个购物车。这样才能对相同的图书进行计数。
        Cart cart = (Cart) request.getSession().getAttribute("cart");
        if (cart == null) {
            //创建购物车
            cart = new Cart();
            //放到 session 域中
            request.getSession().setAttribute("cart", cart);
        }
        //5、增加到购物车
        cart.addItem(cartItems);
        System.out.println("购物车的情况:"+cart);
        System.out.println("Referer 保存的地址:" + request.getHeader("Referer"));



        // 使用 Ajax 响应到 jsp页面。
        Map<String,Object> jsonMap = new HashMap<>();
        //一般是页面需要什么,就将数据通过数据流返回给页面
        jsonMap.put("totalCount",cart.getTotalCount());
        jsonMap.put("lastItem",cartItems.getName());
        Gson gson = new Gson();
        String mapToJsonString = gson.toJson(jsonMap);
        response.getWriter().write(mapToJsonString);

        // 将最后一次放到购物车中的商品 增加到 session 域中
        request.getSession().setAttribute("lastItem",cartItems.getName());
    }

 index.jsp 页面:

    <script type="text/javascript">
        $(function () {
            //为加入购物车按钮绑上点击事件。
            $(".addItem").click(function () {
                //获取到 图书的id
                //this 表示正在响应的DOM对象。就是 button 标签对象
                var id = $(this).attr("bookId");
                //跳转到 cartServlet
                // window.location = "cartServlet?action=addItem&id=" + id;

                // 使用 Ajax 实现增加购物车
                $.getJSON("cartServlet","action=ajaxAddItem&id="+id,function (data) {
                    $(".lastName").text(data.lastItem);
                    $("#cartTotalCount").text("您的购物车中有"+ data.totalCount +"件商品");
                });

            });
        });
    </script>

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐