简介:基于Java+MySQL的学生信息管理系统Web课程设计资源,面向计算机相关专业学生及JavaWeb初学者,完整实现了学生、教师、系统管理员三类角色的核心业务。项目在IntelliJ IDEA中开发,采用javaBean、Servlet和DAO分层架构,通过Servlet完成请求处理,并设置过滤器控制用户权限,兼顾功能完整性与安全访问控制。压缩包共551个文件,含32个Java源码、36个JSP页面、44个CSS、112个JS及129个PNG界面图等,同时附带class编译文件、jar依赖和项目文档,总大小约17.88MB,目录结构清楚,便于对照学习和二次开发。已有568人下载学习。借助该资源可掌握Servlet+DAO的开发模式、MySQL数据库操作、权限过滤机制,并参考学生成绩查询、教师成绩录入、管理员信息管理等具体实现,帮助快速完成课程设计或毕业设计。
1. 学生信息管理系统——Java+MySQL的Web入门项目为什么值得亲手写一遍
如果你正在做课程设计,或者在Java Web门口徘徊不知道该拿什么练手,「基于Java+MySQL的学生信息管理系统」几乎是绕不开的一个题目。它麻雀虽小五脏俱全:登录校验、增删改查、分页搜索、数据库设计、会话管理全都有,恰好覆盖了一门Java Web课最重要的知识点。很多java课程设计案例源码仓库里都有这个题目,但直接下源码跑通和自己从头写一遍,收获完全两码事。
我的建议很直接:哪怕最终答辩要用Spring Boot,也先拿Servlet + JSP + MySQL把它亲手写一遍。这套组合能让你看见一次HTTP请求从浏览器到数据库再回来的完整链路,而这些正是java面试题里最常被追问的底层逻辑。适合正在赶课程设计的学生,也适合想补Java Web地基、准备转后端的开发者。
2. 技术选型与工程骨架:Servlet/JSP/MySQL 的搭配逻辑和目录搭建
2.1 为什么选 Servlet + JSP 而不是直接上 Spring Boot
很多读者会问:现在企业里都用Spring Boot,为什么课程设计还要用Servlet这种老古董?这个问题的答案,恰好就是这个项目最大的价值。
Spring Boot把Tomcat内嵌了,把DispatcherServlet封装了,把MyBatis的会话工厂托管了,你写一个@RequestMapping注解就能接请求。但学生信息管理系统这种体量的项目,用Servlet反而更清楚:一个URL对应一个Servlet类,doGet和doPost里写的每一行业务代码都摆在明面上。答辩时老师问「请求是怎么进来的」,你能从web.xml的映射一路讲到HttpServletRequest里的参数解析,而不是只能说「框架帮我处理的」。
从学习路径看,Servlet + JSP也是Java后端完整成长路线的第一站。先搞懂请求响应模型、Session与Cookie的区别、Filter的执行顺序,再去看Spring MVC的DispatcherServlet,你会瞬间明白那些框架设计是怎么来的。反过来直接学框架,遇到问题就像在黑匣子里猜,连日志都看不懂。
所以这个项目我坚持用最朴素的组合:Servlet处理请求,JSP渲染页面,JDBC访问MySQL,Tomcat做Web容器。一套下来,你对Java Web的认知是完整的、成体系的。
2.2 环境准备:JDK、Tomcat、MySQL 8.0 的版本搭配
环境搭配这件事,很多人不以为意,实际上版本不对能折腾一下午。我常用的组合是这样的:
| 组件 | 版本 | 说明 |
|---|---|---|
| JDK | 1.8(8u201+) | 课程设计最稳,javax.servlet包兼容性最好 |
| Tomcat | 8.5 或 9.0 | 两者都支持Servlet 3.1/4.0,课程设计够用 |
| MySQL | 8.0 | 驱动必须用com.mysql.cj.jdbc.Driver |
| Maven | 3.6+ | 可选,推荐用来管理jar包 |
| IDE | IDEA 或 Eclipse | IDEA的Community版免费够用 |
JDK的环境变量配置是老生常谈:新建JAVA_HOME指向JDK安装目录,在PATH里追加%JAVA_HOME%\bin,然后在命令行执行java -version验证。如果你卡在这一步,网上随便搜java环境变量配置详细教程都能解决,这里不展开。
MySQL我建议直接装8.0版本,字符集选utf8mb4,排序规则选utf8mb4_general_ci。如果你是照着mysql安装教程8.0装的,装完后记得在MySQL Workbench里跑通一次SELECT 1,确认服务是活的再进下一步。这里提前说一个关键点:8.0的驱动类名和连接URL参数跟5.7不一样,后面第5章的排查部分我会专门讲。
2.3 目录结构:按分层思想组织源码
工程结构直接决定你后面写代码时脑子乱不乱。我用Maven建的标准Java Web工程目录长这样:
student-manager/ ├── pom.xml └── src/main/ ├── java/com/campus/ │ ├── dao/ # 数据访问层,全部JDBC操作在这层 │ │ ├── BaseDAO.java │ │ ├── StudentDAO.java │ │ └── UserDAO.java │ ├── entity/ # 实体类,对应数据库表 │ │ ├── Student.java │ │ └── User.java │ ├── servlet/ # Web控制层,接收请求、调DAO、转发JSP │ │ ├── LoginServlet.java │ │ ├── StudentListServlet.java │ │ ├── StudentAddServlet.java │ │ ├── StudentEditServlet.java │ │ └── StudentDeleteServlet.java │ ├── filter/ # 过滤器,统一编码、登录校验 │ │ └── EncodingFilter.java │ └── util/ # 工具类:连接池、MD5加密 │ ├── DBUtil.java │ └── MD5Util.java ├── resources/ │ └── druid.properties # 连接池配置 └── webapp/ ├── WEB-INF/web.xml # Servlet映射、过滤器声明 ├── css/ # 样式文件 ├── js/ # 前端脚本 └── jsp/ ├── login.jsp └── student_list.jsp这个结构遵循的是最经典的三层思想:servlet层只做参数接收和页面跳转,dao层只做SQL执行,entity层只做数据载体。学生信息管理系统虽然小,但按这个骨架写,后面加功能只是往对应层里加文件的事。
web.xml里的核心配置要注意,Servlet的映射路径决定了浏览器访问的URL。我习惯把列表页映射成/student/list,登录页映射成/login,这样URL语义清晰,也不容易和其他Servlet冲突。JSP文件统一放在/jsp目录下,WEB-INF里面只放配置和编译产物,外部直接访问不到,必须通过Servlet转发进去——这一点在后面做登录权限控制时会很有用。
3. 数据库设计与连接池:建表SQL、JDBC封装和连接配置的落地细节
3.1 学生信息管理系统的核心表结构
数据库设计是这个项目的地基。见过太多人一上来就建一张大表,字段全堆在一起,后期改需求改到想哭。学生信息管理系统最少需要两张表:一张存学生信息,一张存登录用户。我做课程设计时一般会再拆一张班级表,但考虑到篇幅,这里先讲最常用的两表方案。
-- 学生表 CREATE TABLE `student` ( `id` INT NOT NULL AUTO_INCREMENT COMMENT '主键', `student_no` VARCHAR(20) NOT NULL COMMENT '学号', `name` VARCHAR(50) NOT NULL COMMENT '姓名', `gender` TINYINT DEFAULT 0 COMMENT '性别:0男 1女', `birth_date` DATE DEFAULT NULL COMMENT '出生日期', `phone` VARCHAR(20) DEFAULT NULL COMMENT '手机号', `class_name` VARCHAR(50) DEFAULT NULL COMMENT '班级', `status` TINYINT DEFAULT 0 COMMENT '状态:0在读 1休学 2毕业', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_student_no` (`student_no`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='学生信息表'; -- 用户表 CREATE TABLE `user` ( `id` INT NOT NULL AUTO_INCREMENT COMMENT '主键', `username` VARCHAR(50) NOT NULL COMMENT '登录名', `password` VARCHAR(64) NOT NULL COMMENT '密码(MD5)', `real_name` VARCHAR(50) DEFAULT NULL COMMENT '真实姓名', `role` TINYINT DEFAULT 1 COMMENT '角色:1管理员 2普通用户', PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';字段设计有几个细节值得说道。学号加唯一索引uk_student_no,防止同一学号录两遍,这是业务上最常见的脏数据来源。gender和status用TINYINT而不是VARCHAR存储,既省空间又方便扩展——这也是mysql开发中很常见的习惯。status字段我设置了默认值为0,表示在录,这样插入数据时可以少写一个字段。create_time用DEFAULT CURRENT_TIMESTAMP自动填充,连代码都不用写。
有些教程会在建表时用存储过程或者触发器来维护create_time,但课程设计项目我不建议这么做。存储过程把业务逻辑藏进了数据库,你在答辩时很难讲清楚,出了问题排查也更麻烦。用DEFAULT CURRENT_TIMESTAMP这种内置机制就够了,简单直接。
3.2 连接池选择与Druid配置参数
JDBC连接数据库最原始的方式是DriverManager.getConnection(),每操作一次就建立一个物理连接,用完再关。这种方式的缺点是连接创建和销毁非常耗时,在高并发场景下会把数据库拖垮。学生信息管理系统虽然并发不高,但既然要写一个像样的项目,连接池是必须的。
连接池我用Druid,原因很简单:配置直观、自带监控、文档多。在resources目录下建一个druid.properties:
driverClassName=com.mysql.cj.jdbc.Driver url=jdbc:mysql://localhost:3306/student_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true username=root password=123456 initialSize=5 minIdle=5 maxActive=20 maxWait=30000 validationQuery=SELECT 1 testWhileIdle=true poolPreparedStatements=true这里面的参数没有一个是多余的。initialSize是启动时初始化的连接数,maxActive是最大活跃连接数,maxWait是获取连接的最大等待毫秒数,超时直接报错而不是无限等下去。validationQuery=SELECT 1用于检测连接是否有效,testWhileIdle=true表示空闲时定期检测,防止MySQL服务端把空闲连接断掉。
那串URL参数是MySQL 8.0的标配:serverTimezone=Asia/Shanghai解决时区报错,useSSL=false跳过SSL握手(本地开发没必要加密),characterEncoding=utf8保证中文参数不乱码,allowPublicKeyRetrieval=true解决MySQL 8.0的认证插件在首次连接时抛Public Key Retrieval is not allowed的问题——这个报错在第5章我会细讲。
3.3 DAO层封装:PreparedStatement防注入与代码复用
DAO层的核心原则是:一个方法对应一条SQL,方法的输入参数和返回值都用实体类,不把SQL细节暴露给Servlet层。我一般会先写一个BaseDAO把通用的获取连接和释放资源封装好:
public class BaseDAO { protected Connection getConnection() throws SQLException { return DBUtil.getConnection(); // 从Druid连接池拿连接 } protected void close(ResultSet rs, PreparedStatement ps, Connection conn) { if (rs != null) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } if (ps != null) { try { ps.close(); } catch (SQLException e) { e.printStackTrace(); } } if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }DBUtil内部用静态代码块加载druid.properties,初始化DruidDataSource,对外只暴露getConnection()。第一次写这个类的时候注意,DruidDataSource要声明成static,保证整个应用只有一个连接池实例,否则每个页面请求都重新创建一个连接池,内存直接溢出。
具体的增删改查我全部用PreparedStatement,不用Statement拼SQL。PreparedStatement的好处不只是防SQL注入——它会把SQL结构提前编译好,参数用占位符?传入,即使参数里带了单引号、OR 1=1这类危险内容,也只会被当作普通字符串处理。这一点在涉及学号、姓名这种用户输入时尤为重要。而且SQL语义清晰,代码可读性也强,比如查询学生列表:
String sql = "SELECT * FROM student WHERE class_name = ? ORDER BY id DESC"; PreparedStatement ps = conn.prepareStatement(sql); ps.setString(1, className); ResultSet rs = ps.executeQuery();4. 核心功能逐一实现:登录、分页、增删改查的代码与参数设置
4.1 登录校验与Session管理
登录是系统的门户,做得规范不规范,直接影响后续所有功能的安全性。登录流程分三步:接收用户名密码、校验数据库、把用户信息放进Session。
密码存储我建议用MD5加盐处理,不要明文存库。虽然MD5本身已经不算安全,但课程设计场景下它足够应付,而且比明文强一个档次。实际代码如下:
@WebServlet("/login") public class LoginServlet extends HttpServlet { private UserDAO userDAO = new UserDAO(); @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("UTF-8"); String username = req.getParameter("username"); String password = req.getParameter("password"); User user = userDAO.findByUsername(username); if (user != null && user.getPassword().equals(MD5Util.md5(password))) { req.getSession().setAttribute("loginUser", user); resp.sendRedirect(req.getContextPath() + "/student/list"); } else { req.setAttribute("errorMsg", "用户名或密码错误"); req.getRequestDispatcher("/jsp/login.jsp").forward(req, resp); } } }这里有两个细节容易踩坑。第一是req.setCharacterEncoding("UTF-8")必须放在getParameter之前调用,否则中文用户名会乱码。第二是密码校验放在Java代码里比较,不要拼到SQL里查,这样即使密码错也不会暴露数据库查询逻辑。
登录成功后把User对象放进Session,后续所有页面要判断登录态时,直接取session.getAttribute("loginUser"),取不到就跳回登录页。所谓权限控制,本质就是这一步没做好的话,别人直接访问/student/list就能绕过登录看到所有数据。
4.2 学生列表的分页与模糊查询
分页查询几乎是所有管理系统的标配,学生信息管理系统也不例外。MySQL的LIMIT关键字天然支持分页,语法是LIMIT offset, rows,注意第一个参数是偏移量而不是页码,这个搞错是新手最常见的SQL错误。正确的写法是offset = (pageNum - 1) * pageSize。
int pageNum = 1; // 当前页码,默认第1页 int pageSize = 10; // 每页条数 String keyword = req.getParameter("keyword"); if (req.getParameter("pageNum") != null) { pageNum = Integer.parseInt(req.getParameter("pageNum")); } String sql = "SELECT * FROM student WHERE name LIKE ? OR student_no LIKE ? " + "ORDER BY id DESC LIMIT ?, ?"; PreparedStatement ps = conn.prepareStatement(sql); ps.setString(1, "%" + keyword + "%"); ps.setString(2, "%" + keyword + "%"); ps.setInt(3, (pageNum - 1) * pageSize); ps.setInt(4, pageSize);这里的关键是排序规则:ORDER BY id DESC,按主键倒序能让新添加的学生排在最前面,也方便后面扩展。模糊查询用LIKE配合前后通配符%,如果你只做前缀匹配keyword%,查询速度会快一些,但会漏掉名字中间含关键字的记录,根据业务需求权衡即可。
分页信息需要三个数据:总条数、总页数、当前页数据。总条数用SELECT COUNT(*)单独查一次,总页数用(totalCount + pageSize - 1) / pageSize计算,这个公式比Math.ceil更直观。注意COUNT(*)查询和分页查询是两次独立的数据库操作,不要试图一条SQL同时返回数据和总数。
4.3 新增与编辑学生:日期转换和表单回显
学生表里有birth_date日期字段,这是新增功能里最容易翻车的地方。HTML表单提交的日期是一个字符串yyyy-MM-dd,而JDBC需要java.sql.Date对象,中间必须做一次转换。
String birthDateStr = req.getParameter("birthDate"); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); java.util.Date utilDate = sdf.parse(birthDateStr); java.sql.Date sqlDate = new java.sql.Date(utilDate.getTime());这段代码看起来简单,但有两个隐性问题。第一,SimpleDateFormat.parse()会抛ParseException,必须try-catch或者向上抛出,否则编译都过不了。第二,如果用户在前端没填日期,getParameter返回null,parse(null)会直接抛异常,所以要先判空,为空就传null给SQL。
编辑功能复用新增的表单页面,区别在于编辑时要先查原始数据回显到表单里。回显时JSP页面的value="${student.name}"会把数据库里查出来的值填回输入框,此时要注意HTML转义问题。如果学生姓名里包含<script>标签,不做转义直接输出会触发XSS攻击。JSP的${}表达式在EL默认会做HTML转义,但如果你在代码里用了out.println()手动输出,就得自己调用StringEscapeUtils.escapeHtml4()。
更新时我用的是标准的UPDATE语法:
String sql = "UPDATE student SET student_no=?, name=?, gender=?, birth_date=?, phone=?, class_name=?, status=? WHERE id=?";注意WHERE id=?一定要带上,忘了它就会把整张表的数据全部更新成同一个值——这个坑我当年踩过一次,血泪经验。
4.4 删除操作与权限控制
删除操作是破坏性的,必须做两层保护。第一层是前端确认框,用JavaScript的confirm()拦截误点:
<button onclick="return confirm('确定删除学号为${s.studentNo}的学生吗?')">删除</button>第二层是后端校验,也是最关键的一层。前端弹窗只是用户体验,真正拦住非法请求必须靠服务端。删除Servlet里要做两件事:检查登录态、检查删除参数合法性。
HttpSession session = req.getSession(); User loginUser = (User) session.getAttribute("loginUser"); if (loginUser == null) { resp.sendRedirect(req.getContextPath() + "/login.jsp"); return; } if (loginUser.getRole() != 1) { // 只有管理员能删除 resp.sendError(403, "无权限执行删除操作"); return; } int id = Integer.parseInt(req.getParameter("id")); studentDAO.deleteById(id); resp.sendRedirect(req.getContextPath() + "/student/list");注意Integer.parseInt在参数非法时会抛NumberFormatException,如果用户手动构造请求/student/delete?id=abc,这个异常会直接导致500。稳妥的做法是包一层try-catch,捕获到异常就返回「参数错误」的提示页面。这也是web服务器安全里常提到的一个细节:永远不要信任前端传过来的任何参数。
5. 部署与运行排查:学生管理系统最常见的5个翻车现场
写这个项目的人,十个里有八个会卡在环境或部署上,而不是业务逻辑。以下5个问题我几乎每次带人做课程设计都会遇到,按现象、原因、解决三步写清楚。
5.1 现象:Tomcat启动后访问项目返回404
Tomcat能启动,但浏览器访问http://localhost:8080/student-manager/一直404。
原因几乎都是部署路径对不上。IDEA里Artifact的部署名称是student-manager,访问URL却写成了/student,或者直接把项目丢进了webapps目录但没重启Tomcat。还有一个常见原因:项目里配置了<welcome-file>指向/jsp/login.jsp,但JSP文件实际不在那个位置。
解决方法是先在Tomcat的webapps目录下确认项目文件夹名称,然后访问http://localhost:8080/项目文件夹名/。如果用的是IDEA,打开Run Configuration,看Deployment选项卡里的Application context是什么,那才是访问根路径。另外,不要手动往Tomcat的webapps里复制文件,IDEA的on update机制会自动同步,手动复制反而容易搞出两份不同版本。
5.2 现象:首次操作数据库报 Public Key Retrieval is not allowed
用MySQL 8.0连接数据库,第一次执行SQL时抛出Public Key Retrieval is not allowed。
这是MySQL 8.0默认认证插件caching_sha2_password的行为:客户端第一次连接时要求获取服务器的公钥来加密密码,但JDBC驱动默认不允许这一操作。网上很多人说要改MySQL的认证插件为mysql_native_password,那是绕远路。
解决方法是连接URL加上allowPublicKeyRetrieval=true。如果你不想把这个参数暴露在配置里,还可以在MySQL里执行ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '密码',但这样会改变认证方式,影响其他客户端连接,不如改URL参数来得干净。
5.3 现象:页面上显示的中文全是问号或乱码
学生姓名、班级名在页面上显示为???或者æå¼º这种乱码。
这是编码不一致导致的,涉及三个环节:数据库字符集、JDBC连接编码、HTTP请求响应编码。三种情况的表现不一样:数据库里存进去就是乱的,说明表和库的字符集不是utf8mb4;数据库里是正常的但页面乱码,说明JSP页面或者Servlet输出流的编码不对;表单提交到数据库变乱码,说明request.setCharacterEncoding("UTF-8")没写或者位置不对。
解决思路按顺序排查:先查表结构SHOW CREATE TABLE student,确认DEFAULT CHARSET=utf8mb4;再看连接URL里有没有characterEncoding=utf8;最后在Servlet的doPost第一行加req.setCharacterEncoding("UTF-8")。JSP页面顶部加<%@ page contentType="text/html;charset=UTF-8" language="java" %>。这三处统一了,乱码基本消失。
5.4 现象:启动Tomcat报 ClassNotFoundException: com.mysql.cj.jdbc.Driver
项目启动时,日志报找不到MySQL驱动类,但pom.xml里明明已经引入了依赖。
原因有两种可能:一种是Maven依赖没下载全,另一种是打包时驱动jar没有打进去。如果你用的不是Maven而是手动放jar包的传统方式,最常见的错误是把mysql-connector-java的jar包放在了src/main/java目录下,或者放进了WEB-INF/classes而不是WEB-INF/lib。
解决方法是:用Maven的话,去本地仓库~/.m2/repository/mysql/看驱动jar是否存在,删掉后执行mvn clean install强制重新下载;手动管理jar的话,确认驱动jar在WEB-INF/lib目录下,然后重新部署项目。另外注意,Maven项目里如果scope写成了provided,驱动jar也不会被打进war包,因为provided表示「容器提供」,而Tomcat默认还真不提供MySQL驱动——这个细节坑过很多人。
5.5 现象:访问数据库报 Cannot create PoolableConnectionFactory 或者连接超时
Cannot create PoolableConnectionFactory,后面跟Communications link failure或者Connection refused,日志里还能看到error 2002 (HY000): can't connect to local mysql server through socket。
这是数据库连接层面的报错,原因基本可以锁定在几个方面:MySQL服务没启动、端口不对、URL里的IP或主机名写错、防火墙拦截、时区参数缺失。排在第一位的是MySQL服务没启动,Windows下打开服务管理器(services.msc)找到MySQL服务看状态;Linux下执行systemctl status mysqld。
解决时先用MySQL Workbench或者命令行连一次,确认服务本身正常。然后查URL里的jdbc:mysql://localhost:3306/student_db,端口要跟实际一致。如果你的MySQL跑在远程服务器上,还要检查bind-address配置和防火墙规则,本地开发不建议开远程连接,容易暴露安全问题。最后看有没有serverTimezone参数,没有就补上serverTimezone=Asia/Shanghai,时区报错通常比连接失败出现得更频繁。
6. 进阶:用一个Filter同时解决编码和登录态校验
项目跑通之后,你会发现代码里到处都是setCharacterEncoding和登录判断,重复且容易漏。这时候就用得上Filter了,这也是Java Web里非常核心的一个组件。
@WebFilter(urlPatterns = "/*") public class AuthFilter implements Filter { @Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) resp; request.setCharacterEncoding("UTF-8"); response.setCharacterEncoding("UTF-8"); String uri = request.getRequestURI(); String ctx = request.getContextPath(); String path = uri.substring(ctx.length()); // 放行登录页、登录接口和静态资源 if (path.equals("/login") || path.startsWith("/jsp/login") || path.startsWith("/css/") || path.startsWith("/js/")) { chain.doFilter(request, response); return; } // 其他所有请求都必须有登录态 Object loginUser = request.getSession().getAttribute("loginUser"); if (loginUser == null) { response.sendRedirect(ctx + "/jsp/login.jsp"); return; } chain.doFilter(request, response); } }这样一来,编码设置和登录拦截全部收敛到一个类里,每个Servlet只需要关心自己的业务逻辑,不用再重复写那几行样板代码。urlPatterns = "/*"表示拦截所有请求,然后通过路径判断放行哪些。注意静态资源一定要放行,否则CSS和JS文件全被拦下来,页面样式直接崩掉。
你可以在这个Filter里继续扩展:记录请求耗时、把用户操作写入日志、做细粒度的角色权限判断。学生信息管理系统做完这些,你已经把Java Web里Servlet、Filter、Session、JDBC、连接池这些核心概念全部串起来了。我一直有个习惯:每写完一个功能,就试着从浏览器F12打开Network面板,观察一个请求经过多少个环节、耗时多少,这种好奇心比看多少遍教程都管用。希望帮到你。
本文还有配套的精品资源,点击获取