JSP+SQL选课系统源码解析:从环境搭建到二次开发避坑指南
简介这是一套面向高校计算机相关专业学生与Java Web初学者的网上选课系统完整项目资料以JSP结合SQL数据库实现可作为毕业设计选题参考、个人技术练手或小型教务管理项目的原型模板。压缩包共482个文件约18.77MB其中70个jsp页面构成选课、学生信息、教师信息、课程信息与用户管理等核心模块配套163个gif与107个jpg界面素材、30个class与10个jar运行依赖、11个db数据库文件及mdf、ldf数据文件另含4个java源文件、2个xml配置与css样式并附论文文档、答辩PPT与说明txt便于理解系统设计与实现思路。已有218人学习下载适合需要完整源码、数据库与答辩材料一体化参考的读者可据此快速搭建环境、梳理功能模块并完成二次开发。1. 从一份 JSPSQL 选课系统源码说起它到底能帮你解决什么如果你正在做 JavaWeb 课程设计或者毕业设计大概率绕不开「选课系统」这个题目。它几乎是每个计算机专业学生都会遇到的经典命题学生选课、退课、查成绩管理员维护课程和用户教师查看选课名单。功能不复杂但五脏俱全涉及数据库设计、前后端交互、权限控制、并发处理是一个能完整跑通 JavaWeb 全链路的项目。而 JSPSQL 这套组合恰恰是很多高校教学里最常用的技术栈——JSP 负责页面渲染Servlet 处理业务逻辑JDBC 直连数据库结构清晰代码量可控适合在有限时间内做出一个能演示、能答辩的完整系统。这份打包资料里通常包含三样东西可运行的 Java 源码、配套论文、答辩 PPT。源码是核心论文帮你理清设计思路和文档结构PPT 则是答辩时的展示材料。三者配合基本覆盖了从开发到交付的全过程。但拿到压缩包只是第一步真正的问题是怎么把它跑起来代码结构怎么看数据库怎么配论文和 PPT 怎么改成自己的这些才是决定你能不能顺利交差的关键。接下来的内容我会按实际操作的顺序把这份资料从解压到跑通、从看懂到改好的路径拆开讲清楚。2. 跑通选课系统源码环境搭建与数据库初始化2.1 JDK、Tomcat 与 IDE 的版本匹配拿到源码后第一件事不是急着导入 IDE而是先确认环境版本。JSPSQL 这类教学项目通常基于较老的规范常见组合是 JDK 8 Tomcat 8.5 或 9.0 MySQL 5.7。如果你用 JDK 17 配 Tomcat 10大概率会在编译或运行时翻车因为 Tomcat 10 把 javax.servlet 包名改成了 jakarta.servlet老代码直接报 ClassNotFoundException。我一般会先看源码里 import 的 servlet 包名。打开任意一个 Servlet 文件如果看到import javax.servlet.*那就锁定 Tomcat 9 及以下如果看到import jakarta.servlet.*才考虑 Tomcat 10。JDK 方面JDK 8 是最稳妥的选择JDK 11 也能跑但再高就可能遇到模块化相关的警告甚至错误。IDE 用 IntelliJ IDEA 或 Eclipse 都行。IDEA 对 JavaWeb 项目的支持更顺手尤其是配置 Artifact 和 Tomcat 运行环境时界面更直观。Eclipse 的优势是很多老教程默认用它遇到问题搜到的答案可能更多。选哪个看你自己的习惯但建议统一用一套不要中途换。配置环境变量这一步不能省。JAVA_HOME指向 JDK 安装目录CATALINA_HOME指向 Tomcat 目录PATH里加上%JAVA_HOME%\bin和%CATALINA_HOME%\bin。配完后在命令行执行java -version和startup.batWindows或startup.shLinux/Mac确认版本号和 Tomcat 启动日志都正常。提示如果 Tomcat 启动窗口一闪而过大概率是 JAVA_HOME 没配好或者 JDK 版本不兼容。先在命令行手动运行 startup.bat看报错信息再对症处理。2.2 导入项目与依赖检查源码导入 IDE 时注意项目类型。如果是 Eclipse 项目目录下会有.project和.classpath文件如果是 IDEA 项目会有.iml文件和.idea目录。如果压缩包里没有这些说明是纯源码需要手动创建项目并导入。导入后先看WEB-INF/lib目录下有没有 jar 包。常见的依赖包括MySQL 驱动mysql-connector-java-x.x.x.jar、JSTLjstl.jar 和 standard.jar、可能还有 JSON 处理库或文件上传库。如果 lib 目录为空说明依赖需要自己下载。MySQL 驱动版本要和数据库版本匹配MySQL 5.7 用 5.1.x 或 8.0.x 驱动都能连但 8.0.x 驱动需要改连接 URL 里的时区和 SSL 参数。# 检查 lib 目录下的依赖 ls WEB-INF/lib/ # 常见输出示例 # mysql-connector-java-5.1.49.jar # jstl.jar # standard.jar如果缺少 MySQL 驱动去 Maven 仓库下载对应版本放进WEB-INF/lib然后在 IDE 里右键选择「Add as Library」。JSTL 缺失的话页面上的c:forEach标签会直接报错这个坑很常见。2.3 数据库建库建表与连接配置选课系统的数据库一般包含以下几张核心表学生表student、教师表teacher、课程表course、选课记录表sc 或 selection、管理员表admin。有些项目还会加院系表、班级表。建表语句通常在源码的sql目录下或者论文附录里。先在 MySQL 里创建数据库字符集用 utf8mb4排序规则用 utf8mb4_general_ci。然后执行建表脚本。如果脚本里有CREATE DATABASE语句注意数据库名要和后面配置文件里的一致。CREATE DATABASE course_selection DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE course_selection; -- 学生表 CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT, student_no VARCHAR(20) NOT NULL UNIQUE, name VARCHAR(50) NOT NULL, password VARCHAR(100) NOT NULL, major VARCHAR(50), class_name VARCHAR(50) ); -- 课程表 CREATE TABLE course ( id INT PRIMARY KEY AUTO_INCREMENT, course_no VARCHAR(20) NOT NULL UNIQUE, course_name VARCHAR(100) NOT NULL, teacher_id INT, credit DECIMAL(3,1), capacity INT DEFAULT 50, selected_count INT DEFAULT 0, FOREIGN KEY (teacher_id) REFERENCES teacher(id) ); -- 选课记录表 CREATE TABLE selection ( id INT PRIMARY KEY AUTO_INCREMENT, student_id INT NOT NULL, course_id INT NOT NULL, select_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_student_course (student_id, course_id), FOREIGN KEY (student_id) REFERENCES student(id), FOREIGN KEY (course_id) REFERENCES course(id) );建完表后找到源码里的数据库配置文件。常见位置是src/db.properties、src/c3p0-config.xml或者直接在某个工具类里硬编码。如果是 properties 文件修改jdbc.url、jdbc.username、jdbc.password三项即可。URL 格式一般是jdbc:mysql://localhost:3306/course_selection?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai。MySQL 8.0 驱动还需要加useSSLfalseallowPublicKeyRetrievaltrue否则会报 SSL 连接错误。# db.properties 示例 jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/course_selection?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue jdbc.usernameroot jdbc.passwordyour_password配置改完后写一个简单的测试类或者直接在 IDE 里跑一个 main 方法用DriverManager.getConnection()测试连接。连不上就检查数据库服务是否启动、端口是否被占用、用户名密码是否正确、驱动 jar 是否在 classpath 里。2.4 部署到 Tomcat 并验证登录流程数据库通了之后把项目部署到 Tomcat。IDEA 里在 Run/Debug Configurations 里添加 Tomcat Server选择本地 Tomcat 目录然后在 Deployment 选项卡里添加 Artifact。注意 Application context 设成/或者/selection这决定了你访问的 URL 路径。启动 Tomcat 后浏览器访问http://localhost:8080/或http://localhost:8080/selection/。如果看到登录页面说明部署成功。用数据库里的默认账号登录学生、教师、管理员三个角色分别测一遍。常见问题是登录后跳转 404这通常是web.xml里的 servlet mapping 和页面表单 action 不一致或者 JSP 页面路径写错了。// 登录 Servlet 的核心逻辑示例 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String username request.getParameter(username); String password request.getParameter(password); String role request.getParameter(role); UserDao userDao new UserDao(); User user userDao.login(username, password, role); if (user ! null) { request.getSession().setAttribute(user, user); response.sendRedirect(request.getContextPath() /main.jsp); } else { request.setAttribute(msg, 用户名或密码错误); request.getRequestDispatcher(/login.jsp).forward(request, response); } }这段代码的逻辑很直白拿表单参数查数据库成功就写 Session 并跳转失败就带错误信息回登录页。参数说明username和password对应表单输入框的 name 属性role是角色下拉框的值。Session 里存的 user 对象在后续页面用来判断权限和显示个人信息。3. 看懂源码结构JSP 页面、Servlet 与 DAO 的分层逻辑3.1 Model2 模式下各层的职责划分这套选课系统大概率是按 Model2 思想写的也就是 JSP 只负责显示Servlet 负责控制流程JavaBean 和 DAO 负责数据和业务。理解这个分层你才能知道改一个功能要动哪些文件。JSP 页面放在 WebContent 或 webapp 目录下按角色分文件夹student/、teacher/、admin/。每个文件夹里是各个功能页面比如student/selectCourse.jsp、teacher/viewStudents.jsp。Servlet 放在src下的包里面包名通常是com.xxx.servlet或com.xxx.controller。DAO 类在com.xxx.dao包里实体类在com.xxx.entity或com.xxx.bean包里。一个典型的选课请求流程是这样的学生在selectCourse.jsp点击「选课」按钮表单提交到SelectCourseServletServlet 调用SelectionDao.addSelection()DAO 执行 INSERT 语句并更新课程表的已选人数然后 Servlet 重定向回课程列表页面。整个链路清晰每一层只做自己的事。注意有些教学项目为了省事会把 JDBC 代码直接写在 JSP 的% %脚本片段里。这种写法虽然能跑但维护起来很痛苦而且容易被答辩老师挑刺。如果源码是这种风格建议至少把数据库操作抽到 DAO 里。3.2 关键页面与 Servlet 的对应关系要改功能先找到对应的文件。下面这张表帮你快速定位功能JSP 页面ServletDAO 方法学生登录login.jspLoginServletUserDao.login()课程列表student/courseList.jspCourseListServletCourseDao.findAll()选课操作student/courseList.jspSelectCourseServletSelectionDao.add()退课操作student/myCourses.jspDropCourseServletSelectionDao.delete()教师查看选课teacher/viewStudents.jspViewStudentsServletSelectionDao.findByCourse()管理员添加课程admin/addCourse.jspAddCourseServletCourseDao.insert()找到对应关系后改功能就是改三处JSP 页面加输入框或按钮Servlet 加参数处理DAO 加 SQL 语句。比如你想在选课的时候加一个「备注」字段就在courseList.jsp的表单里加一个 input在SelectCourseServlet里用request.getParameter(remark)取出来在SelectionDao.add()的 INSERT 语句里加上这个字段。3.3 用调试工具追踪一次选课请求光看代码不够跑起来调试才能真正确认逻辑。在 IDEA 里找到SelectCourseServlet的doPost方法在SelectionDao.add()调用那一行打个断点。然后在前端页面点击选课程序会停在断点处。这时候看 Variables 面板studentId和courseId是不是你预期的值如果studentId是 null说明 Session 里没存用户信息或者取 Session 的 key 写错了。如果courseId是 null说明表单里没有传这个参数检查 JSP 里是不是漏了input typehidden namecourseId value${course.id}。继续单步执行进入 DAO 方法看 SQL 语句拼接得对不对。有时候参数是对的但 SQL 写错了比如 WHERE 条件用了但传的是字符串没加引号或者 INSERT 的字段数和 VALUES 数对不上。调试工具能帮你把这些细节一个个揪出来。// 在 DAO 里打印 SQL 和参数方便排查 public boolean add(int studentId, int courseId) { String sql INSERT INTO selection (student_id, course_id) VALUES (?, ?); System.out.println(执行 SQL: sql 参数: studentId , courseId); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, studentId); ps.setInt(2, courseId); return ps.executeUpdate() 0; } catch (SQLException e) { e.printStackTrace(); return false; } }用PreparedStatement而不是Statement拼接字符串能避免 SQL 注入也能让参数处理更清晰。打印 SQL 和参数是个土办法但在没有日志框架的教学项目里这是最快的排查手段。4. 避坑与排查选课系统跑不起来时先查这五处4.1 中文乱码从数据库到页面的全链路排查现象页面上显示的中文变成问号或乱码数据库里存进去的也是乱码。原因编码不一致。数据库字符集、表字符集、JDBC 连接 URL、JSP 页面编码、Tomcat 的 URIEncoding任何一处不统一都会导致乱码。解决按链路逐项检查。数据库和表用 utf8mb4JDBC URL 加useUnicodetruecharacterEncodingutf8JSP 页面顶部加% page contentTypetext/html;charsetUTF-8 languagejava %Tomcat 的server.xml里 Connector 加URIEncodingUTF-8。如果还不行在 Servlet 里request.setCharacterEncoding(UTF-8)和response.setContentType(text/html;charsetUTF-8)各加一行。4.2 选课人数超限并发场景下的经典翻车现象课程容量设了 50 人但实际选课人数超过了 50或者两个学生同时选最后一个名额时都成功了。原因先查再改的逻辑不是原子的。代码里先SELECT selected_count FROM course WHERE id?判断小于 capacity 后再UPDATE这两步之间如果有另一个请求进来就会超卖。解决把判断和更新合并到一条 SQL 里利用数据库的行锁。比如UPDATE course SET selected_count selected_count 1 WHERE id ? AND selected_count capacity然后看executeUpdate()返回的影响行数如果是 0 说明名额已满。这样在数据库层面保证了原子性不需要在 Java 代码里加锁。-- 原子性的选课更新语句 UPDATE course SET selected_count selected_count 1 WHERE id ? AND selected_count capacity; -- 返回影响行数为 1 表示选课成功0 表示名额已满4.3 重复选课唯一约束没生效的排查现象同一个学生可以选同一门课多次选课记录表里出现重复行。原因表设计时没加唯一约束或者加了但代码里没处理插入失败的异常。解决在selection表的(student_id, course_id)上建唯一索引。代码里捕获SQLIntegrityConstraintViolationException提示「您已选过这门课」。如果表里已经有重复数据先清理再建索引。-- 清理重复数据保留 id 最小的那条 DELETE t1 FROM selection t1 INNER JOIN selection t2 WHERE t1.id t2.id AND t1.student_id t2.student_id AND t1.course_id t2.course_id; -- 建唯一索引 ALTER TABLE selection ADD UNIQUE KEY uk_student_course (student_id, course_id);4.4 数据库连接池耗尽连接没关的血泪经验现象系统跑一段时间后报Too many connections或Connection pool exhausted重启 Tomcat 后暂时恢复。原因DAO 里的Connection、PreparedStatement、ResultSet没有在 finally 块里关闭或者用了连接池但连接泄漏。解决用 try-with-resources 语法让 Java 自动关闭资源。如果项目用的是 C3P0 或 DBCP 连接池检查配置文件里的maxPoolSize和checkoutTimeout适当调大。但根本办法还是确保每个连接用完就还。// try-with-resources 自动关闭连接 try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { while (rs.next()) { // 处理结果 } } catch (SQLException e) { e.printStackTrace(); } // 离开 try 块后rs、ps、conn 自动关闭4.5 答辩 PPT 与论文的匹配别让文档拖后腿现象代码跑通了但论文里的数据库表结构和实际代码不一致PPT 里的功能截图和演示效果对不上。原因论文和 PPT 是最后赶出来的没有和代码同步更新。解决先定稿代码和数据库再从数据库导出表结构贴进论文从运行中的系统截图放进 PPT。论文里的 E-R 图用 Visio 或 draw.io 画别直接截图数据库客户端。PPT 控制在 10 到 15 页重点讲清楚系统架构、核心功能、技术难点和解决方案别把代码贴上去念。5. 从能跑到能改二次开发与答辩加分技巧5.1 给选课系统加一个「选课时间窗口」控制原始系统通常没有选课时间限制任何时间都能选。加一个时间窗口只在指定时间段内允许选课是个不错的加分项。实现思路在数据库里加一张system_config表存select_start_time和select_end_time两个配置项。在SelectCourseServlet里先查当前时间是否在窗口内不在就返回提示。// 在 SelectCourseServlet 里加时间窗口判断 ConfigDao configDao new ConfigDao(); Date start configDao.getSelectStartTime(); Date end configDao.getSelectEndTime(); Date now new Date(); if (now.before(start) || now.after(end)) { request.setAttribute(msg, 当前不在选课时间段内); request.getRequestDispatcher(/student/courseList.jsp).forward(request, response); return; } // 继续执行选课逻辑参数说明select_start_time和select_end_time用 DATETIME 类型存储管理员可以在后台页面修改。这个功能不复杂但能让答辩老师看到你对业务场景的思考。5.2 用过滤器统一处理编码和登录校验原始项目里每个 Servlet 都写一遍request.setCharacterEncoding(UTF-8)和登录判断代码重复且容易漏。用 Filter 统一处理既优雅又不容易出错。// EncodingFilter.java WebFilter(/*) public class EncodingFilter implements Filter { 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.setContentType(text/html;charsetUTF-8); chain.doFilter(request, response); } } // LoginFilter.java WebFilter(/student/*) 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; if (request.getSession().getAttribute(user) null) { response.sendRedirect(request.getContextPath() /login.jsp); return; } chain.doFilter(request, response); } }WebFilter注解省去了在web.xml里配置的麻烦。EncodingFilter拦截所有请求LoginFilter只拦截学生目录下的请求。注意 Filter 的执行顺序和web.xml里的配置顺序有关用注解的话顺序不确定如果有多个 Filter 且顺序重要还是建议在web.xml里配。5.3 答辩时怎么讲清楚你的技术选型答辩老师最爱问的问题之一是「为什么用 JSP 不用前后端分离」。别慌这不是在否定你是在看你对技术选型的理解。可以这样回答这个系统的目标是快速实现一个功能完整的选课流程JSPServlet 的开发模式成熟、资料多、上手快适合在课程设计的时间周期内完成。前后端分离虽然更现代但需要额外处理跨域、接口文档、前端框架学习成本对于这个规模的项目来说投入产出比不高。如果后续要扩展成多端访问可以把 Servlet 改造成 RESTful 接口JSP 换成静态页面加 Ajax 调用迁移路径是清晰的。另一个常见问题是「你的系统能支持多少人同时选课」。诚实回答教学项目的并发量不大数据库层面用行锁保证了选课不超卖但如果要支持大规模并发需要引入 Redis 缓存课程余量、消息队列削峰、数据库读写分离。这样既展示了你知道边界在哪也展示了你有扩展思路。5.4 论文查重与代码注释的平衡论文查重是个绕不开的坎。很多人把代码注释直接复制到论文里结果查重率飙升。我的习惯是论文里的代码只贴核心片段不超过 20 行而且要用自己的话重新解释一遍逻辑。数据库表结构用表格呈现别贴 CREATE TABLE 语句。E-R 图自己画别用工具生成的图片直接贴。代码注释在源码里写详细没问题但论文里的「系统实现」章节要写成叙述性文字比如「选课功能通过 SelectCourseServlet 接收前端请求调用 SelectionDao 的 add 方法向数据库插入选课记录同时更新课程表的已选人数字段」。这样既说清楚了实现又不会被查重系统盯上。最后说一个我自己的习惯每次改完代码先跑一遍完整流程从登录到选课到退课到退出确认没有 regression。然后把数据库导出备份把源码打个 zip 存一份。这样即使后面改崩了也有后悔药可以吃。希望帮到你。本文还有配套的精品资源点击获取