前言
这两天闲来无事,用 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):模型层中又包含两层:
- Service 层:主要负责处理业务,例如根据用户的订单信息修改商品的库存等。
- 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,所以我们需要完善登录页面的代码。
写登录页面时我们需要注意以下两点:
-
- 在请求服务器静态资源时需要加上项目的根路径,也就是我们在配置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}
肯定要简单得多。
-
- 登录页面的表单是 post 请求,请求路径前面记得也要加上
${path}
,例如:
- 登录页面的表单是 post 请求,请求路径前面记得也要加上
<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 项目的核心就是用户发起请求,然后经过三层处理再将数据返回给浏览器。
所以知道了流程才能开发出完整的项目。
今天的文章基于jsp的学生管理系统分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/15567.html