Java+MySQL学生信息管理系统:Servlet/JSP/JDBC实战教程 简介基于JavaMySQL的学生信息管理系统Web课程设计资源面向计算机相关专业学生及JavaWeb初学者完整实现了学生、教师、系统管理员三类角色的核心业务。项目在IntelliJ IDEA中开发采用javaBean、Servlet和DAO分层架构通过Servlet完成请求处理并设置过滤器控制用户权限兼顾功能完整性与安全访问控制。压缩包共551个文件含32个Java源码、36个JSP页面、44个CSS、112个JS及129个PNG界面图等同时附带class编译文件、jar依赖和项目文档总大小约17.88MB目录结构清楚便于对照学习和二次开发。已有568人下载学习。借助该资源可掌握ServletDAO的开发模式、MySQL数据库操作、权限过滤机制并参考学生成绩查询、教师成绩录入、管理员信息管理等具体实现帮助快速完成课程设计或毕业设计。1. 学生信息管理系统——JavaMySQL的Web入门项目为什么值得亲手写一遍如果你正在做课程设计或者在Java Web门口徘徊不知道该拿什么练手「基于JavaMySQL的学生信息管理系统」几乎是绕不开的一个题目。它麻雀虽小五脏俱全登录校验、增删改查、分页搜索、数据库设计、会话管理全都有恰好覆盖了一门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访问MySQLTomcat做Web容器。一套下来你对Java Web的认知是完整的、成体系的。2.2 环境准备JDK、Tomcat、MySQL 8.0 的版本搭配环境搭配这件事很多人不以为意实际上版本不对能折腾一下午。我常用的组合是这样的组件版本说明JDK1.88u201课程设计最稳javax.servlet包兼容性最好Tomcat8.5 或 9.0两者都支持Servlet 3.1/4.0课程设计够用MySQL8.0驱动必须用com.mysql.cj.jdbc.DriverMaven3.6可选推荐用来管理jar包IDEIDEA 或 EclipseIDEA的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) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 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) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 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.propertiesdriverClassNamecom.mysql.cj.jdbc.Driver urljdbc:mysql://localhost:3306/student_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue usernameroot password123456 initialSize5 minIdle5 maxActive20 maxWait30000 validationQuerySELECT 1 testWhileIdletrue poolPreparedStatementstrue这里面的参数没有一个是多余的。initialSize是启动时初始化的连接数maxActive是最大活跃连接数maxWait是获取连接的最大等待毫秒数超时直接报错而不是无限等下去。validationQuerySELECT 1用于检测连接是否有效testWhileIdletrue表示空闲时定期检测防止MySQL服务端把空闲连接断掉。那串URL参数是MySQL 8.0的标配serverTimezoneAsia/Shanghai解决时区报错useSSLfalse跳过SSL握手本地开发没必要加密characterEncodingutf8保证中文参数不乱码allowPublicKeyRetrievaltrue解决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 11这类危险内容也只会被当作普通字符串处理。这一点在涉及学号、姓名这种用户输入时尤为重要。而且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返回nullparse(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 onclickreturn 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?idabc这个异常会直接导致500。稳妥的做法是包一层try-catch捕获到异常就返回「参数错误」的提示页面。这也是web服务器安全里常提到的一个细节永远不要信任前端传过来的任何参数。5. 部署与运行排查学生管理系统最常见的5个翻车现场写这个项目的人十个里有八个会卡在环境或部署上而不是业务逻辑。以下5个问题我几乎每次带人做课程设计都会遇到按现象、原因、解决三步写清楚。5.1 现象Tomcat启动后访问项目返回404Tomcat能启动但浏览器访问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加上allowPublicKeyRetrievaltrue。如果你不想把这个参数暴露在配置里还可以在MySQL里执行ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 密码但这样会改变认证方式影响其他客户端连接不如改URL参数来得干净。5.3 现象页面上显示的中文全是问号或乱码学生姓名、班级名在页面上显示为???或者æå¼º这种乱码。这是编码不一致导致的涉及三个环节数据库字符集、JDBC连接编码、HTTP请求响应编码。三种情况的表现不一样数据库里存进去就是乱的说明表和库的字符集不是utf8mb4数据库里是正常的但页面乱码说明JSP页面或者Servlet输出流的编码不对表单提交到数据库变乱码说明request.setCharacterEncoding(UTF-8)没写或者位置不对。解决思路按顺序排查先查表结构SHOW CREATE TABLE student确认DEFAULT CHARSETutf8mb4再看连接URL里有没有characterEncodingutf8最后在Servlet的doPost第一行加req.setCharacterEncoding(UTF-8)。JSP页面顶部加% page contentTypetext/html;charsetUTF-8 languagejava %。这三处统一了乱码基本消失。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): cant 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参数没有就补上serverTimezoneAsia/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面板观察一个请求经过多少个环节、耗时多少这种好奇心比看多少遍教程都管用。希望帮到你。本文还有配套的精品资源点击获取