ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

JSP+SQL网上选课系统:从数据库设计到部署优化指南

JSP+SQL网上选课系统:从数据库设计到部署优化指南 简介JSPSQL网上选课系统是一套面向毕业设计、课程设计及个人技术提升的完整项目资料适合计算机专业学生、Java Web学习者以及需要快速搭建选课功能原型的小型项目团队。资源将源代码、毕业论文与答辩PPT整合打包涵盖登录权限、学生信息管理、教师管理、课程信息管理与选课记录等核心模块便于对照代码理解JSPServletSQL模式下的典型交互流程。压缩包共482个文件大小约18.77MB主体包含70个jsp页面、4个java源文件及30个class编译文件另有62个bak备份文件、11个db数据库文件以及doc论文、ppt答辩文档和大量gif、jpg界面图片素材目录按功能模块组织结构清晰便于按需检索与复用。该套资源已有217人学习下载对完成毕业设计论文、准备答辩PPT以及理解传统Java Web项目从数据库设计到前端展示的完整链路均有较强的参考价值。1. 为什么网上选课系统是JSPSQL的“全栈”试金石网上选课系统这个题目出现在高校课程设计、Java Web期末项目、甚至不少社招笔试面试的项目描述里原因很简单它把前端页面、后端状态管理、数据库事务、并发控制这几块硬骨头压缩到了一个“看起来很小”的系统里。你以为是在做增删改查实际要回答的是“一个学生同时点击两门课为什么只有一门能选上”这种生产级问题。源码包里的JSP页面、SQL脚本、论文和答辩PPT本质是把这段实践经验打包成可验证的成果。这套东西值得拆解因为它能直接迁移到选座、预约、抢单类业务。2. JSPSQL选课系统的表结构设计与JDBC连接方案2.1 学生、课程、选课三张表怎么建才不冗余网上选课系统的核心数据关系是“学生-课程”的多对多。常见做法是拆成三张表student、course、student_course。选课记录表不自带业务字段而是把学生ID和课程ID做成联合主键顺便携带选课时间和状态字段。CREATE TABLE student ( sid INT PRIMARY KEY AUTO_INCREMENT, sid_no VARCHAR(20) UNIQUE NOT NULL, -- 学号用户登录名 sname VARCHAR(50) NOT NULL, spassword VARCHAR(64) NOT NULL, -- 存哈希别存明文 sclass VARCHAR(50), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE course ( cid INT PRIMARY KEY AUTO_INCREMENT, cname VARCHAR(100) NOT NULL, teacher VARCHAR(50) NOT NULL, capacity INT NOT NULL DEFAULT 0, -- 课程容量 selected_count INT NOT NULL DEFAULT 0, -- 已选人数冗余字段 credit DECIMAL(3,1), course_time VARCHAR(100), -- 例如“周一3-4节” location VARCHAR(100) ); CREATE TABLE student_course ( id INT PRIMARY KEY AUTO_INCREMENT, sid INT NOT NULL, cid INT NOT NULL, select_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_sid_cid (sid, cid), CONSTRAINT fk_sc_student FOREIGN KEY (sid) REFERENCES student(sid), CONSTRAINT fk_sc_course FOREIGN KEY (cid) REFERENCES course(cid) );这段设计里有两个容易被忽略的参数。第一个是selected_count它是冗余列用来在选课时快速判断“容量是否已满”避免实时COUNT(*)带来的锁竞争。第二个是UNIQUE KEY uk_sid_cid它在数据库层杜绝同一学生重复选同一门课比在代码里先查再插更可靠。外键约束在课程设计阶段可以保留但如果你在答辩时主动说出“线上环境通常去掉外键靠应用层保证一致性”会显得比只会建表熟练得多。2.2 JDBC连接SQL Server还是MySQL参数有什么讲究标题里写的是SQL但实际交付包里最常见的是MySQL脚本其次是SQL Server。两种数据库的JDBC驱动类名和URL格式差异很大踩坑点也完全不一样。// MySQL 8.x 连接代码 Class.forName(com.mysql.cj.jdbc.Driver); String url jdbc:mysql://localhost:3306/course_system?useUnicodetruecharacterEncodingUTF-8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue; Connection conn DriverManager.getConnection(url, root, password);// SQL Server 2019 连接代码 Class.forName(com.microsoft.sqlserver.jdbc.SQLServerDriver); String url jdbc:sqlserver://localhost:1433;DatabaseNamecourse_system;encrypttrue;trustServerCertificatetrue;; Connection conn DriverManager.getConnection(url, sa, password);serverTimezoneAsia/Shanghai是MySQL 8必带的少了它会在取时间字段时报时区错误。allowPublicKeyRetrievaltrue是MySQL 8.0.11之后的问题当用户使用caching_sha2_password插件且没有SSL时客户端要求服务端提供公钥不设置这个参数就会连接失败。SQL Server这边encrypttrue是2019版本默认推荐的但如果你是老项目跑在新驱动上必须配合trustServerCertificatetrue否则自签名证书会直接掐断连接。2.3 防SQL注入的预处理语句写法JSP项目里最常见的低级错误是字符串拼接SQL。许多同学会写成SELECT * FROM student WHERE sid_no username 这在答辩演示时没问题但如果面试官追问“你怎么防SQL注入”这句话就是扣分点。正确做法是使用PreparedStatement让参数走占位符。String sql SELECT * FROM student WHERE sid_no ? AND spassword ?; PreparedStatement ps conn.prepareStatement(sql); ps.setString(1, username); ps.setString(2, passwordHash); ResultSet rs ps.executeQuery();PreparedStatement的效果不是简单的转义引号而是把参数当作数据传给数据库引擎在SQL编译阶段就不会把参数内容拼接进语句结构。热词里那些“sql注入万能密码绕过”用这种写法天然免疫。还有一个隐性好处同一条SQL用不同参数执行多次时数据库可以复用执行计划对选课这种高频操作明显更快。3. 实现选课、退课、冲突检测的JSP核心代码3.1 查询可选课程的JSP页面与分页SQL选课系统的首页通常是“当前学期可选课程列表”。如果不想把所有课程一次性塞进页面分页是必考题。JSP脚本里直接用SELECT * FROM course WHERE selected_count capacity是最朴素的做法但数据量一上来就慢在查询缓存和网络传输上。我在实际项目中习惯同时带上LIMIT和OFFSET并计算总页数。int page 1; int pageSize 10; if (request.getParameter(page) ! null) { page Integer.parseInt(request.getParameter(page)); } String countSql SELECT COUNT(*) FROM course WHERE selected_count capacity; String pageSql SELECT * FROM course WHERE selected_count capacity ORDER BY cid LIMIT ? OFFSET ?; PreparedStatement countPs conn.prepareStatement(countSql); ResultSet countRs countPs.executeQuery(); countRs.next(); int totalRecords countRs.getInt(1); int totalPages (totalRecords pageSize - 1) / pageSize; PreparedStatement ps conn.prepareStatement(pageSql); ps.setInt(1, pageSize); ps.setInt(2, (page - 1) * pageSize); ResultSet rs ps.executeQuery();分页参数里LIMIT和OFFSET的顺序不能反这是MySQL的固定语法。SQL Server则不同它需要用OFFSET ... ROWS FETCH NEXT ... ROWS ONLY所以同一个系统如果要兼容两个数据库分页代码就得抽成接口。这里也顺便回应热词里“sql server 2019安装教程”“sql server 2008 r2下载”这类检索意图——本地开发环境怎么装其实不是难点难在把不同数据库方言的JDBC行为摸清。3.2 选课事务先查容量再插入避免超额一个学生点击“选课”按钮后端要做的校验是课程是否还存在、容量是否已满、该学生是否已经选过。三者之间有并发窗口所以必须用事务包住一段逻辑并且把“容量判断”和“插入记录”放在同一个事务里。Connection conn dataSource.getConnection(); try { conn.setAutoCommit(false); conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED); // 锁住课程行防止两个事务同时读到剩余容量1 String checkSql SELECT selected_count, capacity FROM course WHERE cid ? FOR UPDATE; PreparedStatement checkPs conn.prepareStatement(checkSql); checkPs.setInt(1, courseId); ResultSet rs checkPs.executeQuery(); if (!rs.next()) { throw new Exception(课程不存在); } int selected rs.getInt(selected_count); int capacity rs.getInt(capacity); if (selected capacity) { throw new Exception(容量已满); } // 插入选课记录靠联合唯一索引兜底 String insertSql INSERT INTO student_course (sid, cid) VALUES (?, ?); PreparedStatement insertPs conn.prepareStatement(insertSql); insertPs.setInt(1, studentId); insertPs.setInt(2, courseId); insertPs.executeUpdate(); // 更新冗余计数 String updateSql UPDATE course SET selected_count selected_count 1 WHERE cid ?; PreparedStatement updatePs conn.prepareStatement(updateSql); updatePs.setInt(1, courseId); updatePs.executeUpdate(); conn.commit(); } catch (Exception e) { conn.rollback(); throw e; } finally { conn.setAutoCommit(true); conn.close(); }SELECT ... FOR UPDATE是这串代码的关键。它把课程行加上排他锁让第二个选课事务必须等第一个提交后才能判断容量。如果不加这个锁两个并发请求同时读到selected_count20、capacity20然后各自判断“未满”结果就是超额选课。热词里那条“java线程等待都完成”与此类似不过数据库层的锁等待比线程join更隐蔽一旦锁持有时间过长会出现高等待我后面会讲怎么排查。3.3 退课与容量回补以及session作用域陷阱退课相对简单但同样需要事务。先删除student_course记录再把course.selected_count减一。顺序上建议先删子表再更新父表减少锁的持有时间。另一个常见坑是在JSP里使用session保存当前登录学生信息退课时却从request.getParameter(sid)拿学生ID。如果这个参数被恶意篡改就能帮别人退课。稳妥做法是每次从session里读学生ID不依赖请求参数。Integer sid (Integer) session.getAttribute(loginSid); if (sid null) { response.sendRedirect(login.jsp); return; }session的细节在理想情况下不应成为业务逻辑的一部分但JSP课程设计阶段的评分点往往包含“登录后状态保持”。把学生ID写入session不算错但要记住session默认存储在服务器内存中Tomcat重启就会丢。如果系统真正上线就必须换成Redis保存会话序列化否则所有登录态同时失效这个过程还能在答辩时说成“基于Redis的分布式会话方案”。4. 把源码、论文和答辩PPT整理成能下分的交付物4.1 Tomcat下部署JSP项目的最小步骤拿到别人的“源代码论文答辩PPT”最常见的问题是部署不上。经验是先把环境固定在特定软件版本上再谈代码。我用的是Tomcat 8.5、JDK 1.8、MySQL 5.7这个组合兼容性最好不会撞上Jakarta命名空间的问题。# 1. 编译并打包 mvn clean package -DskipTests # 2. 将war包拷入Tomcat的webapps目录 cp target/course-system.war /opt/tomcat/webapps/ # 3. 启动Tomcat /bin/sh /opt/tomcat/bin/startup.sh # 4. 查看日志确认无异常 tail -f /opt/tomcat/logs/catalina.out部署之后必须在浏览器访问http://服务器IP:8080/course-system/如果返回404先看catalina.out里的报错不要急着改代码。我从经验里提取一个规律九成启动失败是因为MySQL驱动版本和Tomcat的类加载机制冲突。解决办法是把mysql-connector-java的jar包放到Tomcat的lib目录而不是项目本身的WEB-INF/lib这样能让所有应用共享同一个版本也避免jar包重复加载导致的ClassNotFoundException。4.2 论文里必须写清楚的技术点ER图、用例图、测试用例论文部分的评分点不在字数而在你是否把系统设计讲清楚。需要重点呈现的是ER图它跟前面三张表一一对应但要实体、属性和联系画规范。其次是用例图至少画出学生、教师、管理员三个角色并标出选课、退课、查看课表、管理课程、审核选课等用例。测试用例表是论文里最能体现严谨性的部分我建议用表格列出正常选课、超容量选课、重复选课、未登录选课这四个场景每条记录输入步骤和预期结果。用例编号操作场景输入数据预期结果TC-001正常选课学生A课程C1选课成功selected_count加1TC-002超容量选课学生B容量已满的课程提示“容量已满”事务回滚TC-003重复选课学生A再次选择课程C1提示“已选过该课程”记录不插入TC-004未登录直接访问选课URL无session重定向到login.jsp这个表格也是答辩时的高频提问区。面试官或评委通常不会逐行读代码而是问“并发选课怎么办”。你把TC-002和上一章的FOR UPDATE代码对应起来讲他们就会认为你是真的做过调试而不是只接了别人的工程。4.3 答辩PPT的页面结构与演示设计答辩PPT的常规结构是背景与目标、技术选型、系统架构、数据库设计、功能截图、测试结果、总结展望。我不建议把PPT做成代码粘贴本一页最多放一个核心代码片段和一段说明。真正的加分项是“演示设计”也就是你计划在答辩现场如何用五分钟把系统跑通。我的习惯是先准备一个“演示账号密码表”说明哪个账号登录学生端、哪个账号登录管理员端并提前把某门课调到剩余1个名额。答辩时先演示正常选课再立刻开两个浏览器窗口同时对这最后一个名额发起选课当场展示只有一个成功。这段演示比口头说“我做了事务处理”有说服力得多而且在视觉上非常直观。5. 上线前必调的5个参数和3个坑5.1 连接池参数与tomcat-jdbc配置JSP项目里如果直接用DriverManager.getConnection每次请求都会重新创建物理连接在高并发下数据库会直接被打满。正确做法是用连接池Tomcat内置了tomcat-jdbc在context.xml里配置就行。Resource namejdbc/courseDB authContainer typejavax.sql.DataSource factoryorg.apache.tomcat.jdbc.pool.DataSourceFactory driverClassNamecom.mysql.cj.jdbc.Driver urljdbc:mysql://localhost:3306/course_system?serverTimezoneAsia/Shanghai usernameroot passwordpassword initialSize5 maxActive20 maxIdle10 minIdle5 maxWait10000 testOnBorrowtrue validationQuerySELECT 1 /initialSize是启动时创建的连接数maxActive是最大活跃连接数maxWait是获取连接的等待毫秒数。testOnBorrowtrue和validationQuerySELECT 1一起可以确保从池里拿到的连接没有被数据库主动断开。MySQL默认闲置8小时会断掉连接如果不加这两个参数线上会出现“连接失效”的报错重启后能缓一会儿过段时间又复发。5.2 重复选课、并发选课锁策略调整上一章我用SELECT ... FOR UPDATE锁住了课程行但在高并发场景下这种悲观锁会让所有选同一门课的操作串行化。如果想要更高的吞吐量可以把selected_count的判断改成乐观锁做法执行更新时带上版本号或旧值条件。UPDATE course SET selected_count selected_count 1 WHERE cid ? AND selected_count capacity;这个UPDATE单独执行时是原子操作数据库会自己判断条件是否满足受影响行数为0就表示选课失败。它和INSERT一样需要放到事务里但配合student_course表的唯一索引就能在绝大多数场景下避免锁等待。字段selected_count capacity里的两个值都是从数据库实时读出的不存在代码里的过期缓存。5.3 慢SQL排查从EXPLAIN看索引是否命中选课系统的慢查询主要集中在两处student_course表越大按sid查已选课程越慢course表按course_time筛选时也可能全表扫描。排查时用EXPLAIN看执行计划确认是否用到索引。EXPLAIN SELECT * FROM student_course WHERE sid 1 AND cid 2;理想结果里type字段至少是ref或eq_refrows值要远小于表的行数。如果看到typeALL就说明没有走索引。最实用的解法是给student_course加联合索引(sid, cid)这个索引同时覆盖了“查某学生所有选课”和“查某学生是否选过某门课”两个查询路径。索引不是越多越好因为每次INSERT和DELETE都要维护索引选课表的写入量本来就不小加两个以上非必要索引反而拖慢事务提交。热词里那批“慢sql优化”问题更多是生产环境的索引设计问题。课程设计阶段的数据量只有几百条看不出差距但你可以主动构造一万条选课记录再跑一遍EXPLAIN把优化前后的执行时间贴进论文的测试部分这对答辩是直接加分项。回到交付包本身拿到“JSPSQL网上选课系统”源码后我建议你第一件事不是看代码而是替换所有数据库连接信息里的弱口令第二件事是检查WEB-INF/web.xml里的session-timeout。这个session-timeout参数单位是分钟默认可能写的是60分钟或-1表示永不过期。如果你把系统部署到公网服务器上记得改成30防止别人的电脑闲置一上午还挂着你的登录态。第三件事是把tomcat-users.xml里的管理员账号密码一并改掉否则Tomcat默认的管理接口一旦暴露整个文件夹都会被别人打包下载。本文还有配套的精品资源点击获取
返回列表