ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Java课程设计实战:自助选课系统与JDBC事务并发控制

Java课程设计实战:自助选课系统与JDBC事务并发控制 简介这份自助选课系统毕业设计文档以单份docx呈现面向计算机专业需要完成课程设计、毕业论文或答辩准备的读者。内容围绕基于Java语言、MySQL数据库和B/S架构的自助选课系统展开采用MVC模式设计涵盖了用户管理、课程管理、课程推荐、选课管理、班级管理、教师信息管理和公告管理等模块系统可改善传统选课时间复杂、信息不透明、易出错等问题提升选课与管理效率。论文从绪论、关键技术到系统分析、设计与实现均有完整阐述包含中英文摘要和目录结构能帮助读者理解从需求分析、数据库设计到IDEA环境下代码实现的全过程尤其适合借鉴类似教务信息系统开发的实现思路和写作框架。资源包共1个文件类型为docx压缩包大小7.03MB已有35人学习下载。1. 自助选课系统别人能跑的你也能讲清楚选课系统大概是Java课程设计里被做烂了也最容易被问住的一个。容量30人的课第31个人是怎么进来的很多人拿到的源码能跑但打开选课方法一看是先SELECT COUNT再INSERT并发一上来就翻车。这份基于Java的自助选课系统的论文源码正好是用来补这块短板的技术栈是Java SE Swing JDBC MySQL覆盖学生端自助选课、退课、课表查看教师端查看选课名单管理员端维护课程信息并配套一份能直接改写的课程论文章节。适合正在找Java课程设计案例源码的在校生也适合马上要参加Java面试、想复习JDBC事务和并发控制的人。它不是那种黑匣子式的Demo而是把每一步都摊开给你看的东西。2. 从技术选型到模块划分为什么是 Swing JDBC MySQL2.1 技术栈怎么定课程设计场景下最稳的组合先说要技术栈。市面上Java课程设计有两条路一条是Spring Boot Vue或者Thymeleaf一套全家桶下来写的是接口和页面互相调用另一条就是这套资源走的Java SE路线图形界面用Swing数据访问用原生JDBC数据库用MySQL。我接触过的课程设计里后者占大多数。原因不复杂工期短重点全在面向对象编程和数据库这两门课上Swing虽然界面老土但事件监听、组件模型、布局管理都是课堂上反复强调的东西答辩好讲。另外Swing JDBC的环境要求很低。机房电脑只要有JDK就能跑不用装Tomcat、不用配Maven私服MySQL装一个5.7或者8.0都可以。驱动jar放进lib目录一条Class.forName就能连上。相比Spring Boot它把很多细节暴露出来了——连接怎么开、事务怎么控制、结果集怎么遍历这些恰好是Java面试题里高频出现的东西。做完这个项目再回头看那些事务隔离级别和SQL注入的题目会有种原来如此的感觉。如果用的是IDEA我会建一个普通的Java项目而不是Maven项目把mysql-connector-java.jar放到lib目录右键Add as Library。要是你习惯Maven在pom.xml里加依赖也是一样的。只是写论文的时候注意统一说法别一会儿写“采用原生JDBC”一会儿又写“通过Maven管理依赖”答辩时被追问就露馅了。这份源码的目录分层一般长这样和课程设计论文里最爱画的三层架构图能对应上├── src │ └── com/course/selection │ ├── view │ │ ├── LoginFrame.java │ │ ├── StudentFrame.java │ │ ├── TeacherFrame.java │ │ └── AdminFrame.java │ ├── service │ │ ├── UserService.java │ │ └── CourseService.java │ ├── dao │ │ ├── UserDao.java │ │ ├── CourseDao.java │ │ └── SelectCourseDao.java │ ├── model │ │ ├── Student.java │ │ ├── Teacher.java │ │ └── Course.java │ └── util │ └── DBUtil.java ├── sql │ └── init.sql └── lib └── mysql-connector-java.jar这套结构里view层只负责组件展示和事件绑定不直接写SQLservice层处理业务规则比如登录校验、选课冲突、时间冲突判断dao层每一个方法对应一条SQLmodel层就是Student、Teacher、Course这些JavaBean属性跟数据库字段一一对应。后面第3章的代码都是按这个分层放的。拿到源码以后先别急着跑按这个目录画一张模块图心里就有了底后面改哪里、加哪里都不会迷路。2.2 角色权限与业务边界三个角色各能干什么自助选课系统的核心词是“自助”意思是学生自己完成选课和退课不需要管理员插手。所以业务边界要先分清不然写代码的时候很容易把功能堆到一起。角色可用功能不能做的学生登录、浏览课程列表、选课、退课、查看个人课表不能改课程容量不能替别人选课教师登录、查看自己任教的课程、查看选课学生名单不能替学生选课不能改容量管理员登录、维护学生和教师信息、管理课程容量与排课信息不能直接替学生选课权限这套东西落到代码里其实就是登录成功后把role字段存在内存里在每个service方法入口判断一下当前用户的角色。这个项目不走Spring Security那套全靠入口判断。好处是代码短坏处是方法多了容易漏。我一般会在User类里加一个role字段然后写一个checkRole工具方法三行代码的事别在每一个按钮的点击事件里复制粘贴判断逻辑。业务边界确定后选课主流程就很好描述了学生登录后看到课程列表列表里每个课程带容量、已选人数、时间、地点、任课教师点选课先判断是否已选过该课、是否时间冲突、是否达到学分上限全部通过就向select_course表插入一条记录同时把course表的已选人数加一。退课是反向操作删除记录、人数减一。这一整套流程就是论文里需求分析部分的用例图也是第3章代码要回答的问题。有一点要提醒拿到源码第一步先跑一遍sql目录下的init.sql确认库名、表名、字段名和代码里写的一致。很多“跑不起来”的问题最后查出来都是库名对不上。3. 核心功能实现登录、选课事务与时间冲突检测3.1 登录模块PreparedStatement与连接资源管理登录是所有模块的入口代码逻辑最短但最容易被问三个问题为什么不用Statement、连接为什么用try-with-resources、密码存明文怎么办。前两个问题在登录的DAO代码里就能答。public User login(String username, String password) { String sql SELECT id, username, password, role FROM user WHERE username ? AND password ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, username); ps.setString(2, password); try (ResultSet rs ps.executeQuery()) { if (rs.next()) { User u new User(); u.setId(rs.getInt(id)); u.setUsername(rs.getString(username)); u.setRole(rs.getString(role)); return u; } } } catch (SQLException e) { System.out.println(login failed: e.getMessage()); } return null; }这里的要点在第2行到第4行。PreparedStatement先把SQL模板发给MySQL做预编译再用setString把参数按类型传进去引号、单引号都会被当作普通字符串处理。如果改成Statement拼接字符串用户名里输入一个 or 11整张表都能查出来。这是答辩老师考察频率极高的一个点也是写论文时“安全性设计”那一节的核心素材。try-with-resources写法在Java 7以后才有它保证conn、ps、rs三个资源即使抛出异常也会自动关闭。课程设计里常见的错误是只关Connection、忘了关ResultSet跑一会儿程序就报“Too many connections”。我用的DBUtil还是DriverManager.getConnection没有引入连接池因为课程设计的并发量用不到连接池但连接资源一定要关干净。登录成功后用户对象怎么在多个窗口之间传递Swing没有HttpSession我一般会写一个简单的内存Context类里面放一个静态的User currentUser登录成功时赋值退出登录时置空。对课程设计来说够用了不用为此去翻框架源码。3.2 选课逻辑事务边界与容量判断选课是整个系统的心脏。我见过很多“能跑”的版本选课方法就是三段拼在一起先查已选人数再判断容量最后insert。这种代码能应付演示但经不起问。下面这个写法把事务边界拉开了也是论文里“详细设计”部分最值得贴的片段。public boolean selectCourse(int studentId, int courseId) { Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); String countSql SELECT COUNT(*) FROM select_course WHERE course_id ?; int selected queryInt(conn, countSql, courseId); String capSql SELECT capacity FROM course WHERE id ?; int capacity queryInt(conn, capSql, courseId); if (selected capacity) { conn.rollback(); return false; } String dupSql SELECT 1 FROM select_course WHERE student_id ? AND course_id ?; if (exists(conn, dupSql, studentId, courseId)) { conn.rollback(); return false; } String insertSql INSERT INTO select_course(student_id, course_id, select_time) VALUES (?, ?, NOW()); try (PreparedStatement ps conn.prepareStatement(insertSql)) { ps.setInt(1, studentId); ps.setInt(2, courseId); if (ps.executeUpdate() ! 1) { conn.rollback(); return false; } } conn.commit(); return true; } catch (SQLException e) { rollbackQuietly(conn); System.out.println(selectCourse failed: e.getMessage()); return false; } finally { closeQuietly(conn); } }这里queryInt和exists是DBUtil里封装好的辅助方法分别负责查询单行单列、查询记录是否存在代码里不用反复写PreparedStatement那一套。选课失败时返回的boolean会直接传到界面上弹窗提示“容量已满”或者“不能重复选课”。这个写法的价值有两个。第一setAutoCommit(false)把事务边界拉开后面的SQL要么一起提交要么一起回滚不会出现select_course里插入了记录、course表人数没更新的情况。第二三次查询在同一个Connection上执行看到的是同一个事务视图比每次调用都重新开连接要一致得多。但必须说实话这段代码在并发下依然有漏洞。两个学生同时提交线程A查完selected等于29还没执行insert线程B也查到了29两个都进入insert最终变成31个人。事务解决的是失败回滚锁或者原子更新解决的才是并发超选。我在第5章会专门讲这个坑的解法这里先记住结论。3.3 时间冲突与学分上限两个容易被丢掉的业务规则容量之外选课系统最容易出问题的业务规则是时间冲突和学分上限。时间冲突的经典错误是只比对了星期几忽略了节次区间是二维的。下面这个判断方法可以直接抄进service层public boolean checkTimeConflict(Connection conn, int studentId, int courseId) { String targetSql SELECT day_of_week, start_section, end_section FROM course WHERE id ?; String selectedSql SELECT c.day_of_week, c.start_section, c.end_section FROM select_course sc JOIN course c ON sc.course_id c.id WHERE sc.student_id ?; try (PreparedStatement tp conn.prepareStatement(targetSql); PreparedStatement sp conn.prepareStatement(selectedSql)) { tp.setInt(1, courseId); sp.setInt(2, studentId); Course target; try (ResultSet tr tp.executeQuery()) { if (!tr.next()) return true; target new Course(); target.setDayOfWeek(tr.getInt(day_of_week)); target.setStartSection(tr.getInt(start_section)); target.setEndSection(tr.getInt(end_section)); } try (ResultSet sr sp.executeQuery()) { while (sr.next()) { int day sr.getInt(day_of_week); int start sr.getInt(start_section); int end sr.getInt(end_section); if (day target.getDayOfWeek() start target.getEndSection() end target.getStartSection()) { return true; } } } return false; } catch (SQLException e) { System.out.println(checkTimeConflict failed: e.getMessage()); return true; } }冲突判断的核心就一行同一星期几新课程的start_section小于等于已选课程的end_section同时新课程的end_section大于等于已选课程的start_section也就是newStart oldEnd newEnd oldStart。很多同学只写了day相同就判冲突结果上午一节、下午一节的课被误伤或者只比较start_section相等漏掉了1-2节和2-3节这种跨节次重叠。学分上限的规则一般在student表里存一个credit_limit选课前把该生已选课程的credit累加加上新课程的credit超过上限就拒绝。逻辑不复杂放在service层。我要提醒的是这两个业务规则务必写在service层而不是UI层因为论文学的是分层思想代码也要真按这个来。不要为了图省事把判断塞进按钮的ActionListener里那样答辩抽问时很难自圆其说。4. 数据库设计与论文写作五张表和一个答辩能讲的故事4.1 表结构设计冗余字段、唯一约束与外键策略数据库设计是论文里最好画图也最好拿分的地方。这个项目的核心表通常就五张student、teacher、course、select_course、admin。每张表的字段我整理了一份最常见的定义。表名关键字段用途说明studentid, student_no, name, password, major, class_name学生信息student_no唯一teacherid, teacher_no, name, password, department教师信息用于登录和课程归属courseid, course_no, name, teacher_id, capacity, selected_count, credit, day_of_week, start_section, end_section, classroom课程信息selected_count是冗余字段select_courseid, student_id, course_id, select_time选课关系表adminid, username, password管理员登录三张业务表加一张选课关系表再加一张管理员表关系很清晰。我一般会在course表里放一个selected_count字段每次选课成功加一、退课成功减一。这个字段是冗余的但对界面展示“剩余名额”非常友好不用每次都走COUNT聚合。论文里可以说成“以空间换时间”但必须保证它和select_course里的真实记录一致唯一的办法就是和插入、删除选课记录放在同一个事务里。select_course表上的唯一约束是整张表设计里最重要的一行建表SQL值得单独贴一张CREATE TABLE select_course ( 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), CONSTRAINT fk_sc_student FOREIGN KEY (student_id) REFERENCES student(id), CONSTRAINT fk_sc_course FOREIGN KEY (course_id) REFERENCES course(id) );代码层面的重复选课检查可能漏但数据库唯一约束不会。凡是多对多关系表加唯一约束是数据库设计的基本功论文里写数据库设计时提一句能加分。还有一个关键选择删除课程时select_course里的关联记录怎么办如果数据库里设置了外键并且加了ON DELETE CASCADE删课程会自动清掉选课记录如果没有外键就要在代码里先删选课记录、再删课程。源码里到底是哪种做法不重要重要的是你自己知道项目是哪种答辩老师会顺着这条线往下问。4.2 论文素材怎么从源码里提炼ER图、流程图与测试用例拿到源码后写论文最容易犯的错是把所有代码截图往Word里一贴就交差。查重是一回事关键是答辩时老师一问怎么设计就哑了。每一章都能从源码里反向提炼出来。论文章节素材来源写法建议需求分析角色权限表 选课主流程转成用例图和用例描述总体设计项目目录的view/service/dao分层画三层架构图和模块划分图详细设计登录、选课、冲突检测的核心方法只贴方法片段配流程说明数据库设计五张表的字段定义画ER图标注主键和外键测试手工验证记录做成测试用例表格带预期结果ER图不需要会用那些复杂工具MySQL Workbench的Reverse Engineering能自动从数据库生成ER图再导出图片放进论文。如果工具版本不稳定直接在Word里用表格和形状手工画也可以。流程图就按选课流程画登录→查看课程列表→判断重复选课→判断时间冲突→判断容量→插入选课记录→结束。这张图是论文里老师一眼就会扫到的地方方向一定不要画反。测试部分是最省力气又最拉分的。把常见操作列成表格每条写步骤、预期结果、实际结果比如选一门容量已满的课预期提示“容量已满”实际是不是真的这么反馈。这样一个简单表格就把“做了测试”这件事坐实了比写几千字性能分析有用得多。5. 避坑与常见问题排查做完这个项目后我整理的五个坑5.1 ClassNotFoundException驱动jar没有真正加进项目现象运行LoginFrame一按登录按钮就报java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver可lib目录里明明放着jar。原因IDEA里把jar拖进lib不算引入要右键Add as Library才算。更隐蔽的一个版本是打成可执行jar时lib目录没有一起打包课堂编码阶段没事答辩前想打包演示时当场爆掉。解决打开Project Structure → Modules → Dependencies确认里面有mysql-connector-java。打包时用Artifacts把lib目录的jar复制到输出目录并在MANIFEST里配好Class-Path。我现在的习惯是新建项目第一件事先写一个DBUtil测试连接能跑通再写业务代码驱动问题早暴露早解决。5.2 MySQL时区报错连接串少了serverTimezone现象连数据库时报The server time zone value Öйú±ê׼ʱ¼ä is unrecognized后面跟着一串乱码。看着很玄学其实和字符集没关系。原因MySQL 8.0以上默认时区是SYSTEMJDBC驱动需要明确知道用哪个时区。课程设计碰到这个提示九成是连接串写得太省。解决JDBC URL改成下面这种写法public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/course_selection ?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8; private static final String USER root; private static final String PASSWORD 改成你自己的密码; public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }URL里带的characterEncodingutf8是第5.4条的伏笔后面出现中文乱码时要先检查这里。serverTimezone参数只需要在DBUtil里写一遍所有模块都会跟着好。5.3 并发超选两个线程同时抢最后一个名额现象两个账号同时选同一门只剩一个名额的课两个都提示选课成功最后课程人数和选课记录对不上。原因第3.2节说过SELECT COUNT和INSERT之间有时间窗口。两个线程同时进来都读到selected小于capacity于是都执行了插入。同一个事务也解决不了这个问题除非查询时把课程行锁住或者把人数更新改成原子的。解决推荐用一条UPDATE扛下核心判断让数据库的行锁帮我们做决策。String updateSql UPDATE course SET selected_count selected_count 1 WHERE id ? AND selected_count capacity; try (PreparedStatement ps conn.prepareStatement(updateSql)) { ps.setInt(1, courseId); int rows ps.executeUpdate(); if (rows ! 1) { conn.rollback(); return false; } } String insertSql INSERT INTO select_course(student_id, course_id, select_time) VALUES (?, ?, NOW()); try (PreparedStatement ps conn.prepareStatement(insertSql)) { ps.setInt(1, studentId); ps.setInt(2, courseId); if (ps.executeUpdate() ! 1) { conn.rollback(); return false; } }这条UPDATE利用数据库行锁selected_count小于capacity时影响行数为1容量满了影响行数为0。两个线程同时执行只有一个能拿到那一行。然后再插入选课记录整个操作放在同一个事务里。源码里如果用的是先查后插答辩时主动指出这一点并给出这个改进会是很加分的一笔。5.4 中文乱码建库、连接串、界面三层都要查现象课程名称在数据库命令行里显示正常界面上全是问号或者反过来界面正常命令行乱码。原因字符集不统一。建库时用了默认latin1连接串没带characterEncoding或者Swing窗口字体不支持中文。三个环节任一出问题都会乱。解决建库时就定好字符集用CREATE DATABASE course_selection DEFAULT CHARACTER SET utf8mb4连接串带characterEncodingutf8界面字体用dialog这类系统字体。不要在代码里写new String(bytes, GBK)去手动转码转来转去只会更乱。排查顺序是先看表结构再看连接参数最后看代码里有没有硬编码编码转换。5.5 论文查重与答辩追问别把代码整段贴进Word现象论文查重率30%以上其中一大半是代码块贡献的。答辩时老师指着流程图问“这里为什么这么画”你答不上来。原因源码整段粘进Word查重系统把代码当成文本比对重复率蹭蹭往上涨。代码占的篇幅太大设计思路写得太少老师没得可问也懒得看。解决正文只保留关键方法的片段控制在15到25行之间其余用文字描述核心逻辑可以画成流程图或者伪代码。流程图用Word自带的形状画不要从别的论文截图。查重不是目的把设计讲清楚才是。我自己写论文时代码截图几乎不用全部换成方法签名加核心三行的文字描述效果反而更好。6. 从能跑到能答辩验证清单与三个进阶改造6.1 答辩前按这张清单过一遍拿到源码改完以后不要只看“能启动”就放心。按下面这张清单过一遍每一行都要亲自点一下序号测试操作预期结果1用错误的密码登录提示账号或密码错误不跳转2选一门容量未满的课已选列表出现该课程人数加一3重复选同一门课提示不能重复选课4选同一时间段的两门课提示时间冲突5退课后重新选名额释放可以再选这张表直接抄进论文的测试章节也没问题。演示的时候照着这个顺序点一遍顺手、连续不会出现点完选课不知道下一步该点什么的尴尬。6.2 三个值得动手的进阶改造第一个改造是把课表导出成Excel。用Apache POI的XSSFWorkbook遍历已选课程按星期写入单元格答辩现场导出一份课表视觉效果比任何架构图都直观。有人问过我用POI能不能生成图表课表这种场景用不到图表做点简单的Excel报表倒是顺手的。第二个改造是把明文密码改成MD5加盐存储。登录时不比对明文而是对输入做同样的散列再比较。这和前面PreparedStatement防注入形成两个安全点论文里可以单独写一小节“系统安全性设计”。第三个改造是并发控制。如果源码里依然是先查后插的写法就按第5.3节的方案改成UPDATE原子操作这是整个项目里代码质量分最关键的一个改动。我自己的教训是第一次拿到这类源码时想的是赶紧跑起来截图交差结果答辩现场被一句“选课冲突你怎么解决的”问住脸一直红到讲台下面。从那以后我每次拿到别人写的项目都强制自己先画出事务边界和模块图再打开代码找那条UPDATE或者INSERT最后才去碰界面。这套流程救过我很多次也希望你能用上。希望帮到你。本文还有配套的精品资源点击获取
返回列表