超市订单管理系统讲解(SMBMS)


SMBMS超市订单管理系统(一)项目搭建准备工作

1 项目架构

image-20210727214114175

image-20210727214122729

2 数据库设计

image-20210727214438959

image-20210727214448955

3 项目搭建

3.1 项目如何搭建?

  • 是否使用maven?

    • 使用maven要去网上找依赖
    • 不使用maven要自己手动导jar包

    为了方便,这个项目使用maven搭建

3.2 创建项目

  • 使用maven模板创建一个maven项目

image-20210727214640081

image-20210727214705323

image-20210727214719599

3.2.1 补全maven项目结构

image-20210727214823622

3.2.2 更新WEB.XML的配置版本

<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                      http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0"
         metadata-complete="true">
</web-app>

3.2.3 清理pom.xml

只需要保留GAV+项目的打包方式即可

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.thhh</groupId>
  <artifactId>smbms</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>

</project>

3.3 配置Tomcat

image-20210727215116822

image-20210727215130954

image-20210727215140643

image-20210727215153102

4 测试项目是否搭建成功

image-20210727215227862

image-20210727215256215

测试通过!项目结构搭建完成!!!

5 导入依赖

用什么导入什么,不一定要一次性全部导入
  <dependencies>
    <!--servlet依赖-->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>servlet-api</artifactId>
      <version>2.5</version>
    </dependency>
    <!--JSP依赖-->
    <dependency>
      <groupId>javax.servlet.jsp</groupId>
      <artifactId>jsp-api</artifactId>
      <version>2.2.1-b03</version>
    </dependency>
    <!--数据库连接依赖-->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.47</version>
    </dependency>
    <!--JSTL标签-->
    <dependency>
      <groupId>javax.servlet.jsp.jstl</groupId>
      <artifactId>jstl-api</artifactId>
      <version>1.2</version>
    </dependency>
    <!--JSTL标签的依赖-->
    <dependency>
      <groupId>taglibs</groupId>
      <artifactId>standard</artifactId>
      <version>1.1.2</version>
    </dependency>
  </dependencies>

6 创建项目包结构

  • 首先创建一个总的包,命名"com.公司名"
  • 再创建这个项目会使用的包

image-20210727215628509

7 创建实体类/JavaBean/ORM映射

  • 使用IDEA连接数据库,按照数据库中的表的结构来创建实体类

image-20210727215834141

image-20210727215858312

image-20210727215914500

image-20210727215930522

数据库中有5张表,但是地址表没有什么用处,所以我们暂时不创建地址表对用的实体类

image-20210727220022325

image-20210727220034435

8 编写数据库操作的基础公共类BaseDao

  • 数据库配置文件,这是一个资源文件,应该创建在maven项目的resources文件中
DRIVER=com.mysql.jdbc.Driver
URL=jdbc:mysql://localhost:3306/smbms?useUnicode=true&characterEncoding=utf-8
USERNAME=root
PASSWORD=123

image-20210727220309731

  • 使用静态代码块实现初始化参数
private static String DRIVER;
private static String URL;
private static String USERNAME;
private static String PASSWORD;

static {//静态代码块,在调用这个类的地方优先自动执行
    //读取配置文件
    //1、创建properties对象
    Properties properties = new Properties();
    //2、通过类加载器加载资源文件为字节输入流
    InputStream in = BaseDao.class.getClassLoader().getResourceAsStream("db.properties");
    try {
        properties.load(in);
    } catch (IOException e) {
        e.printStackTrace();
    }
    DRIVER = properties.getProperty("DRIVER");
    URL = properties.getProperty("URL");
    USERNAME = properties.getProperty("USERNAME");
    PASSWORD = properties.getProperty("PASSWORD");
}
  • 编写数据库操作的公共方法
package com.thhh.dao;
/**
 * 注意理解这个类中的方法之所以要传入这些数据库操纵对象是因为为了统一的关闭资源
 * 而传入的对象中可以都是null,具体的对象获取在方法里面进行;也可以只有conn实例化,其他对象的实例化同样放在具体的方法里进行
 */

import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;

//操作数据库的公共类
public class BaseDao {
    private static String DRIVER;
    private static String URL;
    private static String USERNAME;
    private static String PASSWORD;

    //静态代码块用于初始化JDBC4大参数,且静态代码块只会在第一次调用这个类的时候执行一次
    static {//静态代码块,在调用这个类的地方优先自动执行
        //读取配置文件
        //1、创建properties对象
        Properties properties = new Properties();
        //2、通过类加载器加载资源文件为字节输入流
        InputStream in = BaseDao.class.getClassLoader().getResourceAsStream("db.properties");
        try {
            properties.load(in);
        } catch (IOException e) {
            e.printStackTrace();
        }
        DRIVER = properties.getProperty("DRIVER");
        URL = properties.getProperty("URL");
        USERNAME = properties.getProperty("USERNAME");
        PASSWORD = properties.getProperty("PASSWORD");
    }

    //1、编写获取数据库的连接对象的公共方法
    public static Connection getConnection(){
        Connection conn= null;
        try {
            //1、加载驱动类
            Class.forName(DRIVER);
            //2、获取连接对象
            conn = DriverManager.getConnection(URL,USERNAME,PASSWORD);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return conn;
    }

    //2、编写查询公共方法 —— 注意查询的结果返回为ResultSet结果集
    /**
     * 用于查询数据的公共方法,注意:使用发送SQL语句的对象为PreparedStatement
     * @param sql:查询的sql语句,由前端传递
     * @param params:sql语句中占位符的值
     *
     *===============这下面的3个参数之所以在调用的时候传递原因就在于这3个都是资源,我们需要关闭,如果我们直接在这个方法里获取资源对象的话,那么我们就应该在这个方法中关闭资源===============
     *===============但是调用处还在等待这个方法返回结果集,所以我们不应该在这个地方获取这3个对象,而应该由调用出传递,这样可以统一管理和关闭资源===============
     *
     * @param conn:调用出使用BaseDao.getConnection()获取到数据库连接对象传入
     * @param pstmt:调用出只是传入null的引用。这个对象真正的实例化放在这个方法里面
     * @param rs:返回的结果集,和pstmt只是传入null的引用。这个对象真正的实例化放在这个方法里面
     *
     * @return:返回查询到的结果集
     */
    public static ResultSet executeQuery(String sql,Object[] params,Connection conn,PreparedStatement pstmt,ResultSet rs){
        try {
            pstmt = conn.prepareStatement(sql);
            for (int i=1;i<= params.length;i++){//循环遍历参数数组,并将参数设入SQL中
                pstmt.setObject(i,params[i-1]);//注意:数组的index从0开始,而PreparedStatement中设置占位符的值的index从1开始
            }
            rs = pstmt.executeQuery();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return  rs;
    }

    //3、编写修改公共方法
    /**
     * 用于修改数据的公共方法,注意:使用发送SQL语句的对象为PreparedStatement
     * @param sql:修改数据的sql语句模板
     * @param params:模板中占位符对应的值
     *
     * =====下面两个对象需要调用出传入也是为了统一管理和关闭资源=====
     * @param conn:调用出使用BaseDao.getConnection()获取到数据库连接对象传入
     * @param pstmt:调用出只是传入null的引用。这个对象真正的实例化放在这个方法里面
     *
     * @return 返回受影响的行数
     */
    public static int executeUpdate(String sql,Object[] params,Connection conn,PreparedStatement pstmt){
        int result = 0;
        try {
            pstmt = conn.prepareStatement(sql);
            for (int i=1;i< params.length;i++){//循环遍历参数数组,并将参数设入SQL中
                pstmt.setObject(i,params[i-1]);//注意:数组的index从0开始,而PreparedStatement中设置占位符的值的index从1开始
            }
            result = pstmt.executeUpdate();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return  result;
    }

    //4、编写关闭资源公共方法
    /**
     * 关闭资源
     * @param conn:调用出使用BaseDao.getConnection()获取到数据库连接对象传入
     * @param pstmt:调用出只是传入null的引用。这个对象真正的实例化放在这个方法里面
     * @param rs:返回的结果集,和pstmt只是传入null的引用。这个对象真正的实例化放在这个方法里面
     * @return:返回关闭资源的结果
     *
     * 注意:关闭资源的时候要倒着关
     */
    public static boolean close(Connection conn,PreparedStatement pstmt,ResultSet rs){
        boolean flag = true;
        if (rs!=null){
            try {
                rs.close();
                rs = null;//让这个变量为null,gc就会自动对其进行回收
            } catch (SQLException throwables) {
                throwables.printStackTrace();
                flag = false;//关闭失败就将flag设置false
            }
        }
        if (pstmt!=null){
            try {
                pstmt.close();
                pstmt = null;
            } catch (SQLException throwables) {
                throwables.printStackTrace();
                flag = false;
            }
        }
        if (conn!=null){
            try {
                conn.close();
                conn = null;
            } catch (SQLException throwables) {
                throwables.printStackTrace();
                flag = false;
            }
        }
        return flag;//返回关闭结果
    }
}

9 编写字符编码过滤器

//编写过滤器

package com.thhh.filter;

import javax.servlet.*;
import java.io.IOException;

public class CharacterEncoding implements Filter {
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        request.setCharacterEncoding("utf-8");
        response.setCharacterEncoding("utf-8");
        response.setContentType("text/html;charset=utf-8");

        chain.doFilter(request,response);
    }

    public void destroy() {

    }
}
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                      http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0"
         metadata-complete="true">
    <!--注册字符编码过滤器-->
    <filter>
        <filter-name>CharacterEncoding</filter-name>
        <filter-class>com.thhh.filter.CharacterEncoding</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>CharacterEncoding</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
</web-app>

10 导入静态资源

包括HTML、CSS、JS等,注意:这些资源都是网站的,所以我们应该将这些资源放在webapp下面

image-20210727222810093

通过以上的步骤,一个WEB项目的搭建工作就算基本完成了

SMBMS(二)登陆功能实现

页面实现逻辑/流程

image-20210728081350428

1 编写前端页面

前端页面直接使用已有的,我们主要编写网页背后的执行代码

<%@ page language="java" contentType="text/html; charset=UTF-8"
         pageEncoding="UTF-8" %>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>系统登录 - 超市订单管理系统</title>
    <link type="text/css" rel="stylesheet" href="${pageContext.request.contextPath }/css/style.css"/>
    <script type="text/javascript">
        /* if(top.location!=self.location){
              top.location=self.location;
         } */
    </script>
</head>
<body class="login_bg">
<section class="loginBox">
    <header class="loginHeader">
        <h1>超市订单管理系统</h1>
    </header>
    <section class="loginCont">
        <form class="loginForm" action="${pageContext.request.contextPath }/login.do" name="actionForm" id="actionForm"
              method="post">
            <div class="info">${error}</div>
            <div class="inputbox">
                <label for="userCode">用户名:</label>
                <input type="text" class="input-text" id="userCode" name="userCode" placeholder="请输入用户名" required/>
            </div>
            <div class="inputbox">
                <label for="userPassword">密码:</label>
                <input type="password" id="userPassword" name="userPassword" placeholder="请输入密码" required/>
            </div>
            <div class="subBtn">
                <input type="submit" value="登录"/>
                <input type="reset" value="重置"/>
            </div>
        </form>
    </section>
</section>
</body>
</html>

2 设置首页

   <!--设置欢迎页/首页-->
    <welcome-file-list>
        <welcome-file>login.jsp</welcome-file>
    </welcome-file-list>

image-20210728081735175

3 编写Dao层用户登陆的接口

  • 创建一个UserDao接口,然后我们按照"面向接口编程"的原则去接口UserDaoImpl中实现这个接口中的方法
    • 使用面向接口编程的好处就在与我们在接口中只需要定义方法,而不需要实现方法,整个结构和思路都很清晰
    • 其次就是将设计和实现分离,保证了设计专注于设计,实现专注于实现
package com.thhh.dao.user;

import com.thhh.pojo.User;

import java.sql.Connection;

public interface UserDao {
    /**
     * 得到要进行登陆的用户
     * @param conn:数据库连接对象
     * @param userCode:通过用户的用户名userCode查询用户数据
     * @return
     */
    public User getLoginUserInfo(Connection conn,String userCode);
}

4 编写Dao接口的实现类

package com.thhh.dao.user;

import com.thhh.dao.BaseDao;
import com.thhh.pojo.User;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class UserDaoImpl implements UserDao{
    @Override
    public User getLoginUserInfo(Connection conn, String userCode) {
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        User user = null;
        if (conn!=null){
            String sql = "SELECT * FROM smbms_user WHERE userCode = ?";
            Object[] params = {userCode};
            rs = BaseDao.executeQuery(sql,params,conn,pstmt,rs);//调用项目搭建阶段准备的公共查询方法
            try {
                    while (rs.next()){
                        user = new User();
                        user.setId(rs.getInt("id"));
                        user.setUserCode(rs.getString("userCode"));
                        user.setUserName(rs.getString("userName"));
                        user.setUserPassword(rs.getString("userPassword"));
                        user.setGender(rs.getInt("gender"));
                        user.setBirthday(rs.getDate("birthday"));
                        user.setPhone(rs.getString("phone"));
                        user.setAddress(rs.getString("address"));
                        user.setUserRole(rs.getInt("userRole"));
                        user.setCreatedBy(rs.getInt("createdBy"));
                        user.setCreationDate(rs.getTimestamp("creationDate"));
                        user.setModifyBy(rs.getInt("modifyBy"));
                        user.setModifyDate(rs.getTimestamp("modifyDate"));user.setId(rs.getInt("id"));
                        user.setUserCode(rs.getString("userCode"));
                        user.setUserName(rs.getString("userName"));
                        user.setUserPassword(rs.getString("userPassword"));
                        user.setGender(rs.getInt("gender"));
                        user.setBirthday(rs.getDate("birthday"));
                        user.setPhone(rs.getString("phone"));
                        user.setAddress(rs.getString("address"));
                        user.setUserRole(rs.getInt("userRole"));
                        user.setCreatedBy(rs.getInt("createdBy"));
                        user.setCreationDate(rs.getTimestamp("creationDate"));
                        user.setModifyBy(rs.getInt("modifyBy"));
                        user.setModifyDate(rs.getTimestamp("modifyDate"));
                    }

                //关闭资源
                BaseDao.close(null,pstmt,rs);//因为数据库的连接可能不只是这一个操作,所以我们不应该做完一件事就把数据库连接对象销毁,所以conn处传的null
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
        return user;
    }
}

5 编写业务层接口

package com.thhh.service.user;

import com.thhh.pojo.User;

public interface UserService {
    /**
     * 用户登陆身份验证
     * @param userCode:用户账号
     * @param userPassword:用户密码,注意,密码判断我们在service层进行;
     *                      在Dao层只是简单的操作数据库,没有其他的逻辑代码;在servlet层中只是接收和转发请求以及控制视图跳转
     *                      而对于业务层(service)就是用来实现业务逻辑代码的
     * @return
     */
    public User login(String userCode,String userPassword);
}

6 业务层实现类

package com.thhh.service.user;
/**
 * 业务层主要就是编写业务代码,在编写业务代码的时候经常会调用数据库
 * 所以在业务层中需要使用到我们一开始编写好的DAO的代码
 */

import com.thhh.dao.BaseDao;
import com.thhh.dao.user.UserDao;
import com.thhh.pojo.User;
import org.junit.Test;

import java.sql.Connection;

public class UserServiceImpl implements UserService{
    private UserDao userDao;//业务层需要使用Dao,所以直接将Dao作为一个成员变量来使用

    public UserServiceImpl(UserDao userDao) {
        this.userDao = userDao;//在业务层被实例化的时候就让它得到Dao对象,后面就可以直接去用
    }

    @Override
    public User login(String userCode, String userPassword) {
        Connection conn = null;
        User user = null;

        conn = BaseDao.getConnection();//获取数据库连接对象
        //通过业务层调用Dao层
        user = userDao.getLoginUserInfo(conn,userCode);//调用userDao中的获取用户信息的方法
        BaseDao.close(conn,null,null);

        return user;
    }
}

测试是否能够成功运行

@Test
public void test(){
    UserServiceImpl userService = new UserServiceImpl();
    User admin = userService.login("admin","12bfasbka");
    System.out.println(admin.getUserPassword());
}

image-20210728083431455

7 编写servlet

首先导入我们也用到的JSP页面

image-20210728083528696

其中frame.jsp是登陆成功之后跳转的页面,而common中是页面的头部和底部

  1. frame.jsp
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@include file="/jsp/common/head.jsp"%>
<div class="right">
    <img class="wColck" src="${pageContext.request.contextPath }/images/clock.jpg" alt=""/>
    <div class="wFont">
        <h2>${userSession.userName }</h2>
        <p>欢迎来到超市订单管理系统!</p>
    </div>
</div>
</section>
<%@include file="/jsp/common/foot.jsp" %>
  1. head.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
   pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>超市订单管理系统</title>
    <link type="text/css" rel="stylesheet" href="${pageContext.request.contextPath }/css/style.css" />
    <link type="text/css" rel="stylesheet" href="${pageContext.request.contextPath }/css/public.css" />
</head>
<body>
<!--头部-->
    <header class="publicHeader">
        <h1>超市订单管理系统</h1>
        <div class="publicHeaderR">
            <p><span>下午好!</span><span style="color: #fff21b"> ${userSession.userName }</span> , 欢迎你!</p>
            <a href="${pageContext.request.contextPath }/jsp/logout.do">退出</a>
        </div>
    </header>
<!--时间-->
    <section class="publicTime">
        <span id="time">2015年1月1日 11:11  星期一</span>
        <a href="#">温馨提示:为了能正常浏览,请使用高版本浏览器!(IE10+)</a>
    </section>
 <!--主体内容-->
 <section class="publicMian ">
     <div class="left">
         <h2 class="leftH2"><span class="span1"></span>功能列表 <span></span></h2>
         <nav>
             <ul class="list">
                 <li ><a href="${pageContext.request.contextPath }/jsp/bill.do?method=query">订单管理</a></li>
              <li><a href="${pageContext.request.contextPath }/jsp/provider.do?method=query">供应商管理</a></li>
              <li><a href="${pageContext.request.contextPath }/jsp/user.do?method=query">用户管理</a></li>
              <li><a href="${pageContext.request.contextPath }/jsp/pwdmodify.jsp">密码修改</a></li>
              <li><a href="${pageContext.request.contextPath }/jsp/logout.do">退出系统</a></li>
             </ul>
         </nav>
     </div>
     <input type="hidden" id="path" name="path" value="${pageContext.request.contextPath }"/>
     <input type="hidden" id="referer" name="referer" value="<%=request.getHeader("Referer")%>"/>
  1. foot.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>

<footer class="footer">
    超市订单管理系统
</footer>
<script type="text/javascript" src="${pageContext.request.contextPath }/js/time.js"></script>
<script type="text/javascript" src="${pageContext.request.contextPath }/js/jquery-1.8.3.min.js"></script>
<script type="text/javascript" src="${pageContext.request.contextPath }/js/common.js"></script>
<script type="text/javascript" src="${pageContext.request.contextPath }/calendar/WdatePicker.js"></script>
</body>
</html>

注意:我们在编写servlet和注册servlet的时候一定要注意我们使用的前端页面上面写的地址,为了不出错最后时与前端页面上写的地址保持一致;其次就是注意前端使用的JSP内置对象和EL表达式,我们在后端需要使用对应的变量名,否则是取不到值的

7.1 servlet编写

package com.thhh.servlet.user;

import com.thhh.pojo.User;
import com.thhh.service.user.UserService;
import com.thhh.service.user.UserServiceImpl;
import com.thhh.utils.Constants;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 控制层调用业务层
 */
public class LoginServlet extends HttpServlet{
    private UserService userService = null;

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //1、获取前端传过来的用户名+密码
        String userCode = req.getParameter("userCode");
        String userPassword = req.getParameter("userPassword");

        //2、调用业务层,将数据库中查出来的密码和用户的输入进行对比
        this.userService = new UserServiceImpl();
        User user = userService.login(userCode,userPassword);//已经把用户查到了

        //3、判断返回是否为null
        if (user!=null){//账号+密码正确
            //将用户的信息存入session中
            req.getSession().setAttribute(Constants.USER_SESSION,user);
            //跳转内部主页
            resp.sendRedirect("jsp/frame.jsp");
        }else{//账号+密码不正确
            req.setAttribute("error","用户名或密码错误");
            req.getRequestDispatcher("login.jsp").forward(req,resp);
        }


      /*  if ()
        user.getUserPassword()*/
    }

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

8 注册servlet

<!--注册登陆的servlet-->
<servlet>
    <servlet-name>LoginServlet</servlet-name>
    <servlet-class>com.thhh.servlet.user.LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>LoginServlet</servlet-name>
    <url-pattern>/login.do</url-pattern>
</servlet-mapping>

注意:这里的映射与我们使用的前端页面表单提交的action要保持一致

image-20210728084658253

image-20210728084759250

image-20210728084836151

image-20210728084849243

image-20210728084859406

9 测试功能,确保上面的代码正确

image-20210728084935867

image-20210728084948330

image-20210728085004111

image-20210728085016202

image-20210728085031551

测试完成!

SMBMS(三) 登陆功能优化

1 注销功能

思路:移除session对象+返回登陆页面

1.1 编写servlet

package com.thhh.servlet.user;

import com.thhh.utils.Constants;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class LogoutServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.getSession().removeAttribute(Constants.USER_SESSION);//移除用户session
        resp.sendRedirect(req.getContextPath()+"/login.jsp");//重定向回登陆页面
    }

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

image-20210728085711416

1.2 注册servlet

<!--注册注销servlet-->
<servlet>
    <servlet-name>LogoutServlet</servlet-name>
    <servlet-class>com.thhh.servlet.user.LogoutServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>LogoutServlet</servlet-name>
    <url-pattern>/jsp/logout.do</url-pattern>
</servlet-mapping>

image-20210728085803304

image-20210728085830375

2 登陆拦截优化

image-20210728095704640

  1. 编写过滤器
package com.thhh.filter;

import com.thhh.pojo.User;
import com.thhh.utils.Constants;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

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

    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest)req;//父类强转子类,用于获取session
        HttpServletResponse response = (HttpServletResponse) resp;//父类强转子类,用于重定向
        User user = (User) request.getSession().getAttribute(Constants.USER_SESSION);

        if (user==null){//用户处于未登陆状态
            response.sendRedirect(request.getContextPath()+"/error.jsp");//重定向到登陆页面
        }else{
            chain.doFilter(req,resp);//过滤器放行
        }
    }

    @Override
    public void destroy() {

    }
}
  1. 注册过滤器
<!--注册未登录时请求后台页面过滤器-->
<filter>
    <filter-name>SysFilter</filter-name>
    <filter-class>com.thhh.filter.SysFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>SysFilter</filter-name>
    <url-pattern>/jsp/*</url-pattern>
    <!--注意:我们将所有需要登陆之后才能访问的功能性页面给都放在了/jsp文件夹下,所以过滤器应该过滤这个文件夹下的请求-->
</filter-mapping>
  1. 导入error页面

image-20210728100108738

image-20210728101929896

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

<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Insert title here</title>
</head>
<body>
<%--你要访问的页面,已经飞往火星!--%>
<%--<a href="javascript:window.history.back(-1);">返回</a>--%>
<h1>请登录后再访问该页面!</h1>
<a href="login.jsp">返回</a>
</body>
</html>
  1. 测试功能

image-20210728102133489

image-20210728102150005

测试完成!

SMBMS (四) 修改密码

分析:很明显,要修改用户密码我们还是需要和数据库交互,那么就还是前面我们写登陆功能的代码编写步骤 —— DAO层、service层、servlet层,前端页面直接使用现成的,但是注意servlet中使用的地址和servlet的地址映射注意和前端页面保持一致

为什么要按照DAO层、service层、servlet层,JSP页面的顺序来编写呢?

image-20210728102825343

原因在上图展示的很清楚,开发JSP需要填写servlet在服务器上的映射路径,开发servlet需要调用service中的方法完成业务逻辑,开发service需要调用Dao中对数据库的操作来操作数据库,而只有Dao中使用的JDBC我们是数据库厂商实现了的,所以我们可以直接使用;所以为了开发的完整性,我们就应该从Dao开始-->service-->servlet-->JSP

分析实现步骤/模块功能划分(很重要)

只有我们先想好了怎么做,然后再去编写代码才会快,且有条不紊,切忌看完要求之后马上开始写代码

image-20210728103005202

1 导入前端素材

image-20210728103101073

image-20210728103111927

<%@ page language="java" contentType="text/html; charset=UTF-8"
   pageEncoding="UTF-8"%>
<%@include file="/jsp/common/head.jsp"%>
<div class="right">
            <div class="location">
                <strong>你现在所在的位置是:</strong>
                <span>密码修改页面</span>
            </div>
            <div class="providerAdd">
                <form id="userForm" name="userForm" method="post" action="${pageContext.request.contextPath }/jsp/user.do">
                    <input type="hidden" name="method" value="savepwd">
                    <!--div的class 为error是验证错误,ok是验证成功-->
                    <div class="info">${message}</div>
                    <div class="">
                        <label for="oldPassword">旧密码:</label>
                        <input type="password" name="oldpassword" id="oldpassword" value=""> 
                  <font color="red"></font>
                    </div>
                    <div>
                        <label for="newPassword">新密码:</label>
                        <input type="password" name="newpassword" id="newpassword" value=""> 
                  <font color="red"></font>
                    </div>
                    <div>
                        <label for="newPassword">确认新密码:</label>
                        <input type="password" name="rnewpassword" id="rnewpassword" value=""> 
                  <font color="red"></font>
                    </div>
                    <div class="providerAddBtn">
                        <!--<a href="#">保存</a>-->
                        <input type="button" name="save" id="save" value="保存" class="input-button">
                    </div>
                </form>
            </div>
        </div>
    </section>
<%@include file="/jsp/common/foot.jsp" %>
<script type="text/javascript" src="${pageContext.request.contextPath }/js/pwdmodify.js"></script>

2 Dao接口

package com.thhh.dao.user;

import com.thhh.pojo.User;

import java.sql.Connection;

public interface UserDao {
    /**
     * 得到要进行登陆的用户
     * @param conn:数据库连接对象
     * @param userCode:通过用户的用户名userCode查询用户数据
     * @return
     */
    public User getLoginUserInfo(Connection conn,String userCode);

    /**
     * 修改用户密码
     * @param conn:数据库连接对象
     * @param id:修改密码的用户的ID
     * @param newPwd:新密码
     * @return:影响行数
     */
    public int updatePwd(Connection conn,String newPwd,int id);
}

只需要看方法2

3 Dao接口实现

package com.thhh.dao.user;

import com.thhh.dao.BaseDao;
import com.thhh.pojo.User;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class UserDaoImpl implements UserDao{
    //1、获取要进行登陆的用户对象
    @Override
    public User getLoginUserInfo(Connection conn, String userCode) {
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        User user = null;
        if (conn!=null){
            String sql = "SELECT * FROM smbms_user WHERE userCode = ?";
            Object[] params = {userCode};
            rs = BaseDao.executeQuery(sql,params,conn,pstmt,rs);//调用项目搭建阶段准备的公共查询方法
            try {
                    while (rs.next()){
                        user = new User();
                        user.setId(rs.getInt("id"));
                        user.setUserCode(rs.getString("userCode"));
                        user.setUserName(rs.getString("userName"));
                        user.setUserPassword(rs.getString("userPassword"));
                        user.setGender(rs.getInt("gender"));
                        user.setBirthday(rs.getDate("birthday"));
                        user.setPhone(rs.getString("phone"));
                        user.setAddress(rs.getString("address"));
                        user.setUserRole(rs.getInt("userRole"));
                        user.setCreatedBy(rs.getInt("createdBy"));
                        user.setCreationDate(rs.getTimestamp("creationDate"));
                        user.setModifyBy(rs.getInt("modifyBy"));
                        user.setModifyDate(rs.getTimestamp("modifyDate"));user.setId(rs.getInt("id"));
                        user.setUserCode(rs.getString("userCode"));
                        user.setUserName(rs.getString("userName"));
                        user.setUserPassword(rs.getString("userPassword"));
                        user.setGender(rs.getInt("gender"));
                        user.setBirthday(rs.getDate("birthday"));
                        user.setPhone(rs.getString("phone"));
                        user.setAddress(rs.getString("address"));
                        user.setUserRole(rs.getInt("userRole"));
                        user.setCreatedBy(rs.getInt("createdBy"));
                        user.setCreationDate(rs.getTimestamp("creationDate"));
                        user.setModifyBy(rs.getInt("modifyBy"));
                        user.setModifyDate(rs.getTimestamp("modifyDate"));
                    }

                //关闭资源
                BaseDao.close(null,pstmt,rs);//因为数据库的连接可能不只是这一个操作,所以我们不应该做完一件事就把数据库连接对象销毁,所以conn处传的null
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
        return user;
    }

    //2、修改用户密码
    @Override
    public int updatePwd(Connection conn, String newPwd, int id) {
        PreparedStatement pstmt = null;
        int rs = 0;
        User user = null;
        if (conn!=null){
            String sql = "UPDATE smbms_user SET userPassword = ? WHERE id = ?";
            Object[] params = {newPwd,id};//按照sql语句的占位符的顺序来传递数据,使用的时候需要注意
            rs = BaseDao.executeUpdate(sql,params,conn,pstmt);
            BaseDao.close(null,pstmt,null);//把这次使用的sql语句发送器关掉,连接不要关,service还可能有其他用
        }
        return rs;
    }
}

4 service接口

package com.thhh.service.user;

import com.thhh.pojo.User;

import java.sql.Connection;

public interface UserService {
    /**
     * 1、获取登陆用户对象,对用户登陆身份进行验证
     * @param userCode:用户账号
     * @param userPassword:用户密码,注意,密码判断我们在service层进行;
     *                      在Dao层只是简单的操作数据库,没有其他的逻辑代码;在servlet层中只是接收和转发请求以及控制视图跳转
     *                      而对于业务层(service)就是用来实现业务逻辑代码的
     * @return
     */
    public User login(String userCode,String userPassword);

    /**
     * 2、根据用户ID修改用户密码
     * @param newPwd:新密码
     * @param id:用户ID
     * @return
     */
    public boolean updatePwd(String newPwd, int id);
}

只看方法2

5 service接口实现

package com.thhh.service.user;
/**
 * 业务层主要就是编写业务代码,在编写业务代码的时候经常会调用数据库
 * 所以在业务层中需要使用到我们一开始编写好的DAO的代码
 */

import com.thhh.dao.BaseDao;
import com.thhh.dao.user.UserDao;
import com.thhh.dao.user.UserDaoImpl;
import com.thhh.pojo.User;

import java.sql.Connection;

public class UserServiceImpl implements UserService{
    private UserDao userDao;//业务层需要使用Dao,所以直接将Dao作为一个成员变量来使用

    public UserServiceImpl() {
        this.userDao = new UserDaoImpl();//在业务层被实例化的时候就让它得到Dao对象,后面就可以直接去用
    }

    /**
     * 1、判断登陆用户的用户名+密码是否合法,并将用户对象返回
     * @param userCode:用户账号
     * @param userPassword:用户密码,注意,密码判断我们在service层进行;
     *                      在Dao层只是简单的操作数据库,没有其他的逻辑代码;在servlet层中只是接收和转发请求以及控制视图跳转
     *                      而对于业务层(service)就是用来实现业务逻辑代码的
     * @return
     */
    @Override
    public User login(String userCode, String userPassword) {
        Connection conn = null;
        User user = null;
        User error = null;

        conn = BaseDao.getConnection();//获取数据库连接对象
        //通过业务层调用Dao层
        user = userDao.getLoginUserInfo(conn,userCode);//调用userDao中的获取用户信息的方法
        BaseDao.close(conn,null,null);
        if (user.getUserPassword().equals(userPassword)){
            return user;
        }

        return error;
    }

    /**
     *  2、通过已经登陆用户的ID修改新密码,并将数据库中受影响的行数返回
     * @param newPwd:新密码
     * @param id:用户ID
     * @return
     */
    @Override
    public boolean updatePwd(String newPwd, int id) {
        Connection conn = null;
        int rs = 0;
        boolean flag = false;

        conn = BaseDao.getConnection();//获取数据库连接对象
        //通过业务层调用Dao层
        if (userDao.updatePwd(conn,newPwd,id)>0){//数据库修改成功
            flag = true;
        }
        BaseDao.close(conn,null,null);

        return flag;
    }
}

只看方法2

6 servlet编写

package com.thhh.servlet.user;

import com.mysql.jdbc.StringUtils;
import com.thhh.pojo.User;
import com.thhh.service.user.UserService;
import com.thhh.service.user.UserServiceImpl;
import com.thhh.utils.Constants;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

//实现servlet复用
public class UserServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        boolean flag = false;

        Object user = req.getSession().getAttribute(Constants.USER_SESSION);
        String newpassword = req.getParameter("newpassword");

        if (user!=null && !StringUtils.isNullOrEmpty(newpassword)){//获取到了这个用户对象且获取到的新密码不为空
            UserService userService = new UserServiceImpl();
            flag = userService.updatePwd(newpassword,((User)user).getId());//servlet调用业务层
            if (flag){//修改成功
                req.setAttribute("message","密码修改成功!请使用新密码重新登陆");
                //移除用户的session,利用过滤器阻止用户再进行操作,直接跳转error.jsp页面
                req.getSession().removeAttribute(Constants.USER_SESSION);
            }else{
                req.setAttribute("message","密码修改失败");
            }
        }else {
            //用户可以进行密码修改,则user一定不是null,所以跳入这个分支的原因一定是newpassword = NULL
            req.setAttribute("message","密码设置有误,请重新输入!");
        }
        //无论是修改成功还是失败,都重定向到密码修改页面,就是在刷新页面,否则我们设置在req中的message属性不会被前端读到
        req.getRequestDispatcher("pwdmodify.jsp").forward(req,resp);
    }

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

7 注册servlet

<!--注册用户修改密码的servlet-->
<servlet>
    <servlet-name>UserServlet</servlet-name>
    <servlet-class>com.thhh.servlet.user.UserServlet</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>UserServlet</servlet-name>
    <url-pattern>/jsp/user.do</url-pattern>
</servlet-mapping>

8 测试

bug 1:

//1、在编写servlet的时候,要判断前端传过来的新密码是否为空,这里ZB用了一个工具类,但是这个工具类是isNullOrEmpty,即它的作用判断"是空",所以使用的时候注意在前面加上一个"!"
//或者我们就是要常见的方法:newpassword!=null&&newpassword.length!=0

if (user!=null && !StringUtils.isNullOrEmpty(newpassword))

bug 2:

//2、在编写BaseDao即基本公共数据库操作方法的时候,设置PreparedStatement对象中sql占位符的值时要注意
//PreparedStatement的占位符index从1开始,而数组的下标从0开始,所以我们使用的i=1,但是要注意控制循环次数的时候使用的是params.length,所以我们需要取"=",否则数组中的参数是取不完的,取不完就会出现SQL错误

for (int i=1;i<= params.length;i++){//循环遍历参数数组,并将参数设入SQL中
    pstmt.setObject(i,params[i-1]);//注意:数组的index从0开始,而PreparedStatement中设置占位符的值的index从1开始
}

bug 3:

//前端页面上,编写的时候要求输入旧密码,但是实际测试的时候输入旧密码有BUG,我们直接不使用输入旧密码,使用新密码+重复新密码输入框来修改密码
//但是前端使用的JS控制了提交表单的按钮,即需要3个输入框输入都满足要求的时候才能提交表单数据,所以我们需要把判断旧密码输入框的判断语句注释了
//这样才能只通过新密码+重复新密码实现密码修改

saveBtn.on("click",function(){
   oldpassword.blur();
   newpassword.blur();
   rnewpassword.blur();
   // oldpassword.attr("validateStatus") == "true"
   // &&
   if( newpassword.attr("validateStatus") == "true"
      && rnewpassword.attr("validateStatus") == "true"){
      if(confirm("确定要修改密码?")){
         $("#userForm").submit();
      }
   }
   
});

image-20210728110259171

image-20210728110323946

image-20210728110334159

image-20210728110349073

9 优化servlet代码,实现servlet复用

image-20210728110433333

image-20210728110525472

通过测试,功能完全相同,且修改密码正确!

SMBMS (五) 使用Ajax优化密码修改功能

在前面的密码修改中,我们避开使用了前端素材中验证旧密码的Ajax功能,是因为要把Ajax单独拿出来讲

​ 前面实现的密码修改功能是直接输入两遍新密码进行的修改,这显然是不安全的,所以我们应该在修改密码的时候加入验证旧密码的操作,而这个操作根据前端素材就需要使用到Ajax了

1 什么是Ajax

  1. AJAX = Asynchronous JavaScript and XML(异步的 JavaScript 和 XML)
  2. AJAX 不是新的编程语言,而是一种使用现有标准的新方法
  3. AJAX 是在不重新加载整个页面的情况下,与服务器交换数据并更新部分网页的艺术
    • 通过在后台与服务器进行少量数据交换,AJAX 可以使网页实现异步更新
    • 这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新
  4. 传统的网页(不使用 AJAX)如果需要更新内容,必需重载整个网页面

2 怎么实现旧密码验证?

​ 直接的做法就是去数据库中查找,但是这样的画我们又要写一条线:Dao开始-->service-->servlet-->JSP

​ 我们可以采取一种很简单的方式进行:在用户登陆的时候我们将整个用户对象都存储到session中了,所以我们可以利用Ajax的异步请求+session中的user对象实现旧密码的验证

​ 做法就是在后端复用的servlet中再定义一个方法,这个方法专门负责对比Ajax传递给后端的旧密码是否和session中User对象的密码一致,并通过对比的情况和结果来返回JSON数据给前端

3 编写servlet

oldpassword.on("blur",function(){
   $.ajax({
      type:"GET",
      url:path+"/jsp/user.do",//Ajax提交的URL,可见和我们修改密码的JSP页面的表单提交的URL一样,所以我们应该复用servlet
      data:{method:"pwdmodify",oldpassword:oldpassword.val()},//提交的参数
      //上面两句话等价于 = path+/jsp/user.do ? method=pwdmodify & oldpassword=oldpassword.val()

           dataType:"json",
      success:function(data){
         if(data.result == "true"){//旧密码正确
            validateTip(oldpassword.next(),{"color":"green"},imgYes,true);
         }else if(data.result == "false"){//旧密码输入不正确
            validateTip(oldpassword.next(),{"color":"red"},imgNo + " 原密码输入不正确",false);
         }else if(data.result == "sessionerror"){//当前用户session过期,请重新登录
            validateTip(oldpassword.next(),{"color":"red"},imgNo + " 当前用户session过期,请重新登录",false);
         }else if(data.result == "error"){//旧密码输入为空
            validateTip(oldpassword.next(),{"color":"red"},imgNo + " 请输入旧密码",false);
         }
      },
      error:function(data){
         //请求出错
         validateTip(oldpassword.next(),{"color":"red"},imgNo + " 请求错误",false);
      }
   });

image-20210728112329887

    //2.验证旧密码
    //直接与session中的user对象的密码进行对比即可,不用再去查数据库
    public void pwdModify(HttpServletRequest req, HttpServletResponse resp){
        Object user = req.getSession().getAttribute(Constants.USER_SESSION);
        String oldpassword = req.getParameter("oldpassword");

        //使用Map集合存储返回给前端的数据
        Map<String,String> resultMap = new HashMap<String, String>();

        if (user==null){//session中的user对象失效了
            resultMap.put("result","sessionerror");
        }else if (StringUtils.isNullOrEmpty(oldpassword)){//输入的密码为空
            resultMap.put("result","error");
        }else {
            String userPassword = ((User)user).getUserPassword();
            if (userPassword.equals(oldpassword)){//输入的密码匹配
                resultMap.put("result","true");
            }else {//输入的密码不匹配
                resultMap.put("result","false");
            }
        }

        //将Map集合中的数据转为JSON格式传输给前端
        try {
            resp.setContentType("application/JSON");//设置返回数据是一个JSON,这样浏览器拿到这个数据之后就会按照JSON的数据格式来解析它
            PrintWriter writer = resp.getWriter();//获取输出流
            writer.write(JSONArray.toJSONString(resultMap));//使用阿里巴巴提供的一个JSON工具类,直接将其他数据格式的数据转为JSON数据格式,然后直接将其写出去
            writer.flush();//刷新缓冲区
            writer.close();//关闭资源
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

4 测试

image-20210728112604319

image-20210728112614946

image-20210728112700836

image-20210728112708880

image-20210728112716321

(依旧爱ZS)

SMBMS (六) 用户管理功能实现

image-20210728112844929

image-20210728112938001

1 导入分页工具类

image-20210728113008932

image-20210728113049320

查看一下这个工具类的源码

package com.lvcong.util;

public class PageSupport {
    //当前页码-来自于用户输入
    private int currentPageNo = 1;

    //总数量(表)
    private int totalCount = 0;

    //页面容量
    private int pageSize = 0;

    //总页数-totalCount/pageSize(+1)
    private int totalPageCount = 1;

    public int getCurrentPageNo() {
        return currentPageNo;
    }

    public void setCurrentPageNo(int currentPageNo) {
        if (currentPageNo > 0) {
            this.currentPageNo = currentPageNo;
        }
    }

    public int getTotalCount() {
        return totalCount;
    }

    public void setTotalCount(int totalCount) {
        if (totalCount > 0) {
            this.totalCount = totalCount;
            //设置总页数
            this.setTotalPageCountByRs();
        }
    }

    public int getPageSize() {
        return pageSize;
    }

    public void setPageSize(int pageSize) {
        if (pageSize > 0) {
            this.pageSize = pageSize;
        }
    }

    public int getTotalPageCount() {
        return totalPageCount;
    }

    public void setTotalPageCount(int totalPageCount) {
        this.totalPageCount = totalPageCount;
    }

    public void setTotalPageCountByRs() {
        if (this.totalCount % this.pageSize == 0) {
            this.totalPageCount = this.totalCount / this.pageSize;
        } else if (this.totalCount % this.pageSize > 0) {
            this.totalPageCount = this.totalCount / this.pageSize + 1;
        } else {
            this.totalPageCount = 0;
        }
    }

}

image-20210728113203920

image-20210728113344103

OOP的3大特性:封装、继承、多态,其中封装 = 属性私有+属性的get/set() + 在set中限制一些不安全的赋值操作(这一步可以留到service层再做,但是在封装的时候做更好,这样减少了service层的代码,且体现了封装的特性)

2 用户列表页面导入

首先是HTML文件

image-20210728113507808

image-20210728113600903

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
<script type="text/javascript">
   
</script>
</head>
<body>
      <div class="page-bar">
         <ul class="page-num-ul clearfix">
            <li>共${param.totalCount }条记录&nbsp;&nbsp; ${param.currentPageNo }/${param.totalPageCount }页</li>
            <c:if test="${param.currentPageNo > 1}">
               <a href="javascript:page_nav(document.forms[0],1);">首页</a>
               <a href="javascript:page_nav(document.forms[0],${param.currentPageNo-1});">上一页</a>
            </c:if>
            <c:if test="${param.currentPageNo < param.totalPageCount }">
               <a href="javascript:page_nav(document.forms[0],${param.currentPageNo+1 });">下一页</a>
               <a href="javascript:page_nav(document.forms[0],${param.totalPageCount });">最后一页</a>
            </c:if>
            &nbsp;&nbsp;
         </ul>
       <span class="page-go-form"><label>跳转至</label>
        <input type="text" name="inputPage" id="inputPage" class="page-key" />页
        <button type="button" class="page-btn" onClick='jump_to(document.forms[0],document.getElementById("inputPage").value)'>GO</button>
      </span>
      </div> 
</body>
<script type="text/javascript" src="${pageContext.request.contextPath }/js/rollpage.js"></script>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8"
   pageEncoding="UTF-8"%>
<%@include file="/jsp/common/head.jsp"%>
        <div class="right">
            <div class="location">
                <strong>你现在所在的位置是:</strong>
                <span>用户管理页面</span>
            </div>
            <div class="search">
                  <form method="get" action="${pageContext.request.contextPath }/jsp/user.do">
               <input name="method" value="query" class="input-text" type="hidden">
                <span>用户名:</span>
                <input name="queryname" class="input-text"    type="text" value="${queryUserName }">
                
                <span>用户角色:</span>
                <select name="queryUserRole">
                  <c:if test="${roleList != null }">
                     <option value="0">--请选择--</option>
                     <c:forEach var="role" items="${roleList}">
                            <option <c:if test="${role.id == queryUserRole }">selected="selected"</c:if>
                            value="${role.id}">${role.roleName}</option>
                     </c:forEach>
                  </c:if>
                 </select>
                
                <input type="hidden" name="pageIndex" value="1"/>
                <input    value="查 询" type="submit" id="searchbutton">
                <a href="${pageContext.request.contextPath}/jsp/useradd.jsp" >添加用户</a>
            </form>
            </div>
            <!--用户-->
            <table class="providerTable" cellpadding="0" cellspacing="0">
                <tr class="firstTr">
                    <th width="10%">用户编码</th>
                    <th width="20%">用户名称</th>
                    <th width="10%">性别</th>
                    <th width="10%">年龄</th>
                    <th width="10%">电话</th>
                    <th width="10%">用户角色</th>
                    <th width="30%">操作</th>
                </tr>
                   <c:forEach var="user" items="${userList }" varStatus="status">
               <tr>
                  <td>
                  <span>${user.userCode }</span>
                  </td>
                  <td>
                  <span>${user.userName }</span>
                  </td>
                  <td>
                     <span>
                        <c:if test="${user.gender==1}">男</c:if>
                        <c:if test="${user.gender==2}">女</c:if>
                     </span>
                  </td>
                  <td>
                  <span>${user.age}</span>
                  </td>
                  <td>
                  <span>${user.phone}</span>
                  </td>
                  <td>
                     <span>${user.userRoleName}</span>
                  </td>
                  <td>
                  <span><a class="viewUser" href="javascript:;" userid=${user.id } username=${user.userName }><img src="${pageContext.request.contextPath }/images/read.png" alt="查看" title="查看"/></a></span>
                  <span><a class="modifyUser" href="javascript:;" userid=${user.id } username=${user.userName }><img src="${pageContext.request.contextPath }/images/xiugai.png" alt="修改" title="修改"/></a></span>
                  <span><a class="deleteUser" href="javascript:;" userid=${user.id } username=${user.userName }><img src="${pageContext.request.contextPath }/images/schu.png" alt="删除" title="删除"/></a></span>
                  </td>
               </tr>
            </c:forEach>
         </table>
         <input type="hidden" id="totalPageCount" value="${totalPageCount}"/>
         <c:import url="rollpage.jsp">
              <c:param name="totalCount" value="${totalCount}"/>
              <c:param name="currentPageNo" value="${currentPageNo}"/>
              <c:param name="totalPageCount" value="${totalPageCount}"/>
           </c:import>
        </div>
    </section>

<!--点击删除按钮后弹出的页面-->
<div class="zhezhao"></div>
<div class="remove" id="removeUse">
    <div class="removerChid">
        <h2>提示</h2>
        <div class="removeMain">
            <p>你确定要删除该用户吗?</p>
            <a href="#" id="yes">确定</a>
            <a href="#" id="no">取消</a>
        </div>
    </div>
</div>

<%@include file="/jsp/common/foot.jsp" %>
<script type="text/javascript" src="${pageContext.request.contextPath }/js/userlist.js"></script>

上述代码分别rollpage.jsp和userlist.jsp

分析:

  • Dao层使用聚合函数COUNT(*)查询用户表一共多少条记录
  • service层用于统计总共有多少条数据
  • servlet层用于向前端返回总共的数据条数

3 获取用户数量

image-20210728114029573

3.1 UserDao接口

因为是获取用户的数量,所以和用户表有关,那这个接口方法就放在前面已经创建好的UserDao中

方法定义的时候需要哪些参数?

查看前端素材:

image-20210728114227113

可见,查询界面我们需要实现按照用户名查询、按照角色名称查询和整表查询

再去看看数据库中的数据表

image-20210728114334671

image-20210728114344986

联表查询的基本SQL语句

SELECT COUNT(1)
FROM smbms_user u,smbms_role r
WHERE u.userRole = r.id;

这是MYSQL的方言版内连接查询,内连接两张表的地位平等,只要我们加了WHERE过滤笛卡尔积,那么输出的结果就只包含有主外键关联的字段

但是从前面的需求我们可以看出,前端素材提供了按照姓名查询、按照职位查询和整表查询,所以我们需要在上面联表查询的基础上再添加一些筛选条件,添加的手段就是使用"AND 条件"来实现

在Java中实现我们可以使用StringBuffer来实现sql语句的拼接

package com.thhh.dao.user;

import com.thhh.pojo.User;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;

public interface UserDao {
    /**
     * 1、得到要进行登陆的用户
     * @param conn:数据库连接对象
     * @param userCode:通过用户的用户名userCode查询用户数据
     * @return
     */
    public User getLoginUserInfo(Connection conn,String userCode);

    /**
     * 2、修改用户密码
     * @param conn:数据库连接对象
     * @param id:修改密码的用户的ID
     * @param newPwd:新密码
     * @return:影响行数
     */
    public int updatePwd(Connection conn,String newPwd,int id);

    /**
     * 3、用于获取数据库中用户总数
     * @param conn:数据库连接对象
     * @param userName:用户名,这个参数主要用于按照用户名称查询用户信息
     * @param userRole:用户角色,这个参数主要用于按照用户角色查询用户信息
     * @return :查询到的行数
     */
    public int getUserCount(Connection conn, String userName, int userRole) throws SQLException;
}

只看方法3

3.2 UserDaoImpl接口实现

//3、根据用户名/角色名获取用户总数
@Override
public int getUserCount(Connection conn, String userName, int userRole) throws SQLException {
    PreparedStatement pstmt = null;
    ResultSet rs = null;
    int count = 0;
    if (conn!=null){
        StringBuffer sql = new StringBuffer();//使用字符串缓冲区,这样就可以动态的在sql后面追加AND条件了
        sql.append("SELECT COUNT(1) COUNT FROM smbms_user u,smbms_role r WHERE u.userRole = r.id");//基本的联表查询SQL语句
        List<Object> list = new ArrayList<Object>();//创建一个list来存储我们要拼接的筛选条件,由于我们不能限制传入的参数的数据类型,所以泛型指定为object
        if (!StringUtils.isNullOrEmpty(userName)){//判断传入的用户名是不是空,如果不是空表明前端指定了按照姓名查询的参数
            sql.append("AND userName like ?");
            list.add("%"+userName+"%");
        }
        if (userRole>0){
            sql.append("AND userRole = ?");
            list.add(userRole);
        }
        Object[] params = list.toArray();//获得BaseDao中为pstmt对象设置参数的参数列表
        rs = BaseDao.executeQuery(sql.toString(), params, conn, pstmt, rs);//调用查询定义好的查询方法
        if (rs.next()){//获取查询结果
            count = rs.getInt("COUNT");//COUNT是在SQL语句中为查询结果取的别名
        }
        BaseDao.close(null,pstmt,rs);//关闭资源
    }
    return count;
}

注意回顾知识点:

  • StringBuffer类:一个专门用来弥补String内容不可修改的类,调用这个类对象的append()可以实现在字符串的末尾加上新字符串且不会产生新的字符串对象(String对象的拼接操作其实在底层就是产生了新的String对象)
  • 使用List集合来存储参数,这样做的好处就在于我们可以动态的向集合中添加参数,而不是像前面使用数组那样固定了数组长度;其实集合也是用来弥补数组长度不可修改的缺陷而出现的,使用集合存储数据即使数据很多也不会产生新的集合对象,而数组不一样,数组一经创建长度就固定了;而针对上面要实现的需求我们不知道到底前端要进行哪一种操作,所以我们不能定义一个定长的数组,即使按照最大需求量定义数组,虽然可以满足要求,但是在不能充分使用的时候就是资源的浪费
  • 上面使用的动态的拼接SQL语句的做法很明智:既使用了StringBuffer对象控制SQL语句在提交之前可变,又使用了List集合来存储参数,在提交的时候才将其转为数组

3.3 UserService接口

package com.thhh.service.user;

import com.thhh.pojo.User;

public interface UserService {
    /**
     * 1、获取登陆用户对象,对用户登陆身份进行验证
     * @param userCode:用户账号
     * @param userPassword:用户密码,注意,密码判断我们在service层进行;
     *                      在Dao层只是简单的操作数据库,没有其他的逻辑代码;在servlet层中只是接收和转发请求以及控制视图跳转
     *                      而对于业务层(service)就是用来实现业务逻辑代码的
     * @return
     */
    public User login(String userCode,String userPassword);

    /**
     * 2、根据用户ID修改用户密码
     * @param newPwd:新密码
     * @param id:用户ID
     * @return
     */
    public boolean updatePwd(String newPwd, int id);

    /**
     * 3、获取用户总数
     * @param userName:按照用户姓名查,查到的用户总数
     * @param userRole:按照用户角色查,查到的用户总数
     * @return:返回对应查询条件查到的用户总数
     */
    public int getUserCount(String userName, int userRole) ;
}

只看方法3

3.4 UserServiceImpl接口实现

/**
 * 3、按照条件查询符合条件的用户总数
 * @param userName:按照用户姓名查,查到的用户总数
 * @param userRole:按照用户角色查,查到的用户总数
 * @return
 */
@Override
public int getUserCount(String userName, int userRole) {
    Connection conn = null;
    int rs = 0;

    try {
        conn = BaseDao.getConnection();//获取数据库连接对象
        rs = userDao.getUserCount(conn,userName,userRole);//业务层调用Dao层获取业务结果
    } catch (SQLException throwables) {
        throwables.printStackTrace();
    }finally {
        BaseDao.close(conn,null,null);//关闭资源
    }
    return rs;//业务层将业务结果返回给servlet
}

3.5 测试

bug 1:

image-20210728120050970

(独爱ZS)

4 获取用户列表

image-20210728163434590

4.1 UserDao接口

/**
 * 4、用户获取用户列表
 * @param conn:数据库连接对象
 * ===========后面两个参数用于条件查询用户数据
 * @param userName:按照用户名查找
 * @param userRole:按照角色名称查找
 * ===========后面两个参数用于对按照上面条件查询出来的结果进行分页处理
 * @param currentPageNo:翻到第多少页
 * @param pageSize:每一页多少条数据
 * @return:返回满足条件的user对象集合
 */
public List<User> getUserList(Connection conn, String userName, int userRole, int currentPageNo, int pageSize) throws SQLException;

4.2 UserDaoImpl接口实现

//4、获取满足条件的用户对象集合
@Override
public List<User> getUserList(Connection conn, String userName, int userRole, int currentPageNo, int pageSize) throws SQLException {
    PreparedStatement pstmt = null;
    ResultSet rs = null;
    List<User> userList = null;
    if (conn!=null){
        userList = new ArrayList<User>();
        StringBuffer sql = new StringBuffer();
        sql.append("SELECT u.*,r.roleName as userRoleName FROM smbms_user u,smbms_role r WHERE u.userRole = r.id");

        List<Object> temp = new ArrayList<Object>();
        if (userName!=null){
            sql.append(" AND u.userName LIKE ?");
            temp.add("%"+userName+"%");
        }
        if (userRole>0){
            sql.append(" AND u.userRole = ?");
            temp.add(userRole);
        }

        sql.append(" ORDER BY u.creationDate DESC LIMIT ?,?");//在sql最后追加一个排序和分页
        //5
        //1     5
        //2     10
        //3     15
        currentPageNo = (currentPageNo-1)*pageSize;//减一的原因就是MYSQL分页的index从0开始

        temp.add(currentPageNo);//从哪一个下标开始
        temp.add(pageSize);//从currentPageNo连续取几个

        Object[] params = temp.toArray();
        rs = BaseDao.executeQuery(sql.toString(),params,conn,pstmt,rs);
        while (rs.next()){
            User _user = new User();
            _user.setId(rs.getInt("id"));
            _user.setUserCode(rs.getString("userCode"));
            _user.setUserName(rs.getString("userName"));
            _user.setGender(rs.getInt("gender"));
            _user.setBirthday(rs.getDate("birthday"));
            _user.setPhone(rs.getString("phone"));
            _user.setUserRole(rs.getInt("userRole"));
            _user.setUserRoleName(rs.getString("userRoleName"));//这个属性是在POJO中新加入的,数据表中没有
            userList.add(_user);//将查到的这个对象分装为对象并存入List集合中
        }
        BaseDao.close(null,pstmt,rs);
    }
    return userList;
}

4.3 UserService接口

/**
 * 4、根据用户名/用户角色名称来查询数据,返回一个User对象集合,而currentPageNo+pageSize用于前端做分页操作
 * @param userName
 * @param userRole
 * @param currentPageNo
 * @param pageSize
 * @return:满足条件+limit的User对象集合
 */
public List<User> getUserList(String userName, int userRole, int currentPageNo, int pageSize);

4.4 Userservice接口实现

/**
 * 4、根据用户名/用户角色名称来查询数据,返回一个User对象集合,而currentPageNo+pageSize用于前端做分页操作
 * @param userName
 * @param userRole
 * @param currentPageNo
 * @param pageSize
 * @return:满足条件+limit的User对象集合
 */
@Override
public List<User> getUserList(String userName, int userRole, int currentPageNo, int pageSize) {
    Connection conn = null;
    List<User> userList = null;

    try {
        conn = BaseDao.getConnection();//获取连接
        userList = userDao.getUserList(conn,userName,userRole,currentPageNo,pageSize);//业务层调用Dao层获取业务结果
    } catch (SQLException throwables) {
        throwables.printStackTrace();
    }finally {
        BaseDao.close(conn,null, null);//关闭资源
    }
    return userList;//业务层将业务结果返回给servlet
}

4.5 测试

@Test
public void test(){
    List userList = new UserServiceImpl().getUserList(null,0,2,5);
    //(3-1)*5 = 10,所以展示的是10~14条数据,但是一共只有12条,注意:MYSQL中结果index从0开始
    for (Object o : userList) {
        System.out.println(((User)o).getUserName());
    }
}

image-20210728164656936

image-20210728164712440

测试完成!

SMBMS(七)获取角色列表

image-20210728164902360

1. RoleDao接口

package com.thhh.dao.role;

import com.thhh.pojo.Role;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;

public interface RoleDao {
    /**
     * 1、获取角色列表
     * @param conn:数据库连接对象
     * @return:返回的结构集
     * @throws SQLException
     */
    public List<Role> getRoleList(Connection conn) throws SQLException;
}

2 RoleDaoImpl接口实现

package com.thhh.dao.role;

import com.thhh.dao.BaseDao;
import com.thhh.pojo.Role;
import org.junit.Test;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

public class RoleDaoImpl implements RoleDao{
    //1、获取角色列表

    @Override
    public List<Role> getRoleList(Connection conn) throws SQLException {
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        List<Role> list = null;//存储角色对象的集合

        if (conn!=null){
            list = new ArrayList<Role>();
            String sql = "SELECT * FROM smbms_role";//直接写死,不用参数
            Object[] params = {};
            rs = BaseDao.executeQuery(sql,params,conn,pstmt,rs);
            while (rs.next()){
                Role role = new Role();
                role.setId(rs.getInt("id"));
                role.setRoleCode(rs.getString("roleCode"));
                role.setRoleName(rs.getString("roleName"));
                list.add(role);
            }
            BaseDao.close(null,pstmt,rs);
        }
        return list;
    }
}

3 RoleService接口

package com.thhh.service.role;

import com.thhh.pojo.Role;

import java.util.List;

public interface RoleService {
    //1、获取角色列表
    public List<Role> getRoleList();
}

4 RoleService接口实现

package com.thhh.service.role;

import com.thhh.dao.BaseDao;
import com.thhh.dao.role.RoleDao;
import com.thhh.dao.role.RoleDaoImpl;
import com.thhh.pojo.Role;
import org.junit.Test;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;

public class RoleServiceImpl implements RoleService{
    private RoleDao roleDao = null;

    public RoleServiceImpl() {
        this.roleDao = new RoleDaoImpl();   //servlet中一旦调用这个service,就会实例化该Dao对象
    }

    @Override
    public List<Role> getRoleList() {
        Connection conn = null;//获取连接
        List<Role> roleList = null;
        try {
            conn = BaseDao.getConnection();
            roleList = roleDao.getRoleList(conn);//服务层调用Dao层方法
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }finally {
            BaseDao.close(conn,null,null);//关闭连接
        }
        return roleList;
    }
}

5 测试

@Test
public void test(){
    RoleService roleService = new RoleServiceImpl();
    List<Role> list = roleService.getRoleList();
    for (Role role:list) {
        System.out.println(role.getId()+"\t"+role.getRoleName()+"\t"+role.getRoleCode());
    }
}

image-20210728165612724

6 编写servlet

image-20210728165718331

href="${pageContext.request.contextPath }/jsp/user.do?method=query"

/jsp/user.do
//这个URL映射的servlet我们在前面已经注册过了,所以区别就是它后面跟的参数
    
method=query
这个参数就是在传递指定servlet应该调用的方法

这一次编写的 servlet 比较的复杂,我们把它单独拎出来看

//3、按照用户名/职位名称查询用户列表或整表查询
//【重点&难点】
public void quary(HttpServletRequest req, HttpServletResponse resp){
    //1、从前端获取数据
    String queryname = req.getParameter("queryname");
    String queryUserRole = req.getParameter("queryUserRole");
    String pageIndex = req.getParameter("pageIndex");//通过隐藏域进行的提交,默认 = 1

    int UserRole = 0;//我们先让UserRole = 0,因为从前端接收到的queryUserRole可能就是一个NULL,此时我们就需要将其指定为0
    int pageSize = 5;//这个数字最好是写在配置文件中,这样以后想要修改一页上面显示的行数,我们就不用再从新编译代码和测试了
    int currentPageNo = 1;//先给当前页设置一个默认的值

    //2、通过判断参数决定哪些请求需要处理
    if (queryname == null){
        queryname = "";//如果前端没有按照用户名查询,我们就将用户名设置""
    }
    if (queryUserRole!=null && queryUserRole!=""){
        UserRole = Integer.parseInt(queryUserRole);//当前端传过来的queryUserRole有数据的时候我们才对其进行解析
    }
    if (pageIndex!=null){
        currentPageNo = Integer.parseInt(pageIndex);
    }

    //3、为了实现分页,需要使用工具类PageSupport并传入总用户数,计算出totalPageCount
    //参数totalCount由getUserCount得出;pageSize是固定死了的;currentPageNo默认设为1
    UserService userService = new UserServiceImpl();
    int totalCount = userService.getUserCount(queryname,UserRole);
    //使用最开始导入的工具类
    PageSupport pageSupport = new PageSupport();
    pageSupport.setPageSize(pageSize);//设置一页多少行数据
    pageSupport.setTotalCount(totalCount);//设置总页数
    pageSupport.setCurrentPageNo(currentPageNo);//设置当前页数


    int totalPageCount = 0;
    totalPageCount = pageSupport.getTotalPageCount();

    //4、控制翻页
    if (currentPageNo<1){//在第一页的时候还想点击上一页
        currentPageNo = 1;
    }else if(currentPageNo>pageSupport.getTotalPageCount()) {//在第最后一页的时候还想点击下一页
        currentPageNo = totalPageCount;
    }

    //5、用户列表展示
    List<User> userList = userService.getUserList(queryname,UserRole,currentPageNo,pageSize);
    //将集合返回给前端进行解析显示
    req.setAttribute("userList",userList);

    //6、角色列表展示
    List<Role> roleList = new RoleServiceImpl().getRoleList();
    req.setAttribute("roleList",roleList);

    //7、将参数返回前端
    req.setAttribute("queryUserName",queryname);
    req.setAttribute("queryUserRole",queryUserRole);
    req.setAttribute("totalPageCount",totalPageCount);
    req.setAttribute("totalCount",totalCount);
    req.setAttribute("currentPageNo",currentPageNo);

    //8、重定向刷新页面
    try {
        System.out.println("=======================进入到servlet,且i调用method = query");
        req.getRequestDispatcher("userlist.jsp").forward(req,resp);
    } catch (ServletException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

首先,这个servlet直接和前端的用户管理页面进行交互,且用户管理页面所有展示的数据都是由这个servlet的query()返回的,所以我们应该按照前端素材来编写servlet的query()

image-20210728170811374

image-20210728170828164

image-20210728170847688

​ 通过上面的分析我们可以发现:我们需要从前端页面获取3个参数,并给前端返回7个参数

  • 获取前端参数
    • queryname:按照姓名查询的姓名
    • queryUserRole:按照职位查询的职位名称
    • pageIndex:当前的页面index
  • 传递给前端的参数
    • userList:存储前端展示的用户(user)对象集合
    • roleList:存储前端展示的角色(role)对象集合
    • queryname:再将这个参数传递回去是为了前端搜索之后搜索框中还保留着用户搜索的值
    • queryUserRole:作用同queryname
    • totalCount:总共多少条用户记录
    • currentPageNo:当前所在页数
    • totalPageCount:总页数参数,注意:这个参数来自于工具类pageSupport

image-20210728170951944

//1、从前端获取数据
String queryname = req.getParameter("queryname");
String queryUserRole = req.getParameter("queryUserRole");
String pageIndex = req.getParameter("pageIndex");//通过隐藏域进行的提交,默认 = 1

int UserRole = 0;//我们先让UserRole = 0,因为从前端接收到的queryUserRole可能就是一个NULL,此时我们就需要将其指定为0
int pageSize = 5;//这个数字最好是写在配置文件中,这样以后想要修改一页上面显示的行数,我们就不用再从新编译代码和测试了
int currentPageNo = 1;//先给当前页设置一个默认的值


//2、通过判断参数决定哪些请求需要处理
if (queryname == null){
    queryname = "";//如果前端没有按照用户名查询,我们就将用户名设置""
}
if (queryUserRole!=null && queryUserRole!=""){
    UserRole = Integer.parseInt(queryUserRole);//当前端传过来的queryUserRole有数据的时候我们才对其进行解析
}
if (pageIndex!=null){
    currentPageNo = Integer.parseInt(pageIndex);
}


//3、为了实现分页,需要使用工具类PageSupport并传入总用户数,计算出totalPageCount
//参数totalCount由getUserCount得出;pageSize是固定死了的;currentPageNo默认设为1
UserService userService = new UserServiceImpl();
int totalCount = userService.getUserCount(queryname,UserRole);
//使用最开始导入的工具类
PageSupport pageSupport = new PageSupport();
pageSupport.setPageSize(pageSize);//设置一页多少行数据
pageSupport.setTotalCount(totalCount);//设置总页数
pageSupport.setCurrentPageNo(currentPageNo);//设置当前页数


int totalPageCount = 0;
totalPageCount = pageSupport.getTotalPageCount();


//4、控制翻页
if (currentPageNo<1){//在第一页的时候还想点击上一页
    currentPageNo = 1;
}else if(currentPageNo>pageSupport.getTotalPageCount()) {//在第最后一页的时候还想点击下一页
    currentPageNo = totalPageCount;
}


//5、用户列表展示
List<User> userList = userService.getUserList(queryname,UserRole,currentPageNo,pageSize);
//将集合返回给前端进行解析显示
req.setAttribute("userList",userList)

    
//6、角色列表展示
List<Role> roleList = new RoleServiceImpl().getRoleList();
req.setAttribute("roleList",roleList);


//7、将参数返回前端
req.setAttribute("queryUserName",queryname);
req.setAttribute("queryUserRole",queryUserRole);
req.setAttribute("totalPageCount",totalPageCount);
req.setAttribute("totalCount",totalCount);
req.setAttribute("currentPageNo",currentPageNo);


//8、重定向刷新页面
try {
    System.out.println("=======================进入到servlet,且i调用method = query");
    req.getRequestDispatcher("userlist.jsp").forward(req,resp);
} catch (ServletException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
}

7 测试

image-20210728171150538

image-20210728171202619

image-20210728171217057

测试完成!

SMBMS (八)其它模块

其它还没有开发的模块包含用户管理中的增删改,以及订单管理模块,供应商管理模块的功能

其实有时间我们只需要把用户管理模块开发完整就行了,其它两个模块其实都是一个原理,只是查询的表不同

项目暂时完结!

SMBMS (九)项目感悟

项目亮点/技巧/学习点

1 在前端使用EL表达式取值提示用户账号/密码错误

​ 在登陆界面使用的EL表达式(

${error }

)取后端传回来的参数,用于提示用户输入错误

  • 由于在用户登陆之前,这个div中的EL表达式取不到值,所以它不会显示,而一旦用户输入错误,后端就会向前端响应参数(req.setAttribute("error","用户名或密码错误")😉 + 重定向前端视图,这就使用div中的EL表达式取到了error的值,从而在登陆界面上进行显示

2 使用session存储用于的全部信息

​ 在用户登陆上的时候,将从数据库中将用户的全部信息封装的pojo中,并将pojo存到session中,这就便于后面我们需要使用这个用户的某些信息的时候,不用再去查询数据库,而直接从session中获取对应的pojo对象的属性即可

  • 比如修改密码的时候使用Ajax与session中User对象的passwor做对比
  • 比如我们使用过滤器,在每一次用户发起请求的时候都去查看用户是否登陆,保证数据安全
  • 比如我们用session控制用户最长在线时间,只要长时间没操作将自动注销登陆
  • 比如注销功能,直接将session中用户对象属性删除即可配合过滤器将用户注销
  • ...

3 SQL语句的动态拼接

  • 在实现用户管理功能的模块中,首次用到了StringBuffer+集合List的组合来实现动态拼接SQL语句和存储pstmt对象所需要的参数数组的功能
    • StringBuffer提供动态拼接SQL语句的功能,使得我们可以在基础的SQL语句上加上独特的筛选条件
    • 集合List通过存储pstmt对象所需要的参数数组的功能,使得不管我们要在后面添加多少筛选条件都只是用一个集合List存储,在最后传输的时候调用List.toArray()即可将其转为数组

4 某一功能的开发流程

  • 要在前端实现一个功能,我们需要按照Dao开始-->service-->servlet-->JSP的顺序来开发

    并在进行写代码之前分析实现步骤/模块功能划分(很重要)

    ​ 只有我们先想好了怎么做,然后再去编写代码才会快,且有条不紊,切忌看完要求之后马上开始写代码

image-20210728172436448

image-20210728172733068

5 在项目中多复用,少创建

本项目中,我们复用了servlet

  • 通过前端传递的指定参数名称的参数的值,调用同一个servlet中不同的方法(将实现多个功能的servlet独立封装为一个个的方法)

在本项目中我们复用了SQL

  • 前面第3点我们提到了使用"StringBuffer+集合List的组合来实现动态拼接SQL语句和存储pstmt对象所需要的参数数组的功能",这个功能给我们带来的效果就是通过判断传递的参数是否为NULL,我们可以动态的决定要在基础的SQL语句后面添加什么筛选条件,这些条件中的参数又是什么
  • 复用的效果就是:使用这一条基本SQL语句+StringBuffer+集合List我们可以实现前端按照用户名查询、按照职位名称查询和整表查询

image-20210728173227325

6 合理使用隐藏域

在本项目中隐藏域的使用是配合servlet复用使用的,我们就是通过隐藏域来让前端向后端提交method参数的值,然后通过判断method的值决定到底调用哪一个封装在复用servlet中的方法

image-20210728173330702

image-20210728173401252

7 合理使用过滤器

通过合理的使用过滤器我们可以为后端服务器程序拦截许多的非法请求、垃圾请求、无效请求,也可以解决中文乱码问题

注意:在使用过滤器的时候,正常情况下我们只需要实现doFilter(),并且最重要的就是在方法的最后放行,否则这个请求是不能到达服务器程序并被响应的

chain.doFilter(req,resp);//过滤器放行

8 合理的编写公用的Dao

在本项目中,在最开始我们就编写了一个BaseDao类,这个类不针对某一张表进行CRUD,内部没有绑定任何的SQL语句

​ 这个类我们一共只定义了1个static代码块+4个方法

  • 静态代码块用于初始化JDBC4大参数
  • 4个方法分别为
    • 获取数据库连接对象
    • 查询数据
    • 修改数据
    • 关闭资源
  • 注意,这些方法之间不要出现相互调用的情况,我们应该在具体的对某一张表进行操作的时候再来调用这个类中的某些方法;且这些方法使用的数据库对象全部都需要从外部传递
package com.thhh.dao;
/**
 * 注意理解这个类中的方法之所以要传入这些数据库操纵对象是因为为了统一的关闭资源
 * 而传入的对象中可以都是null,具体的对象获取在方法里面进行;也可以只有conn实例化,其他对象的实例化同样放在具体的方法里进行
 */

import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;

//操作数据库的公共类
public class BaseDao {
    private static String DRIVER;
    private static String URL;
    private static String USERNAME;
    private static String PASSWORD;

    //静态代码块用于初始化JDBC4大参数,且静态代码块只会在第一次调用这个类的时候执行一次
    static {//静态代码块,在调用这个类的地方优先自动执行
        //读取配置文件
        //1、创建properties对象
        Properties properties = new Properties();
        //2、通过类加载器加载资源文件为字节输入流
        InputStream in = BaseDao.class.getClassLoader().getResourceAsStream("db.properties");
        try {
            properties.load(in);
        } catch (IOException e) {
            e.printStackTrace();
        }
        DRIVER = properties.getProperty("DRIVER");
        URL = properties.getProperty("URL");
        USERNAME = properties.getProperty("USERNAME");
        PASSWORD = properties.getProperty("PASSWORD");
    }

    //1、编写获取数据库的连接对象的公共方法
    public static Connection getConnection(){
        Connection conn= null;
        try {
            //1、加载驱动类
            Class.forName(DRIVER);
            //2、获取连接对象
            conn = DriverManager.getConnection(URL,USERNAME,PASSWORD);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return conn;
    }

    //2、编写查询公共方法 —— 注意查询的结果返回为ResultSet结果集
    /**
     * 用于查询数据的公共方法,注意:使用发送SQL语句的对象为PreparedStatement
     * @param sql:查询的sql语句,由前端传递
     * @param params:sql语句中占位符的值
     *
     *===============这下面的3个参数之所以在调用的时候传递原因就在于这3个都是资源,我们需要关闭,如果我们直接在这个方法里获取资源对象的话,那么我们就应该在这个方法中关闭资源===============
     *===============但是调用处还在等待这个方法返回结果集,所以我们不应该在这个地方获取这3个对象,而应该由调用出传递,这样可以统一管理和关闭资源===============
     *
     * @param conn:调用出使用BaseDao.getConnection()获取到数据库连接对象传入
     * @param pstmt:调用出只是传入null的引用。这个对象真正的实例化放在这个方法里面
     * @param rs:返回的结果集,和pstmt只是传入null的引用。这个对象真正的实例化放在这个方法里面
     *
     * @return:返回查询到的结果集
     */
    public static ResultSet executeQuery(String sql,Object[] params,Connection conn,PreparedStatement pstmt,ResultSet rs){
        try {
            pstmt = conn.prepareStatement(sql);
            for (int i=1;i<= params.length;i++){//循环遍历参数数组,并将参数设入SQL中
                pstmt.setObject(i,params[i-1]);//注意:数组的index从0开始,而PreparedStatement中设置占位符的值的index从1开始
            }
            rs = pstmt.executeQuery();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return  rs;
    }

    //3、编写修改公共方法
    /**
     * 用于修改数据的公共方法,注意:使用发送SQL语句的对象为PreparedStatement
     * @param sql:修改数据的sql语句模板
     * @param params:模板中占位符对应的值
     *
     * =====下面两个对象需要调用出传入也是为了统一管理和关闭资源=====
     * @param conn:调用出使用BaseDao.getConnection()获取到数据库连接对象传入
     * @param pstmt:调用出只是传入null的引用。这个对象真正的实例化放在这个方法里面
     *
     * @return 返回受影响的行数
     */
    public static int executeUpdate(String sql,Object[] params,Connection conn,PreparedStatement pstmt){
        int result = 0;
        try {
            pstmt = conn.prepareStatement(sql);
            for (int i=1;i<= params.length;i++){//循环遍历参数数组,并将参数设入SQL中
                pstmt.setObject(i,params[i-1]);//注意:数组的index从0开始,而PreparedStatement中设置占位符的值的index从1开始
            }
            result = pstmt.executeUpdate();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return  result;
    }

    //4、编写关闭资源公共方法
    /**
     * 关闭资源
     * @param conn:调用出使用BaseDao.getConnection()获取到数据库连接对象传入
     * @param pstmt:调用出只是传入null的引用。这个对象真正的实例化放在这个方法里面
     * @param rs:返回的结果集,和pstmt只是传入null的引用。这个对象真正的实例化放在这个方法里面
     * @return:返回关闭资源的结果
     *
     * 注意:关闭资源的时候要倒着关
     */
    public static boolean close(Connection conn,PreparedStatement pstmt,ResultSet rs){
        boolean flag = true;
        if (rs!=null){
            try {
                rs.close();
                rs = null;//让这个变量为null,gc就会自动对其进行回收
            } catch (SQLException throwables) {
                throwables.printStackTrace();
                flag = false;//关闭失败就将flag设置false
            }
        }
        if (pstmt!=null){
            try {
                pstmt.close();
                pstmt = null;
            } catch (SQLException throwables) {
                throwables.printStackTrace();
                flag = false;
            }
        }
        if (conn!=null){
            try {
                conn.close();
                conn = null;
            } catch (SQLException throwables) {
                throwables.printStackTrace();
                flag = false;
            }
        }
        return flag;//返回关闭结果
    }
}

9 各层功能单一,使得项目维护更高效

​ 这一点其实可以和第4点写在一起,但是为了加深印象就分开写了

​ 第4点里面说了:项目开步骤应该按照Dao开始-->service-->servlet-->JSP的顺序来开发

​ 这里补充说明:在开发的时候我们应该保证开发的每一层只是在完成自己这一层应该有的功能,应该其他层完成的功能,坚决不要混到其他层来开发

​ 各层的功能遵循SpringMVC的划分:

  1. DAO只专注于与数据库交互
    1. service专注于完成业务逻辑编写
  2. servlet专注获取前端请求、调用service、和跳转视图
  3. JSP专注于页面展示

​ 各层之间的功能不要混用,各层之间的联系通过项目调用来构建

10 多利用"面向接口编程"的思想

​ 在本项目中,不管是开发Dao层还是开发service层,我们都使用了面向接口编程的思想

​ 首先编写接口定义,再去实现接口定义;这样我们开发过程中就实现了设计和实现分离,整个项目的接口也就很清晰

image-20210728174050530

11 多测试

​ 在开发中,我们每写完一个功能之后,最好就使用junit对刚刚写的方法进行测试,这保证了开发的稳步前进

​ 在Dao层有SQL语句的地方我们最好是写一句输出SQL语句的代码,这样每次执行的时候我们都可以看到SQL语句具体是什么样的,也容易进行排错

12 细心

​ 开发过程中往往出现bug的地方都很微小,但是就是微小才不容易发现,从而导致了程序报错

  • 比如:SQL语句拼接时的空格问题
  • 比如:for循环时等号取不取的问题
  • 比如:sql.append(" AND u.userRole = ?");中将u.userRole写成r.roleCode

13 小黄鸭调试法

​ 传说中程序大师随身携带一只小黄鸭,在调试代码的时候会在桌上放上这只小黄鸭,然后详细地向鸭子解释每行代码

​ 一边阐述代码的意图一边观察它实际上的意图并做调试,这两者之间的任何不协调会变得很明显,并且更容易发现自己的错误

​ 类似的,有一种现象叫做cone of answers,这是一个常见的现象。你的朋友跑来问你一个问题,但是当他自己把问题说完,或者说到一半的时候就想出了答案走了,留下一脸茫然的你。是的,这个时候你就起到了那只小黄鸭的作用

总的来说,在你试图表述自己的想法的过程中,自然地在促使自己去整理思路,重新考虑问题

14 一些简写(背下来)

  • CRUD:crud是指在做计算处理时的增加(Create)、检索(Retrieve)、更新(Update)和删除(Delete)几个单词的首字母简写,crud主要被用在描述软件系统中数据库或者持久层的基本操作功能
  • ACID:ACID,指数据库事务正确执行的四个基本要素的缩写,包含:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability);一个支持事务(Transaction)的数据库,必须要具有这四种特性,否则在事务过程(Transaction processing)当中无法保证数据的正确性,交易过程极可能达不到交易方的要求

15 实际开发中,多用事务

​ 在实际的开发中,CRUD操作应该都是用事务操作来提交,这是因为这4种操作在执行的时候都可能失败,所以为了安全,最好的解决方案就是使用事务机制编写代码

​ 注意事务的使用位置:在try开始的时候开启事务,在业务逻辑完成的时候提交事务,在catch中回滚事务

16 开发手册

​ 推荐<阿里巴巴开发手册>,主要就是规约我们开发过程中的规范问题,写代码的结构问题 —— 很重要

17 想要了解java底层的可以看看深入理解java虚拟机这本书

说明:本文大部分转载自Jobito


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM