基于jsp的学生管理系统

1,498 阅读11分钟

前言

这两天闲来无事,用 servlet 和 jsp 做了个简单的学生管理系统。

做这个系统的原因是我前面讲了很多 javaweb 的基础知识:

我想把这些知识串起来,让正在学习这一块知识的小伙伴真正了解 web 项目的开发流程。

1. 项目演示

注: 因为我主要想讲解系统开发的流程,所以只实现了学生管理的的功能。后续的功能大家可以自行完善,就当是练手了。

1.1 管理员登录

登录失败

登录成功

未登录访问其他资源

1.2 查询

查询到数据:

没有查询到数据:

清除查询条件:

1.3 新增学生

1.4 修改学生

1.5 删除学生

1.6 退出

2. 系统环境

2.1 开发工具

  • 后端开发工具:IDEA
  • 前端开发工具:VSCODE
  • 数据库连接工具:Navicat

2.2 技术栈

前端:

  • html+css+JavaScript
  • 框架:Juery+BootStrap

后端:

  • JDK:jdk8
  • 技术:Jsp+Servlet
  • 数据库:Mysql
  • Tomcat:Tomcat9

3. 数据库设计

网站的核心资源是数据,而数据存储于数据库之中。所以开发网站的前提是要先设计数据库。

而数据库的设计是基于用户的需求。

什么是用户的需求?需求就是我们要开发这个网站做什么?这个网站都有什么功能?

3.1 管理员信息表

我们开发的这个网站是学生管理系统,既然是管理系统,肯定要有管理员,所以需要有一张管理员表来存储管理员的信息。

而管理员必须要登录才能进入到这个系统,所以管理员表中要包含账号和密码,当然啦还要有主键 id。

3.2 学生信息表

我们开发的这个网站是用来管理学生信息的,所以必须要有一张学生信息表来存储学生的信息。

学生表中要包含学生的重要信息:姓名、性别、年龄等。

3.3 班级信息表

我们除了要知道学生的信息,我们还想知道学生所在的班级怎么办?那就需要一张班级表。

班级表必须要有班级的名称。

3.4 维护表之间的关系

我们知道一个班级包含多个学生,所以班级信息和学生信息是一对多的关系,根据数据库设计范式,需要在的一方存入的一方的 id。

所以学生表里面需要有班级表的 id 。

3.5 根据需求扩展表

因为我只是做一个小的 demo,所以只设计了三张表:管理员表、班级表、学生表。

大家可以根据扩展的功能再增加表,例如增加教师表、成绩表等。

3.6 创建数据库和表

数据库:student_system

管理员表:s_admin

CREATE TABLE `s_admin` (
  `id` int NOT NULL AUTO_INCREMENT COMMENT 'id',
  `username` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '账号',
  `password` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '密码',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC;

学生表:s_student

CREATE TABLE `s_student` (
  `id` int unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
  `class_id` int DEFAULT NULL COMMENT '班级id',
  `name` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '姓名',
  `sex` tinyint(1) DEFAULT NULL COMMENT '0-女 1-男',
  `age` int DEFAULT NULL COMMENT '年龄',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=18 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC;

班级表:s_class

CREATE TABLE `s_class` (
  `id` int NOT NULL AUTO_INCREMENT COMMENT 'id',
  `name` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '班级名称',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC;

完整表结构:

4. 项目搭建

4.1 新建项目

File -> new -> Project

4.2 加载所需 jar 包

右键 lib 目录,Add as Library。

4.3 完善项目目录

  • controller:存放 servlet
  • dao:持久化层,与数据库打交道
  • file:这里存放的是连接 mysql 的配置文件
  • filter:存放过滤器
  • model:存放实体类
  • service:业务处理层
  • util:存放工具类
  • static:存放静态资源,例如 css、js、图片等
  • view:存放 jsp 文件

5. 配置 tomcat

部署项目

配置项目默认的访问路径

6. 配置默认访问页面

我们知道 javaweb 项目启动后默认访问的是 index.jsp、index.html 等文件。

但是管理员需要登录才能进入系统中,所以需要在 web.xml 中配置该项目的默认启动页是登录页:

<?xml version="1.0" encoding="UTF-8"?>
<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">

    <!-- 默认进入到登录页面-->
    <welcome-file-list>
        <welcome-file>login.jsp</welcome-file>
    </welcome-file-list>
</web-app>

7. 创建实体类

实体类和数据库中的表是一一映射的。创建班级类的时候需要注意一点:班级的英文 class 是 java 中的关键字,所以这里我改成了 Classes。

/**
 *  班级实体类
 */
public class Classes {
    // id
    private int id;
    // 班级名称
    private String name;

    public int getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

除了 Admin、Student、Classes类,我还创建了 Page 类用来封装分页信息。

8. JDBC 工具类

网站的核心资源是数据,所以我们不得不和数据库打交道。

而每次添加、编辑、删除数据都需要先获取数据库的连接,再去操作,简直太麻烦了。

那使用 java 连接数据库的时候我不想写一堆重复性的代码,怎么办?

我们可以将操作数据库的代码封装成一个工具类,里面包含一些静态的方法,用到的时候只需要用类名.方法调用即可。

package com.xxx.sms.util;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.sql.*;
import java.util.Properties;
/**
 * 公众号:知否技术
 */
public class JDBCUtils {
    // 数据库账号
    private static String user;
    // 数据库密码
    private static String password;
    // 连接数据库url
    private static String url;
    // 数据库驱动
    private static String driver;

    static {
        //  静态代码块只需要加载一次,读取资源文件
        try {
            // 1. 加载配置文件
            Properties pro = new Properties();
            pro.load(JDBCUtils.class.getResourceAsStream("/com/xxx/sms/file/jdbc.properties"));
            System.out.println(pro);
            // 2. 获取配置文件中连接数据库的信息
            url = pro.getProperty("url");
            user = pro.getProperty("user");
            password = pro.getProperty("password");
            driver = pro.getProperty("driver");
            // 3. 创建数据库连接驱动
            Class.forName(driver);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
    
    //  4. 获取连接对象
    public static Connection getConnection() throws SQLException {
        return DriverManager.getConnection(url, user, password);
    }

    //  5. 释放资源
    public static void close(PreparedStatement ps, Connection conn) {
        close(null, ps, conn);
    }

    //  6. 释放资源(重载)
    public static void close(ResultSet rs, PreparedStatement ps, Connection conn) {
        if (null != rs) {
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (null != ps) {
            try {
                ps.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (null != conn) {
            try {
                conn.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

其中 jdbc.properties 文件包含了数据库的一些配置信息:

url=jdbc:mysql://localhost:3306/student_system
user=root
password=12345678
driver:com.mysql.jdbc.Driver

9. filter 拦截请求

我们知道网站中的资源不是随便就可以获取到的,例如当你没有登录淘宝时你不能将商品加入购物车。

而 filter 就是用来拦截用户的请求的。

在这个系统中,我们需要判断管理员是否登录,如果登录了就放行,如果没登录就重定向到登录页面。

当然了如果遇到登录、退出、静态资源,我们也需要放行。

AccessFilter:

@WebFilter(filterName = "accessFilter", urlPatterns = "/*")
public class AccessFilter implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        HttpSession session = request.getSession();
        // 1. 获取 URI: 例如 /sms/student/list
        String url = request.getRequestURI();
        // 2. 因为 web.xml 里面我们默认配置的欢迎页面是登录页面,而项目进入登录页面的url的末尾是 /sms/
        // 所以这里要排除掉登录、退出、以及静态资源
        if (url.endsWith("/sms/") || url.contains("/login") || url.contains("/logout") || url.contains("/static/")) {
            // 3. 放行
            filterChain.doFilter(request, response);
        } else {
            // 4. 在请求其他资源之前判断admin 有没有登录,如果没有登录则重定向到登录页面
            Admin admin = (Admin) session.getAttribute("admin");
            // 5 .管理员已经登录过
            if (null != admin) {
                // 6. 放行
                filterChain.doFilter(request, response);
            } else {
                // 7. 重定向 login.jsp,请先登录再访问其他资源
                response.sendRedirect(request.getContextPath() + "/login.jsp");
            }
        }
    }
}

10. MVC 设计模式

10.1 啥是 MVC 设计模式?

我们刚开始写代码的时候,喜欢在一个方法里面写一堆又臭又长的代码。例如在一个 servlet 里面既获取用户的请求参数,又要连接数据库获取数据,最后还要封装数据再返回给浏览器。

所以呢为了便于维护代码,让代码看起来不那么恶心,代码分层的思想便出现了。

所谓的代码分层就是每一层干自己最拿手的事情。

按照功能的划分,我们将代码分为三层,即模型层(Model)、视图层(View)、控制层(Controller)。而这种代码的组织架构就叫 MVC模式

10.2 各层功能划分

  • View(视图):就是我们看到的网站页面,包含 html 、js 等,主要负责显示数据。
  • Controller(控制器):主要负责获取用户的请求参数,处理用户的请求。这里就是指的 servlet。
  • 模型层(Model):模型层中又包含两层:
  1. Service 层:主要负责处理业务,例如根据用户的订单信息修改商品的库存等。
  2. Dao层:主要负责和数据库打交道,操作数据、封装数据。

10.3 代码的调用顺序

View -> Controller -> Service -> Dao

用户在 View 层(浏览器)发起请求,然后 Controller 层(servlet)接收用户请求并调用 service 层。

Service 层处理业务逻辑调用 Dao 层获取操作之后的数据,并将结果返回给 Controller 层(servlet)。Controller 层(servlet)再将响应结果发送给浏览器(View 层)。

11. 登录

11.1 login.jsp

因为我们在 web.xml 中配置了项目默认的访问页面是 login.jsp,所以我们需要完善登录页面的代码。

写登录页面时我们需要注意以下两点:

    1. 在请求服务器静态资源时需要加上项目的根路径,也就是我们在配置tomcat 时配置的 Application context:

而在 jsp 文件中获取项目根路径采用如下方式:

${pageContext.request.contextPath}

例如:

<link rel="stylesheet" href="${pageContext.request.contextPath}/static/css/login.css">
<script type="text/javascript" src="${pageContext.request.contextPath}/static/js/jquery.js"></script>

但是呢这样获取项目根路径还是有点麻烦,我们可以在 jsp 头部采用 java 代码的方式获取,然后再放到全局作用域里面。例如:

<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path;
application.setAttribute("path", basePath);
%>

其实 path 的值就是:

http://localhost:8080/sms

这样我们引用服务器静态资源的方式就可以改成如下方式:

<link rel="stylesheet" href="${path}/static/css/login.css">
<script type="text/javascript" src="${path}/static/js/jquery.js"></script>

使用 ${path}肯定要简单得多。

    1. 登录页面的表单是 post 请求,请求路径前面记得也要加上${path},例如:
<form action="${path}/login" method="POST">
  <h4 style="color:black;text-align: center;margin-top:35px">学生管理系统</h4>
  <div class="form">
      <img src="${path}/static/image/person.png" alt="账号">
      <input type="text" name="username" placeholder="请输入账号">
  </div>
  <div class="form">
      <img src="${path}/static/image/password.png" alt="密码">
      <input type="password" name="password" maxlength="6" placeholder="请输入密码"></div>
  <input type="submit" value="登录">
</form>

11.2 loginServlet

管理员打开登录页面,输入账号密码之后点击登录按钮,肯定需要有一个 Servlet 来处理请求。

所以我们需要写一个 LoginServlet 来处理管理员的登录请求。

那管理员随便输入账号密码就能登录到系统后台了吗?肯定不是。

所以 LoginServlet 需要获取管理员输入的账号密码,然后调用 Service 的登录方法,Service 层调用 Dao 层的登录方法,Dao 层根据传递的账号密码和从数据库进行查询,如果查到了就将真实的信息返回。

如果数据库中存在该信息,就将该信息存到 Session 里面,然后重定向到学生列表的页面,也就是登陆成功顺利进入学生管理的后台。

如果数据库中不存在该信息,那不好意思直接重定向到登录页面,提示账号或者密码错误,需要重新登录。

@WebServlet("/login")
public class LoginServlet extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 1. 获取浏览器传递的 username 参数
        String username = request.getParameter("username");
        // 2. 获取浏览器传递的 password 参数
        String password = request.getParameter("password");
        AdminService adminService = new AdminServiceImpl();
        // 3. 调用 service 层的登录方法,根据账号和密码获取管理员信息
        Admin admin = adminService.login(username, password);
        // 4. 如果管理员存在
        if (null != admin) {
            HttpSession session = request.getSession();
            // 5. 将管理员信息存放到 session 里面
            session.setAttribute("admin", admin);
            // 6. 重定向到用户管理的页面。注:这里 request.getContextPath() 是获取项目的根路径
            response.sendRedirect(request.getContextPath()+"/student/list");
        } else {
            // 7. 如果不存在,就将错误信息放到 request 域里面,然后请求转发到登录页面
            request.setAttribute("error","账号或者密码错误!");
            request.getRequestDispatcher("/login.jsp").forward(request,response);
        }
    }
}

12. 管理后台页面

管理后台页面其实就是按照功能区域划分成一个个 div。所以我会先在纸上划分一下区域。

然后再用 vscode 画一下管理后台的页面。

管理后台的页面我引入了 BootStrap 框架,BootStrap 其实就是别人开发好的 js 和 css 样式,我们可以直接引入并使用。

<link rel="stylesheet" href="${path}/static/bootstrap/css/bootstrap.min.css">
<script src="${path}/static/js/jquery.js"></script>
<script src="${path}/static/bootstrap/js/bootstrap.min.js"></script>

BootStrap 官网:

https://v3.bootcss.com/

因为展示列表信息的时候需要用到 JSTL 标签,所以需要引入 JSTL 的标签库:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>

13. 列表

13.1 ListServlet

管理员登录成功之后重定向到获取学生列表的页面。所以需要有一个 servlet 来获取学生的列表信息,然后封装成 Page 类。

/**
 * 公众号:知否技术
 */
@WebServlet("/student/list")
public class ListServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 1. 获取当前页
        String current = req.getParameter("currentPage");
        // 2. 默认为第一页
        int currentPage = (null != current && current.length() > 0) ? Integer.parseInt(current) : 1;
        // 3. 封装查询参数:姓名和班级id
        Map<String, Object> paramMap = new HashMap<>();
        paramMap.put("name",req.getParameter("name"));
        paramMap.put("classId",req.getParameter("classId"));
        StudentService studentService = new StudentServiceImpl();
        // 4. 调用service层的list方法
        Page<Student> page = studentService.list(currentPage,paramMap);
        ClassesService classesService = new ClassesServiceImpl();
        // 5. 获取所有班级信息
        List<Classes> classList = classesService.getClassList();
        // 6. 将数据全部存放到 request 域里面
        req.setAttribute("page", page);
        req.setAttribute("classList", classList);
        req.setAttribute("paramMap", paramMap);
        // 7. 请求转发到学生管理的 jsp 页面
        req.getRequestDispatcher("/WEB-INF/view/student/list.jsp").forward(req, resp);
    }
}

ListServlet 将封装后的数据放到 request 域中,然后再请求转发到 list.jsp 页面。

13.2 list.jsp

list.jsp 从 request 域中获取数据,然后使用 EL 表达式和 JSTL 标签循环遍历学生的信息。

  <c:forEach items="${page.list}" var="student" varStatus="status">
    <tr>
        <td style="vertical-align:middle">${status.index+1}</td>
        <td>${student.name}</td>
        <td>${student.sex==0?"女":"男"}</td>
        <td>${student.age}</td>
        <td>${student.className}</td>
        <td width="20px">
            <a onclick="return showStudentDetailModal('${path}/student/find?id=${student.id}')">
                <button type="button" class="btn btn-default">编辑</button>
            </a>
        </td>
        <td width="20px">
            <a onclick="return showDelStudentModal('${path}/student/delete?id=${student.id}','${student.name}')">
                <button type="button" class="btn btn-danger">删除</button>
            </a>
        </td>
    </tr>
</c:forEach>

14. 添加和编辑

添加和编辑页面我共用的 bootstrap 的模态框,点击不同的按钮使用 jquery 展示不同的 title:

$("#title").text("新增学生信息");
$("#title").text("编辑学生信息");

点击编辑的时候要先回显学生的信息,这里我使用了 ajax 请求 FindServlet 获取学生信息:

// 回显数据
$.ajax({
    url: url,
    dataType: "json",
    success: function (data) {
        $("#studentId").val(data.id);
        $("#studentName").val(data.name);
        $("input[name='sex'][value='" + data.sex + "']").attr("checked", true);
        $("#studentAge").val(data.age);
        $("#studentClassId").val(data.classId);
    }
});

FindServlet

/**
 * 公众号:知否技术
 */
@WebServlet("/student/find")
public class FindServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        StudentService studentService = new StudentServiceImpl();
        // 1. 获取浏览器传递的 id 信息
        int id = Integer.parseInt(req.getParameter("id"));
        // 2. 调用 studentService 的find 方法,获取学生信息
        Student student = studentService.find(id);
        // 3. 设置响应对象的 contentType 为 json格式
        resp.setContentType("application/json;charset=utf-8");
        PrintWriter writer = resp.getWriter();
        // 4. 将数据写入到输出流
        writer.println(JSON.toJSONString(student));
        writer.flush();
        writer.close();
    }
}

因为编辑是根据 id 修改数据库中的信息,所以编辑页面需要有一个隐藏的 id:

<input type="hidden" name="id" id="studentId">

我用了同一个 Servlet 来处理新增和编辑,关键在于传递过来的数据是否包含 id,有 id 那就调用 service 层的 update 方法,否则调用 add 方法。

/**
 * 公众号:知否技术
 */
@WebServlet("/student/saveOrUpdate")
public class SaveServlet extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 1. POST 请求设置编码格式,解决获取中文乱码的问题
        req.setCharacterEncoding("utf-8");
        // 2. 获取浏览器传递的 id 参数
        String id = req.getParameter("id");
        // 3. 新建学生对象
        Student student = new Student();
        // 4. 设置id:如果id不存在,则设置为0。否则就是传递的id
        student.setId(Integer.parseInt(("".equals(id)) ? "0" : req.getParameter("id")));
        // 5. 设置学生姓名
        student.setName(req.getParameter("name"));
        // 6. 设置学生性别
        student.setSex(Integer.parseInt(req.getParameter("sex")));
        // 7. 设置学生年龄
        student.setAge(Integer.parseInt(req.getParameter("age")));
        // 8. 设置学生班级id
        student.setClassId(Integer.parseInt(req.getParameter("classId")));
        StudentService studentService = new StudentServiceImpl();
        boolean flag;
        // 9. 如果 id 为 0,表示是新建学生信息
        if (student.getId() == 0) {
            // 10. 调用service 的新增方法
            flag = studentService.add(student);
        } else {
            // 11. 否则调用service 的编辑方法
            flag = studentService.update(student);
        }
        // 12. 如果新增或者修改成功,则重定向到学生列表页面
        if (flag) {
            resp.sendRedirect(req.getContextPath() + "/student/list");
        }
    }
}

15. 删除

点击删除需要访问 DeleteServlet,DeleteServlet 获取传递的 id 然后调用 service 层的 delete 方法,Service 层调用 Dao 层的 delete 方法,Dao 层通过操作数据库来删除学生的信息。

@Override
public boolean delete(int id) {
    try {
        // 1. 获取数据库连接对象
        conn = JDBCUtils.getConnection();
        // 2.  sql 语句
        String sql = "delete from s_student where id = ?";
        // 3. 创建执行sql的对象
        ps = conn.prepareStatement(sql);
        // 4. 给 ?赋值
        ps.setInt(1, id);
        // 5 执行sql
        int count = ps.executeUpdate();
        if (count > 0) {
            return true;
        } else {
            return false;
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        // 6. 释放资源
        JDBCUtils.close(ps, conn);
    }
    return false;
}

16. 退出

点击退出按钮请求 LogoutServlet,LogoutServlet 获取请求之后先清除 session 里面管理员的信息,然后再重定向到登录页面。

<a href="${path}/logout">退出</a></li>
/**
 * 公众号:知否技术
 */
@WebServlet("/logout")
public class LogoutServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 1. 获取 session 数据
        HttpSession session = req.getSession(false);
        if (session == null) {
            return;
        }
        // 2. 清除 session 里面的管理员信息
        session.removeAttribute("admin");
        // 3. 重定向到登录页面
        resp.sendRedirect(req.getContextPath() + "/login.jsp");
    }
}

17. 总结

其实 web 项目的核心就是用户发起请求,然后经过三层处理再将数据返回给浏览器。

所以知道了流程才能开发出完整的项目。