简介一份基于Servlet、Jsp与MySQL实现的学生成绩管理系统源码包面向计算机相关专业正在准备毕业设计的学生也适合需要项目实战练习的Java学习者。系统围绕管理员、教师、学生三类用户设计覆盖学生信息管理、教师信息管理、课程信息管理、开设课程、成绩提交、课程选择与成绩查询等典型功能可直接作为课程设计或毕设项目的基础。压缩包共166个文件以jsp页面、java源码、class文件为主包含sql数据库脚本、xml配置文件、jar依赖包、图片样式资源等整体大小约1.53MB结构清晰导入IDE并配置Tomcat与MySQL即可运行。目前已有538人学习下载适合希望快速获得一个可运行项目模板的初学者参考使用。通过这份资源读者可以拿到完整的源码与数据库脚本节省从零搭建系统的时间同时参考其分层结构与功能模块划分理解Servlet、Jsp和MySQL在Web项目中的协作方式。1. 学生成绩管理系统还在用ServletMySQLJSP这套组合现在学还晚不晚学生成绩管理系统这类Java Web课程设计现在依然被 ServletMySQLJSP 组合霸榜。很多人觉得这套技术老但正在准备毕设和期末项目的同学会发现能拿到手的源码包十有八九是这套组合。它的价值不在炫技而在于把请求处理、页面展示和数据存储拆得足够直白让你能在一周之内手动写完登录、CRUD、分页和报表。这套组合解决的是典型的“表单提交 数据库读写 结果展示”需求MySQL负责存学生、课程、成绩三张表Servlet负责接收请求并控制跳转JSP负责把数据用HTML呈现出来。对于想搞懂一套能运行的学生成绩管理系统源码、又不想直接用Spring Boot黑盒交付的读者这个路线刚刚好。我会从数据库设计、JDBC连接、Servlet跳转讲到常见坑和连接池改造每一步都有可复现代码。思路是“先立住架构和表结构再动手跑通请求链路最后集中处理那些一踩就翻车的部署问题”。2. 学生成绩管理系统的架构与数据库设计先想清楚谁该干什么2.1 用MVC拆开Servlet、JSP和Java类避免JSP里写业务最容易翻车的不是代码写不出来而是把分工搞乱了。我在给学生看成绩系统源码时发现很多所谓的Servlet项目里JSP直接查数据库、写SQL、循环输出Servlet基本只做转发。这就是没把MVC当成分工边界越写越乱。常见做法是Servlet只接收请求参数、调用业务方法、决定跳转到哪个JSPJSP只负责展示和提交表单Java普通类DAO、实体类、工具类承担数据库读写和业务规则。你不需要引入Spring也能在这条链路上保持单向依赖。比如登录请求从login.jsp发到LoginServletLoginServlet调用UserDao.findPasswordByUsername再根据结果设置Session最后跳转到首页。任何一个环节出错都能顺着Servlet日志和JSP页面定位。换掉任何一个组件另外两层不需要大改。下面是一段典型的DAO方法它访问MySQL中的user表按账号查询密码字段package com.score.dao; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import com.score.util.DbUtil; public class UserDao { public String findPasswordByUsername(String username) { String sql SELECT password FROM user WHERE username ?; try (Connection conn DbUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, username); try (ResultSet rs ps.executeQuery()) { if (rs.next()) { return rs.getString(password); } } } catch (Exception e) { e.printStackTrace(); } return null; } }逻辑说明使用try-with-resources确保Connection、PreparedStatement、ResultSet自动关闭避免连接泄漏。参数说明PreparedStatement占位符从1开始计数与列序号无关只与SQL里问号顺序有关这里只有一个?所以用setString(1, username)。强烈建议不要用字符串拼接SQL那会让成绩查询变成SQL注入的活靶子。2.2 数据库表设计学生表、课程表、成绩表和外键到底建不建学生成绩管理系统的核心数据模型通常不是一张大表硬撑而是三张互相引用的表。把建表语句放在下面基本能覆盖答辩时会被问到的“数据库设计思路”问题CREATE DATABASE IF NOT EXISTS score_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE score_db; CREATE TABLE student ( id INT AUTO_INCREMENT PRIMARY KEY COMMENT 主键自增, student_no VARCHAR(20) NOT NULL UNIQUE COMMENT 学号, name VARCHAR(50) NOT NULL COMMENT 姓名, gender ENUM(男, 女) DEFAULT 男, class_name VARCHAR(50) DEFAULT , create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB COMMENT学生表; CREATE TABLE course ( id INT AUTO_INCREMENT PRIMARY KEY, course_name VARCHAR(50) NOT NULL, credit DECIMAL(3,1) DEFAULT 0.0, teacher VARCHAR(50) DEFAULT ) ENGINEInnoDB COMMENT课程表; CREATE TABLE score ( id INT AUTO_INCREMENT PRIMARY KEY, student_id INT NOT NULL, course_id INT NOT NULL, score DECIMAL(5,2) NOT NULL COMMENT 成绩保留两位小数, exam_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_student_course (student_id, course_id), CONSTRAINT fk_score_student FOREIGN KEY (student_id) REFERENCES student(id) ON DELETE CASCADE, CONSTRAINT fk_score_course FOREIGN KEY (course_id) REFERENCES course(id) ON DELETE CASCADE ) ENGINEInnoDB COMMENT成绩表;这段DDL说明student表把student_no设为UNIQUE避免同一个学号插入两条记录score表用UNIQUE KEY uk_student_course(student_id, course_id)从数据库层面阻止同一学生同一课程重复录入成绩。外键采用ON DELETE CASCADE删学生或课程时关联成绩自动删除避免孤儿数据。学分用DECIMAL(3,1)成绩用DECIMAL(5,2)避免浮点数比较误差。关于外键是否要建我一般分情况如果只是课程设计演示建外键参数可以让答辩时少解释很多但不用把它当业务逻辑核心如果将来要拆微服务外键会变成业务层调用链的负担。对于这套ServletMySQL项目建上外键没问题MySQL 8.0默认InnoDB支持外键只要保持建表顺序即可。2.3 MySQL连接池为什么值得关注JDBC URL参数与选型理由很多初学者把DriverManager.getConnection()当成唯一写法其实这只适合课堂示例。用MySQL驱动连接数据库时URL里的参数往往直接影响能不能连上、编码会不会乱。一个常见的标准格式是jdbc:mysql://localhost:3306/score_db?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8参数作用拆开解释useSSLfalse让连接跳过SSL握手避免莫名其妙的SSL连接错误serverTimezoneAsia/Shanghai解决MySQL 8.0要求设置时区的问题characterEncodingutf8保证中文插入和查询不乱码。如果装的是MySQL 5.7可能不强制要serverTimezone但加上也无妨。连接池则显著改善体验。JDBC每个连接都经过TCP握手和MySQL认证请求一多就变慢。常见选择是Druid、C3P0和HikariCP。Druid自带监控配置直观适合课程设计HikariCP性能好但需要额外适配。我一般建议在Servlet项目里用Druid排错简单。用连接池前先给出一段最常见的配置参数用properties或代码写都行driverClassNamecom.mysql.cj.jdbc.Driver urljdbc:mysql://localhost:3306/score_db?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8 usernameroot password123456 initialSize2 maxActive10 minIdle2这些参数的含义initialSize是启动时创建的连接数maxActive是并发峰值时最多能拿多少个连接minIdle是空闲时保底连接数。单机演示maxActive10足够如果部署到服务器给多个班同时访问建议调到20到30并配合maxWait避免拿不到连接时无限等待。3. 用IDEA新建JSP项目并跑通登录从零到第一个Servlet请求3.1 传统JSP项目打包war前的工程环境准备我习惯用Maven来管理基于ServletMySQLJSP的项目而不是在IDEA里建一个裸Web工程。原因很直接毕业设计交付时通常要给别人一份war包或源码Maven可以把依赖、编译、打包标准化避免“在我电脑上能跑”的尴尬。IDEA新建JSP项目的标准动作是New Project → Java Enterprise → Web Application → 勾选Maven然后补上Tomcat运行环境。如果IDEA版本里没有Java Enterprise选项也可以建普通Maven项目后自己添加src/main/webapp目录。关键在pom.xmlproject xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd modelVersion4.0.0/modelVersion groupIdcom.score/groupId artifactIdstudent-score-manager/artifactId version1.0.0/version packagingwar/packaging properties maven.compiler.source8/maven.compiler.source maven.compiler.target8/maven.compiler.target /properties dependencies dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version4.0.1/version scopeprovided/scope /dependency dependency groupIdjavax.servlet.jsp/groupId artifactIdjavax.servlet.jsp-api/artifactId version2.3.1/version scopeprovided/scope /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency dependency groupIdjavax.servlet/groupId artifactIdjstl/artifactId version1.2/version /dependency /dependencies build finalNameROOT/finalName plugins plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-war-plugin/artifactId version3.3.2/version /plugin /plugins /build /project几个重要参数需要说清楚javax.servlet-api的scope是providedTomcat自带Servlet/JSP容器打包时不用把它塞进war否则可能类冲突mysql-connector-java不加scope默认是compile这样war包里的WEB-INF/lib才有驱动运行时才不会ClassNotFoundjstl 1.2用于JSP页面的c:forEach循环。finalName设成ROOT部署到Tomcat后可以直接用根路径访问不用带项目名。执行mvn clean package后就能得到war包。传统JSP项目打包war就是走这个流程不需要额外插件maven-war-plugin会把src/main/webapp下的资源和依赖统一打进war。3.2 数据库连接工具类从Class.forName到第一个Connection讲完工程结构先写一个最基础的数据库连接工具类。很多源码包里都叫DbUtil作用是把连接URL、用户名、密码集中管理package com.score.util; import java.sql.Connection; import java.sql.DriverManager; public class DbUtil { private static final String URL jdbc:mysql://localhost:3306/score_db?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8; private static final String USER root; private static final String PASSWORD 123456; static { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws Exception { return DriverManager.getConnection(URL, USER, PASSWORD); } }这段代码的要点Class.forName(com.mysql.cj.jdbc.Driver)会注册MySQL驱动。MySQL 8.x驱动类全限定名是com.mysql.cj.jdbc.Driver老版本驱动类是com.mysql.jdbc.Driver如果新驱动配老类名运行时会报ClassNotFoundException。放在静态块里是为了保证第一次调用getConnection之前驱动已加载。需要说明的是这里每次调用getConnection()都会物理建一个连接并发高时性能不好。初期调试用这个类最直观出错了至少知道是哪一行的问题。等系统跑通再换连接池后面第六章会细说。3.3 登录流程Servlet处理表单并携带Session跳转先写登录页到webapp/login.jsp% page contentTypetext/html;charsetUTF-8 languagejava % !DOCTYPE html html head title学生成绩管理系统登录/title /head body form actionlogin methodpost 用户名input typetext nameusername/ 密码input typepassword namepassword/ button typesubmit登录/button /form /body /htmlJSP是服务器端页面actionlogin会相对当前上下文拼接提交时username、password会作为请求参数传给Servlet。再写LoginServletpackage com.score.servlet; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.score.dao.UserDao; WebServlet(/login) public class LoginServlet extends HttpServlet { 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); UserDao dao new UserDao(); String dbPassword dao.findPasswordByUsername(username); if (password ! null password.equals(dbPassword)) { req.getSession().setAttribute(loginUser, username); resp.sendRedirect(req.getContextPath() /index.jsp); } else { req.setAttribute(msg, 用户名或密码错误); req.getRequestDispatcher(/login.jsp).forward(req, resp); } } }这里要解释清楚两个点为什么登录成功后用sendRedirect而不是forward。因为forward是服务器内部转发浏览器地址栏不变刷新时会重复提交表单redirect让浏览器重新请求新地址消除重复提交风险。登录失败用forward因为需要把错误消息msg带回login.jsp并重新渲染整个请求在一次往返内完成。req.setCharacterEncoding(UTF-8)必须放在读取任何参数之前否则Tomcat默认按ISO-8859-1解码POST表单中文用户名就会乱码。这是我的经验里乱码问题最常见来源。调到这里可以启动Tomcat访问login.jsp输入账号密码先跑通登录链路。4. 学生成绩核心功能Servlet做增删改查比JSP直接写SQL稳在哪4.1 成绩列表页查询、排序与JSTL渲染登录后第一个页面通常是成绩列表。一个规范的操作是Servlet查询数据库JSP里用JSTLEL输出而不是在JSP里写%%脚本。先看ServletWebServlet(/score/list) public class ScoreListServlet extends HttpServlet { Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); String keyword req.getParameter(keyword); ScoreDao dao new ScoreDao(); ListScoreVO scoreList dao.findWithStudentAndCourse(keyword); req.setAttribute(scoreList, scoreList); req.setAttribute(keyword, keyword); req.getRequestDispatcher(/WEB-INF/jsp/scoreList.jsp).forward(req, resp); } }说明ScoreVO是视图对象把学号、姓名、课程名、成绩等字段拼在一起方便前端直接显示。查询条件keyword可以是学生姓名或学号DAO层用WHERE s.name LIKE ?实现模糊查询。JSP放在WEB-INF/jsp下浏览器不能直接访问只能通过Servlet转发进入这是Java Web项目里实用的一道保护。对应JSP页面% taglib prefixc urihttp://java.sun.com/jsp/jstl/core % % taglib prefixfmt urihttp://java.sun.com/jsp/jstl/fmt % table border1 tr th学号/thth姓名/thth课程/thth成绩/thth操作/th /tr c:forEach items${scoreList} vars tr td${s.studentNo}/td td${s.studentName}/td td${s.courseName}/td tdfmt:formatNumber value${s.score} pattern0.00//td td a href${pageContext.request.contextPath}/score/edit?id${s.id}修改/a form action${pageContext.request.contextPath}/score/delete methodpost styledisplay:inline input typehidden nameid value${s.id}/ button typesubmit删除/button /form /td /tr /c:forEach /tableJSTL核心标签库用c:forEach迭代Listitems${scoreList}变量名s。EL表达式${s.studentName}自动调用ScoreVO.getStudentName()不用再写%%和强制类型转换。删除操作注意这里用的是formPOST提交配合隐藏id而不是用href做GET删除原因在4.3展开。4.2 添加和修改成绩PreparedStatement、事务与必填校验成绩录入是另一个高频操作。我的做法是提供同一个/score/save接口用请求里的id字段区分新增还是修改WebServlet(/score/save) public class ScoreSaveServlet extends HttpServlet { Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); String id req.getParameter(id); String studentId req.getParameter(studentId); String courseId req.getParameter(courseId); String score req.getParameter(score); if (score null || score.trim().isEmpty() || studentId null || courseId null) { req.setAttribute(errorMsg, 必填字段不能为空); req.getRequestDispatcher(/score/edit).forward(req, resp); return; } double scoreVal; try { scoreVal Double.parseDouble(score); } catch (NumberFormatException e) { req.setAttribute(errorMsg, 成绩必须是数字); req.getRequestDispatcher(/score/edit).forward(req, resp); return; } if (scoreVal 0 || scoreVal 100) { req.setAttribute(errorMsg, 成绩范围必须在0到100之间); req.getRequestDispatcher(/score/edit).forward(req, resp); return; } ScoreDao dao new ScoreDao(); try { if (id null || id.isEmpty()) { dao.insertScore(studentId, courseId, scoreVal); } else { dao.updateScore(id, scoreVal); } } catch (Exception e) { e.printStackTrace(); req.setAttribute(errorMsg, 保存失败可能该学生此课程成绩已存在); req.getRequestDispatcher(/score/edit).forward(req, resp); return; } resp.sendRedirect(req.getContextPath() /score/list); } }这段Servlet的意图是把参数解析、格式校验、业务分发都控制在Controller层DAO只管SQL执行。参数校验不是可有可无不校验一个NaN或999999会被直接写进数据库成绩表成了垃圾场。DAO里的insertScore和updateScore可以这样写public void insertScore(String studentId, String courseId, double scoreValue) throws SQLException { String sql INSERT INTO score(student_id, course_id, score) VALUES(?, ?, ?); try (Connection conn DbUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, Integer.parseInt(studentId)); ps.setInt(2, Integer.parseInt(courseId)); ps.setDouble(3, scoreValue); ps.executeUpdate(); } } public void updateScore(String id, double scoreValue) throws SQLException { String sql UPDATE score SET score ? WHERE id ?; try (Connection conn DbUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setDouble(1, scoreValue); ps.setInt(2, Integer.parseInt(id)); ps.executeUpdate(); } }逻辑说明把SQLException抛给Servlet处理而不是在DAO里直接吞掉是为了让上层有机会回滚事务。上述单条语句操作由JDBC自动提交。如果一套业务涉及多个更新比如录入成绩的同时要更新平均分就要把事务提到Service层使用connection.setAutoCommit(false)多个SQL执行后再commit()失败时rollback()。4.3 删除成绩优先用POST而不是GET防止误删这里要特意谈删除操作。许多入门项目喜欢写a href/score/delete?id1删除/a但GET请求会被预加载、搜索引擎爬虫甚至浏览器预读取触发导致本来只想看页面结果误删成绩。有一次我调试浏览器插件访问历史记录时把成绩表清掉了一半就是因为删除接口是GET。解决方法是让删除表单用POST提交form action/score/delete methodpost onsubmitreturn confirm(确认删除这条成绩记录吗); input typehidden nameid value${s.id}/ button typesubmit classbtn-danger删除/button /formServlet侧同样只需要doPost接收id并执行DELETE语句。生产环境里管理员权限、CSRF令牌、操作日志都是在这个接口上叠加的课程设计至少做到POST提交和二次确认已经比大多数源码包规范了。提示删除操作务必用POST并加确认弹窗这是我在实际项目里踩过坑后养成的习惯。5. 学生成绩管理系统避坑指南5个从开发到部署的排查现场5.1 现象Tomcat一启动就报ClassNotFoundException页面直接500部署项目后浏览器抛出java.lang.ClassNotFoundException: com.mysql.cj.jdbc.DriverTomcat日志里只有这一行。这个报错95%是MySQL驱动jar没有进入最终运行环境。如果你用Maven依赖但漏了scope驱动不会打包到WEB-INF/lib如果你手动把jar放在外部目录Tomcat也读不到。原因其实涉及Servlet生命周期里的类加载机制Tomcat的Web应用类加载器会优先从WEB-INF/lib加载第三方jar外部jar不在它的可见范围内。IDEA里的“Add as Library”和实际war包里的lib目录是两回事。解决把mysql-connector-java的scope设为compile或不写执行mvn clean package后检查target/ROOT/WEB-INF/lib下有没有mysql-connector-java-*.jar或者把jar包直接放到src/main/webapp/WEB-INF/lib下。另外一个隐藏坑是Tomcat版本Tomcat 10已经把包名从javax.*改成jakarta.*传统ServletJSP项目直接用javax.servlet.annotation.WebServlet的注解会找不到类建议锁定Tomcat 9配合Java 8。5.2 现象页面中文全是问号插入数据库的中文也变成“??”登录表单提交中文用户名系统查询结果为空直接执行SQL插入后数据库里存的是??一串问号。这个问题有三个可能叠加的层面请求编码、连接URL和数据库表字符集。解决要三步走。第一在Servlet的doPost开头加request.setCharacterEncoding(UTF-8)第二把JDBC URL改为...?characterEncodingutf8useSSLfalse第三对已建好的表执行ALTER TABLE student CONVERT TO CHARACTER SET utf8mb4;同时确认数据库默认字符集命令是SHOW VARIABLES LIKE character_set_database;。注意MySQL 8.0默认编码是utf8mb4而utf8在MySQL里是utf8mb3的别名新库建议直接指定utf8mb4。这个坑对刚接触MySQL的人尤其明显很多人装完MySQL后表还是默认latin1中文必乱。5.3 现象JSP页面里全是Java代码一改功能就崩成绩模块一开始很顺利但加了权限和分页后JSP文件超过500行里面夹杂着%...%脚本、JDBC代码一改一处错整页就崩。原因是所有逻辑都堆在JSP里没有通过Servlet和DAO做职责分离。JSP脚本异常往往不会显示具体行号只把整个编译错误的页面抛出来排错成本极高。这也是为什么现在很多代码规范里要求JSP只能出现HTML、EL表达式和JSTL标签不允许写Java脚本。解决把Java代码从JSP里搬出来。比如登录校验放在Servlet数据库查询放在DAOJSP只通过request.setAttribute拿结果。改造后遇到报错先去Servlet的日志里定位页面越薄越好排错。代码量会稍多但当你需要给成绩管理加排序、计算平均分时会感谢当年的整理。5.4 现象本地启动一切正常换一台机器部署就访问不了数据库把war包发到另一台Windows或Linux服务器上Tomcat能启动但一打开登录页就报Communications link failure或Access denied for user rootlocalhost。原因有两类一是MySQL用户默认只允许从localhost连接Tomcat所在的服务器IP不是localhost被拒二是云服务器安全组和防火墙默认不放行3306端口连接直接超时。解决在MySQL里创建一个允许任意主机连接的项目账号CREATE USER score_user% IDENTIFIED BY yourpassword; GRANT SELECT, INSERT, UPDATE, DELETE ON score_db.* TO score_user%; FLUSH PRIVILEGES;%表示任意IP授权范围限定score_db库不给全局权限。然后检查Linux防火墙命令是firewall-cmd --list-all如果没放行3306就执行firewall-cmd --add-port3306/tcp --permanent并firewall-cmd --reload。云服务器还要在安全组规则里放行TCP 3306端口。不要图省事给root开远程权限被扫描爆破的风险太高。5.5 现象修改了代码和数据库但刷新页面行为和之前一模一样在IDEA里改了Servlet、JSP点击重启Tomcat后再刷新还是旧页面或者提示404。原因通常是三层IDEA自动编译没打开代码没有重新编译旧war包覆盖不完整Tomcat的work目录缓存了老JSP反编译后的Java文件浏览器缓存了JSP页面。解决打开Settings → Compiler → Build project automatically执行mvn clean package重新打war部署前删除tomcat/webapps/ROOT旧目录和tomcat/work/Catalina下的缓存。浏览器端开发时用CtrlF5强刷或者只在开发阶段在JSP的head里加meta http-equivPragma contentno-cache正式环境记得去掉。这一条往往能省下一下午。很多时候不是你代码没改而是回收站和浏览器联手给你演了一出“改了个寂寞”。6. 让成绩管理系统更值钱Druid连接池与Filter改造6.1 用Druid连接池替换DriverManager最值得做的改造是把第二章的DbUtil换成连接池。引入依赖com.alibaba:druid:1.2.20后工具类可以精简成public class DbUtil { private static DruidDataSource dataSource new DruidDataSource(); static { dataSource.setUrl(jdbc:mysql://localhost:3306/score_db?useSSLfalse...); dataSource.setUsername(root); dataSource.setPassword(123456); dataSource.setInitialSize(2); dataSource.setMaxActive(10); dataSource.setMinIdle(2); } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } }改造点把每次DriverManager.getConnection()替换成从池里拿连接多线程并发访问时不再反复创建和销毁TCP连接几个班同时录成绩时数据库压力明显下降。maxActive不要设得比MySQL的max_connections还大否则连接池把数据库打满就成了新坑。6.2 用Filter统一做登录拦截和编码过滤第二个改造是写登录拦截Filter避免每个Servlet都检查SessionWebFilter(/*) public class LoginFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; String uri request.getRequestURI(); if (uri.endsWith(/login.jsp) || uri.endsWith(/login) || uri.contains(/static/)) { chain.doFilter(req, resp); return; } if (request.getSession().getAttribute(loginUser) null) { response.sendRedirect(request.getContextPath() /login.jsp); return; } chain.doFilter(req, resp); } }WebFilter(/*)匹配所有路径/login.jsp和/login放行其余全部要求已登录。好处是所有业务Servlet再也不用重复写Session判断。字符编码也可以单独放在一个CharacterEncodingFilter里让request.setCharacterEncoding(UTF-8)在一次请求中只执行一次。6.3 验证改造效果并留下一句经验验证方法很简单打开无痕窗口直接访问/score/list如果被弹回login.jsp说明拦截生效再从登录页走一遍正常登录流程确认成绩增删改不受影响。连接池是否生效可以配置Druid自带的StatViewServlet监控页面启动后观察连接数稳定在initialSize附近而不是每次请求都从0变成1。我这些年做Java Web课程设计和给毕业生看代码的习惯是先保留一套可以直接运行的DriverManager版源码让新手看懂原生JDBC再放一份连接池Filter版用来展示工程化意识。如果你时间紧优先做Filter再做连接池因为权限拦截是功能缺陷连接池慢一点只是性能问题。希望帮到你。本文还有配套的精品资源点击获取