
最近好几个读者私信我问同一个问题“JavaWeb课程设计到底做成什么样才能拿高分”他们基本都是老师布置了基于JSPServletMySQL的学生成绩管理系统。说实话这个题目虽然传统但特别适合用来理解JavaWeb的全套流程前端页面用JSPBootstrap后端用Servlet做控制层数据存MySQL跑在Tomcat上。很多同学卡在环境配置、项目结构和数据库连接上明明代码抄对了却跑不起来。前些天我在博客后台又看到有人搜“idea运行javaweb项目配置”“mysql安装配置教程”就知道新一轮课设季又开始了。这篇博文我就把整个系统的设计、编码、部署实战链路完整过一遍全程踩坑记录无论你是大二大三做课设还是毕业前补基础都能直接照着做。我不打算只贴代码——那种文章你已经看过五十篇了——我重点讲清楚每一步“为什么这么做”以及出问题时怎么定位。1. 这个系统到底能干什么为什么还在用JSPServlet1.1 功能边界与三种角色权限先从需求说起。一个教务处学生成绩管理系统如果连需求都说不清楚代码写得再漂亮老师也会怀疑是不是自己抄的。我习惯在动手前画一个简单的功能清单比对着写代码高效得多。管理员管理教师和学生账号、维护班级和课程信息、查看全部成绩统计。教师录入课程成绩、修改学生成绩、查看自己所授课程的成绩列表。学生查看个人成绩、打印成绩单一般做成HTML打印导出PDF属于加分项。登录后进入不同的操作界面靠Session里的角色字段判断权限。但这里有个非常关键的点角色判断不要只写在JSP页面里那样很容易被绕过——用户直接访问/adminServlet这个地址如果后端不做校验照样能进入管理界面。正确做法是用Filter在Servlet执行之前统一拦截登录校验和角色授权放服务端页面只负责展示。这个后面我会专门写一节。1.2 为什么2025年还要认真学JSPServlet这个问题我在知乎和群里都答过无数次。SpringBoot确实接管了大部分底层细节但恰恰因为它太“智能”很多初学者反而不理解HTTP请求到底经历了哪些环节。Servlet是JavaWeb的根你亲手写了doGet和doPost才知道请求参数怎么来响应怎么回你亲手写了request.getRequestDispatcher().forward()才懂得转发和重定向的区别。更现实的原因是很多高校的机房、旧管理系统、甚至一些内部系统还在用TomcatJSP如果你毕业后面试遇到这类维护岗扎实的Servlet基础是加分项。我不是劝你用老技术做生产架构而是说作为课设JSPServlet可以让你在有限时间内把“全栈”流程完整跑一遍这种基础是框架粉替代不了的。1.3 这个项目的难度定位给你打个预防针这个系统的代码量不大真正折磨人的是环境配置和部署。你可能会在Tomcat启动、JDBC驱动、数据库密码上浪费三天。提前做好心理建设正是这种“折腾”让你成长。我做课设时也经历过“代码看着没问题一运行就报ClassNotFound”的时期后来才明白很多问题根本不是你代码的问题而是环境不一致。这个项目能把环境、编码、调试、部署这四个环节全跑一遍你就值回票价了。2. 开发环境准备JDK、IDEA、Tomcat、MySQL一个都不能少2.1 版本选择为什么我推荐这个组合先说版本。做这个项目我推荐 JDK 8 Tomcat 9 MySQL 8.0 IDEA 社区版。别小看版本问题它直接决定你能不能跑起来。JDK 8最稳定课件里的代码基本都是按它的语法写的。Tomcat 9支持Servlet 4.0对应javax.servlet和传统教程一致。如果你用Tomcat 10以上包名变成jakarta.servlet老代码全部报红。MySQL 8.0现在学校环境基本都装这个或者用5.7。两者驱动类名略有不同后面我会提。IDEA社区版免费用来做JavaWeb足够配置Tomcat的按钮位置和企业版略有差异但功能都有。安装顺序无所谓但两个环境变量必须配JAVA_HOME指向JDK根目录CATALINA_HOME指向Tomcat根目录。Windows上配完后最好在命令行输入java -version验证。2.2 IDEA运行JavaWeb项目的完整配置很多同学在“idea运行javaweb项目配置”上翻车我一步一步说新建 Project选择 Java Maven或者直接建空Maven项目添加war打包方式。在pom.xml里添加Servlet依赖tomcat提供的可以声明provided、MySQL驱动、Druid依赖。打开 Run/Debug Configurations点 选择 Tomcat Server - Local。配置 Application Server 指向你的Tomcat目录Deployment 标签里点 选择 Artifact - 你项目的war exploded。Application context 建议设置为/这样访问路径不带项目名方便调试。这里有个坑IDEA里Tomcat控制台中文乱码。解决方法是Help - Edit Custom VM Options加一行-Dfile.encodingUTF-8同时把Tomcat目录下conf/logging.properties里所有java.util.logging.ConsoleHandler.encoding改成UTF-8。改完重启世界就清净了。2.3 MySQL安装与连接前必看的几个问题MySQL安装本身不难难的是装完后连不上。我至少在五个学生电脑上解决过同一个报错ERROR 1045 (28000): Access denied for user rootlocalhost。原因很多最常见的是安装时设置的密码和你代码里写的不一致或者忘记密码后用skip-grant-tables跳过认证修改了密码但没刷新权限。还有个大坑是MySQL 8之前的驱动是com.mysql.jdbc.DriverMySQL 8之后必须用com.mysql.cj.jdbc.Driver。连接URL推荐写成这样String url jdbc:mysql://localhost:3306/score_db ?useUnicodetruecharacterEncodingUTF-8serverTimezoneAsia/ShanghaiuseSSLfalse;这里面每个参数都有讲究useUnicodetrue和characterEncodingUTF-8保证中文不乱码serverTimezoneAsia/Shanghai解决MySQL 8时区报错useSSLfalse避免本地环境出现SSL握手告警。曾经有个同学环境怎么都连不上加了useSSLfalse就好了。3. 数据库设计学生成绩系统的表结构怎么定3.1 从需求到表五张核心表的设计思路数据库设计是整个项目的根基表建错了后面改代码改到哭。我这套方案比较经典适合课设。sys_userid, username, password, role, ref_id。其中role用字符串或枚举存ADMIN/TEACHER/STUDENTref_id指向教师表或学生表的主键用来取关联信息。studentid, student_no, name, gender, class_name。teacherid, teacher_no, name, title。courseid, course_no, name, teacher_id, credit。scoreid, student_id, course_id, score, exam_date。为什么要单独建score表因为成绩是一个典型的多对多关系一个学生选多门课一门课有多个学生。如果你图省事在学生表里加“成绩1”“成绩2”之类的字段那你这系统基本废了——想按课程统计、按平均分排名都没法写SQL。3.2 建表SQL与索引、约束的细节下面给出一版可以直接用的建表SQL。注意外键不必物理创建但逻辑上要保持一致。我一般只在score(student_id, course_id)上建联合索引因为这是查询最频繁的路径。CREATE DATABASE score_db DEFAULT CHARACTER SET utf8mb4; USE score_db; CREATE TABLE sys_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, role VARCHAR(20) NOT NULL, ref_id INT ); CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT, student_no VARCHAR(20) NOT NULL UNIQUE, name VARCHAR(30) NOT NULL, gender VARCHAR(4), class_name VARCHAR(50) ); CREATE TABLE teacher ( id INT PRIMARY KEY AUTO_INCREMENT, teacher_no VARCHAR(20) NOT NULL UNIQUE, name VARCHAR(30) NOT NULL, title VARCHAR(30) ); CREATE TABLE course ( id INT PRIMARY KEY AUTO_INCREMENT, course_no VARCHAR(20) NOT NULL UNIQUE, name VARCHAR(50) NOT NULL, teacher_id INT, credit DECIMAL(3,1) ); CREATE TABLE score ( id INT PRIMARY KEY AUTO_INCREMENT, student_id INT NOT NULL, course_id INT NOT NULL, score DECIMAL(5,2), exam_date DATE, INDEX idx_stu_course (student_id, course_id) );为什么字符集用utf8mb4而不是utf8因为utf8在MySQL里只能存3字节的字符遇到生僻字或表情符号会报错用utf8mb4才兼容4字节。实际项目里学生名字有复姓、有生僻字这个坑我踩过。3.3 事务和排序在成绩系统中的实际应用批量录入成绩时必须使用事务。想象一个老师一次录30个学生的分数如果第15个因为校验失败存不进而前14个已经写进库了整个班级成绩就不一致。正确做法是把30条插入包在一个事务里全部成功再提交任何一条失败都回滚。Connection conn null; try { conn dataSource.getConnection(); conn.setAutoCommit(false); // 循环执行 update/insert conn.commit(); } catch (SQLException e) { conn.rollback(); throw e; } finally { // 关闭连接 }排序和统计SQL也要提前准备好答辩老师爱问。比如“每门课平均分”SELECT c.name, AVG(s.score) AS avg_score FROM score s JOIN course c ON s.course_id c.id GROUP BY c.id, c.name ORDER BY avg_score DESC;注意分组后查出的字段只能是分组字段或聚合函数这个SQL里我把c.id, c.name都放了。这在MySQL默认配置下没问题但如果开了ONLY_FULL_GROUP_BY就得严格按这个写。4. 项目骨架与核心技术JSPServletBootstrap如何协作4.1 标准MVC分层与一次请求的完整流转你打开任何一个优秀的课设代码结构基本都是这样model实体类User、Student、Score等和DAO数据访问对象。servlet控制层处理请求、调用DAO。webappJSP页面、静态资源Bootstrap。util工具类数据库连接池、字符编码Filter。在IDEA里典型目录结构大致是src/main/java → com.example → model User.java, Student.java, Score.java → dao UserDao.java, ScoreDao.java → servlet LoginServlet.java, ScoreListServlet.java等 → util DBUtil.java, MD5Util.java src/main/resources → db.properties src/main/webapp → static bootstrap, css, js, images → WEB-INF web.xml → login.jsp, main.jsp, score_list.jsp等一次请求从浏览器出发用户点击“查询成绩” - 表单或链接提交到/score/list- Tomcat根据web.xml或注解找到对应的ScoreListServlet-doGet()里拿到当前页数和关键字 - 调用ScoreDao.list(page, keyword)- 返回ListScore- 存到request.setAttribute()-forward()到score_list.jsp- JSP里用JSTL或Java脚本循环渲染表格。这里一定要记住转发和重定向的区别。登录成功后我建议用重定向resp.sendRedirect(main.jsp);原因是如果登录用的是表单POST转发地址栏还是/loginServlet用户刷新页面就会“重复提交表单”可能被要求重新登录或产生脏数据。而重定向相当于服务器告诉浏览器“你去访问另一个地址”刷新就是GET请求安全很多。4.2 Bootstrap在JSP里的整合方式和布局实践Bootstrap的引入有一个选型问题本地文件还是CDN我强烈建议下载静态文件放到项目里。答辩现场的教室网络经常不稳定CDN一旦抽风页面所有样式全丢你的分数也跟着丢。具体做法去Bootstrap官网下载bootstrap-4.x.x-dist.zip。解压后把css、js、fonts三个目录复制到src/main/webapp/static下。在JSP的head里引用link relstylesheet href${pageContext.request.contextPath}/static/css/bootstrap.min.css script src${pageContext.request.contextPath}/static/js/jquery-3.5.1.min.js/script script src${pageContext.request.contextPath}/static/js/bootstrap.min.js/script为什么要用${pageContext.request.contextPath}因为项目部署后的路径可能带上下文名直接写/static/...在不同环境下会404。这个表达式会动态拼上项目根路径是标配写法。页面布局上做一个后台管理风格的侧边栏顶栏用Bootstrap的栅格系统就够了。表格用table table-striped table-bordered table-hover操作按钮用btn btn-sm btn-primary/btn-warning/btn-danger弹窗编辑用模态框。这些都是最基础但也最能提升“卖相”的。4.3 DAO层手写JDBC还是上连接池前面我说过不建议用DriverManager每次创建连接。生产环境一定用连接池。我选Druid因为它有监控页面答辩时可以“秀”出实时连接数。但如果你不想引额外依赖用Tomcat自带的连接池也可以驱动都一样。手写DAO的核心是PreparedStatement几乎杜绝SQL注入。举个例子——错误写法String sql SELECT * FROM sys_user WHERE username name AND password pwd ;如果用户在用户名输入 OR 11这条SQL就变成了“查所有用户”直接绕过了密码验证。正确写法String sql SELECT * FROM sys_user WHERE username? AND password?; PreparedStatement ps conn.prepareStatement(sql); ps.setString(1, username); ps.setString(2, password);使用PreparedStatement不仅能防注入还能缓存执行计划性能上也更好。这一步是课设答辩的“送分题”你答出来老师会点头。4.4 用EL和JSTL替代JSP里的Java脚本JSP早期版本允许在% %里写大量Java代码但那会让页面变成“意大利面”。既然用了Bootstrap前端应该尽量干净。我推荐在JSP里用EL表达式${xx}和JSTL标签库做循环。在pom.xml添加JSTL依赖然后在JSP开头% taglib prefixc urihttp://java.sun.com/jsp/jstl/core %渲染成绩列表时c:forEach items${scoreList} vars tr td${s.studentName}/td td${s.courseName}/td td${s.score}/td /tr /c:forEach能这样写说明你已经开始用“表达式”而不是“脚本”来控制页面了代码规范度立刻上一个档次。5. 核心功能模块实现登录、权限、成绩CRUD的完整编码5.1 登录会话管理Session与Cookie的正确姿势登录模块是系统的门面。我建议做成一个独立的LoginServlet接收POST表单。密码校验通过后把用户的核心信息放进SessionWebServlet(/login) public class LoginServlet extends HttpServlet { Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) { String username req.getParameter(username); String password req.getParameter(password); User user userDao.findByUsernameAndPassword(username, password); if (user ! null) { HttpSession session req.getSession(); session.setAttribute(loginUser, user); session.setAttribute(role, user.getRole()); resp.sendRedirect(main.jsp); } else { req.setAttribute(msg, 用户名或密码错误); req.getRequestDispatcher(login.jsp).forward(req, resp); } } }这里要注意密码不能明文存储。课设阶段至少用MD5加盐但如果你想Show一下专业度可以用BCrypt。如果老师要求不高写一个MD5Util.md5(password salt)也算及格。我见过太多答辩项目里数据库直连存的明文密码这是很丢分的细节。“记住我”功能可以用Cookie实现登录成功后把用户名写进一个7天有效的CookieCookie cookie new Cookie(username, URLEncoder.encode(username, UTF-8)); cookie.setMaxAge(7 * 24 * 60 * 60); resp.addCookie(cookie);但注意不要在Cookie里存密码哪怕加密了也别存。Cookie被劫持后攻击者可以重放请求。5.2 用Filter做全局登录校验和权限拦截很多初学者只给需要权限的Servlet手动写一句判断。但如果你新加一个Servlet忘了判断漏洞就出来了。更稳的做法是写一个AuthFilterWebFilter(/*) public class AuthFilter implements Filter { Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) { HttpServletRequest request (HttpServletRequest) req; HttpSession session request.getSession(false); String uri request.getRequestURI(); if (uri.endsWith(login.jsp) || uri.endsWith(/loginServlet) || uri.contains(/static/)) { chain.doFilter(req, resp); return; } if (session null || session.getAttribute(loginUser) null) { ((HttpServletResponse) resp).sendRedirect(login.jsp); return; } chain.doFilter(req, resp); } }注意放行逻辑里面有个大坑如果Filter拦截了CSS、JS、图片等静态资源页面会变得灰头土脸。所以一定要放行/static/。还有request.getSession(false)和getSession()的区别——当Session不存在时前者返回null后者会新创建一个。在拦截器中必须用false否则你本来要拦截未登录用户反而给人家创建了一个空的Session等下一次请求时Session不为空拦截就被绕过了。5.3 成绩批量录入、修改、删除的服务端实现成绩录入往往是老师操作产品形态是页面先显示某个课程的学生列表每行一个输入框底部一个“全部提交”按钮。这里涉及数组参数接收String[] studentIds request.getParameterValues(studentId); String[] scores request.getParameterValues(score); String courseId request.getParameter(courseId);然后循环处理。记得每行成绩做基本校验比如0~100范围空白视为弃考。批量操作必须放在事务里我在前面已经说过。删除成绩功能页面用表单POST提交而不是普通的a hrefdelete?xxx。用GET做删除操作是个坏习惯搜索引擎爬虫或浏览器预加载可能会误请求直接触发删除。正确做法是放一个btn btn-danger点击后form.submit()提交POST到score/delete然后再重定向回列表页。5.4 分页、搜索和统计从SQL到JSP的完整打通分页是课设必考项。我不会推荐用PageHelper这类框架因为这是课设手写分页更能体现你的数据库功夫。核心SQL是SELECT * FROM score WHERE student_name LIKE ? LIMIT ?, ?;Servlet里int pageNo 1; int pageSize 10; String p request.getParameter(pageNo); if (p ! null) { pageNo Integer.parseInt(p); } int start (pageNo - 1) * pageSize; ListScore list scoreDao.findByPage(start, pageSize, keyword); int total scoreDao.count(keyword); int totalPage (int) Math.ceil(total * 1.0 / pageSize);注意这里Integer.parseInt(p)必须做try-catch否则用户手动输入一个非数字页数整个页面500。课设阶段你完全可以写一个NumberUtil来处理。JSP底部分页导航用Bootstrap的pagination组件。我在分页组件上踩过一个坑搜索关键字从第1页到第2页传递丢失。解决方案是分页链接后面带上已有的查询条件a hreflistServlet?pageNo${prev}keyword${keyword}上一页/a同时隐藏域或URL里带上keyword。统计功能的SQL和结果集封装也有讲究。例如查每门课平均分、每个班平均分这些SQL可以放到单独的StatisticDao。我建议用Map或专门的VO来接收不要为了省事把多个统计结果塞进同一个实体类。6. 部署上线与常见问题排查从本地Tomcat到生产环境6.1 传统JSP项目打包War部署到云服务器课设验收一般在机房但很多同学也想把项目部署到云服务器上给老师远程演示这个加分效果很足。传统JSP项目打包War包的流程在IDEA里打开 Project Structure - Artifacts - 点 - Web Application: Archive - For 项目名: war exploded。Build - Build Artifacts - 选择Archive - Build。等到target或out目录生成.war文件。把War包传到服务器Tomcat的webapps目录重启Tomcat会自动解压。如果是Linux服务器我用的是scp或rz上传。启动前要确认MySQL已经装好并建好库。给个最简单的启动命令service tomcat start # 或者 /usr/local/tomcat/bin/startup.sh然后访问http://服务器IP:8080/项目名/。6.2 高频报错排查MySQL服务起不来、SSL连接失败、端口占用我收集了热搜榜上反复出现的几个问题分享一下我的排查思路。net start mysql 服务无法启动Windows下服务无法启动90%是因为my.ini配置错误或者是data目录损坏。先去MySQL安装目录下的data文件夹里找*.err日志看最后几行。常见原因包括端口被占用、basedir/datadir路径写错、权限不足。官方的解决办法往往是重新初始化mysqld --initialize-insecure net start mysqlMySQL SSL连接错误这个问题在MySQL 8.0时代非常常见因为默认开启SSL。报错大概是什么Public Key Retrieval is not allowed。解决办法就是连接URL加两个参数useSSLfalseallowPublicKeyRetrievaltrue其中allowPublicKeyRetrievaltrue是授权JDBC客户端从服务器获取RSA公钥本地开发这样做没问题。Tomcat端口被占用启动报Port 8080 required by Tomcat ... is already in use。Windows下用命令netstat -ano | findstr :8080 taskkill /F /PID 进程号或者直接修改Tomcat的conf/server.xml里的Connector port8080改成9090或其他。注意要同时改IDEA里部署配置的端口。6.3 使用Navicat进行数据库管理和备份热词里有“navicat for mysql”说明不少同学在用图形化工具。Navicat确实适合新手建表和看数据但我不建议完全依赖它。原因是答辩时老师可能直接让你在命令行写SQL如果你只会在界面上点按钮会很尴尬。我的建议是建表阶段可以用Navicat画ER图辅助设计但实际建表语句一定要自己敲一遍。平时查询数据用Navicat的查询编辑器很方便但别忘了“保存查询”常用SQL可以下次直接复用。备份数据库是关键一步。课程设计中期、后期都可能改库建议每天备份一次mysqldump -uroot -p score_db score_db_backup.sql恢复测试mysql -uroot -p score_db score_db_backup.sql如果你把MySQL装在Linux服务器上还需要注意防火墙和安全组。忘了放行3306端口Navicat远程连接会一直卡在“连接失败”。7. 做这个项目最大的收获给后来人的几个真实建议7.1 最容易被忽略的评审细节写到这里整个系统的骨架和主要代码已经完整过了一遍。最后聊点走心的。我见过很多同学把课设当任务糊弄代码东拼西凑最后答辩被问得哑口无言。其实这个项目完全是你展示学习能力的舞台。有四个细节特别容易被忽略数据库连接密码写在代码里。至少放到db.properties配置文件中答辩时老师看到你不会把密码硬编码会加分。没有统一的异常处理。Servlet里catch到Exception后最好转发到一个error.jsp而不是让页面直接打印堆栈。后者不仅丑还会暴露代码结构。前端校验覆盖不全。Bootstrap自带的校验只检查格式服务端必须再校验一次。比如成绩范围0~100前端可能改掉后端必须拦住。列表页没有删除确认。删除操作至少要用JavaScript弹窗确认否则误触一下数据就没了。7.2 答辩现场的自救清单如果你在答辩前还有一天时间按这个顺序做一次全流程测试清空数据库重新运行建表SQL保证从零能跑通。使用教师账号录一批成绩用学生账号查看验证权限隔离。测试分页时翻到最后一页再输入一个不存在的页码看是否报错。把Tomcat重启一次确认没有“手动点运行”之外的隐藏依赖。打开Chrome的开发者工具逐个请求看状态码和响应时间做到心中有数。我记得第一次做这个项目的时候最吃力的是理解“转发”和“重定向”的区别后来我索性在项目里打印日志每次请求都把URI和是否转发打出来。跑通之后真的有一种“通透了”的感觉。最后再分享一个实操技巧把项目跑起来后先用浏览器开发者工具打开“网络”标签看每一个请求的状态码和路径。你能看懂每个请求去了哪个Servlet又转发了哪个JSP你对JavaWeb的认知就已经超过一半的同学了。这个系统后续还能加不少东西但先把当前这些做扎实你的课设就稳了。