JavaWeb学生选课系统实战:从MVC架构到高并发处理的完整实现
简介这是一套基于JavaWeb技术栈开发的学生选课管理系统完整实战项目面向计算机专业毕业设计学生及JavaWeb初学者解决课程管理、角色权限控制与多端业务协同等典型Web应用开发问题。资源包共112个文件含22个Java源码如StudentServlet、CourseDao等核心类、21个JSP页面覆盖登录、选课、成绩录入等全流程视图、5个CSS/5个JS前端资源集成Bootstrap与jQuery实现响应式交互以及MySQL数据库脚本.sql和Tomcat部署所需配置文件整体压缩包仅2.39MB轻量易导入。已有551人学习下载项目经严格调试支持IDEA/EclipseJDK1.8TomcatNavicat环境一键运行。读者可直接获得三层架构清晰、MVC职责分明的可运行源码涵盖管理员、教师、学生三角色全功能模块包括学生信息维护、课程分配、成绩录入与查询等真实业务逻辑是理解Servlet生命周期、JDBC连接池、会话控制与权限过滤器LoginFilter的理想实践样本。1. 项目概述一个典型的JavaWeb选课系统意味着什么最近在整理硬盘翻出来一个压箱底的老项目——一个用JavaWeb技术栈实现的学生选课管理系统。这玩意儿虽然技术栈不算新潮但麻雀虽小五脏俱全从数据库设计到前后端交互再到业务逻辑的完整闭环它都涵盖了。对于刚学完Java基础、Servlet/JSP想找个完整项目练手或者需要快速搭建一个课程管理原型的朋友来说这种“源码数据库”齐全的项目价值就在于它提供了一个可运行、可剖析、可二次开发的完整样本。简单说这个系统核心就干几件事学生能查课、选课、退课老师能开课、管理学生名单、录入成绩管理员则负责最底层的用户、课程信息维护。听起来简单但里面涉及的技术点非常密集如何用JDBC或MyBatis优雅地操作MySQL数据库如何用Servlet处理复杂的业务逻辑并实现MVC分层如何用JSP或简单的HTMLAjax构建用户界面如何管理用户会话Session和实现权限控制每一个环节拆开看都是JavaWeb入门到进阶的必经之路。我当年做这个项目就是为了把书本上零散的知识点像Servlet生命周期、JSP九大内置对象、数据库连接池串起来形成一个实际可用的东西。你会发现很多在理论课上模糊的概念一旦放到一个需要你从头到尾实现的功能里立刻就清晰了。比如为什么要把数据库连接写成工具类因为几乎每个操作数据库的Servlet都要用不封装代码就冗余到没法看。再比如用户登录后信息存Session里那不同的角色学生、老师、管理员访问同一个页面比如课程列表看到的内容和能做的操作为什么不一样这就引出了权限过滤器的设计。所以如果你手头正好有这样一个项目的完整源码和数据库脚本别只把它当成一个可以“一键运行”的黑盒。它的真正价值在于你可以像解剖一只麻雀一样从需求分析、表结构设计到每一行后台代码、每一个前端页面去理解一个典型信息管理系统是如何被构建出来的。接下来我就结合这个“学生选课管理系统”把其中关键的技术实现、设计思路以及我踩过的那些坑掰开揉碎了讲给你听。2. 系统核心设计与架构拆解2.1 需求分析与功能模块划分任何项目的第一步都是搞清楚要做什么。对于一个学生选课系统我们可以从三类核心用户的角度来梳理需求学生端核心需求信息查看查看所有开设的课程信息名称、老师、学分、时间、容量、已选人数等。选课与退课在选课开放时间内选择自己感兴趣的、且时间不冲突、名额未满的课程同样在规定时间内可以退选已选课程。个人课表与成绩查询查看自己当前学期或历史学期的选课列表形成个人课表并能查询已结课课程的成绩。教师端核心需求课程管理发布新的课程需管理员审核或直接开设管理自己名下已开设课程的基本信息。学生名单管理查看选择自己课程的学生名单通常不具备强制踢出学生的权限需遵循选课规则。成绩录入在课程结束后为选修该课程的学生录入或修改成绩。管理员端核心需求系统基础数据管理管理学生、教师、管理员自身的账户信息增删改查。课程总览与审核审核教师提交的新开课申请管理所有课程的生命周期如设置选课时间段、结课等。数据统计与导出查看选课统计报表必要时可进行数据备份或导出。基于以上需求系统的功能模块可以清晰地划分为用户管理模块、课程信息管理模块、选课/退课业务模块、成绩管理模块以及系统管理模块。这种划分方式直接影响了后续的数据库表设计和包Package结构规划。2.2 技术栈选型与MVC架构实现对于一个经典的JavaWeb项目技术栈的选择往往比较固定但也体现了从基础到框架的演进。后端核心Servlet JSP。这是JavaWeb的基石。Servlet作为控制器Controller接收所有HTTP请求处理业务逻辑JSP作为视图View负责渲染动态HTML页面。很多教学项目为了简化会在JSP中嵌套大量Java代码Scriptlet但这不利于维护。更好的实践是严格遵循MVC让JSP只负责显示所有逻辑由Servlet和JavaBeanModel完成。数据持久层JDBC。直接使用JDBC进行数据库操作有助于深刻理解SQL执行、连接管理、事务控制等底层原理。通常会封装一个DBUtil工具类来管理数据库连接使用DBCP或C3P0连接池是必须的能显著提升性能并封装通用的增删改查方法。如果项目想进阶可以引入MyBatis来替代纯JDBC用XML或注解管理SQL会使代码更清晰。数据库MySQL。轻量、开源、应用广泛是学习型项目的首选。需要重点设计好表结构和关系。前端基础的HTML、CSS、JavaScript结合JSP的EL表达式和JSTL标签库来简化页面逻辑。对于异步交互可以使用原生JavaScript的XMLHttpRequest或更现代的Fetch API也可以引入jQuery的$.ajax来简化操作。服务器Apache Tomcat。最常用的Servlet容器轻量、易配置。整个项目的代码组织包结构应该体现分层思想。例如com.xxx.course.system ├── filter // 过滤器如编码过滤器、登录验证过滤器 ├── listener // 监听器可选 ├── servlet // 所有Servlet控制器可按模块分包子目录 │ ├── admin │ ├── student │ └── teacher ├── service // 业务逻辑层接口和实现 ├── dao // 数据访问层接口和实现 ├── entity // 实体类JavaBean与数据库表对应 ├── util // 工具类如DBUtil、StringUtil └── test // 测试类这种结构虽然简单但清晰地分离了关注点为后续引入Spring等框架打下了良好基础。2.3 数据库设计表结构与关系建模数据库设计是系统的基石设计的好坏直接影响程序的复杂度和性能。核心表通常包括用户表 (user / account)存储所有系统用户学生、教师、管理员。通常包含id主键、username登录名、password加密存储、real_name、role角色如student, teacher, admin、status状态等字段。一种设计是将三类用户放在一张表用role区分另一种是分表设计。单表设计查询简单但字段可能冗余分表设计更符合现实但关联查询稍多。教学项目用单表加role字段更常见。课程表 (course)存储课程信息。course_id主键、course_name、teacher_id外键关联教师、credit学分、total_capacity总容量、selected_count已选人数需与选课表统计值同步或作为冗余字段、time上课时间如“周一 1-2节”、location、description等。选课表 (student_course / selection)这是核心的关系表。id主键、student_id外键、course_id外键、selection_time选课时间、score成绩初始为null。(student_id, course_id)组合必须建立唯一索引防止同一学生重复选择同一门课程。成绩表 (score)有时可与选课表合并。但如果一门课程有多次考核平时成绩、期末成绩则需要独立设计。score_id、selection_id外键关联选课记录、score_type、value等。关键设计思考已选人数 (selected_count) 的更新这是一个高频并发点。不能每次都去选课表做COUNT(*)查询效率太低。通常作为课程表的一个冗余字段在选课/退课成功时通过数据库事务原子性地更新这个计数字段。例如选课SQL序列1. 查询课程容量和已选人数 - 2. 判断是否可选 - 3. 插入选课记录 - 4. 课程表已选人数1这四步必须在一个事务里。时间冲突检查学生选课时需要检查新选课程的上课时间是否与已选课程冲突。这需要在业务逻辑层Service实现获取该学生所有已选课程的time信息与新课程的time进行比对。时间字段的设计要便于比较比如存储为“星期几节次”的编码如“12”表示周一第1-2节。密码存储绝对禁止明文存储密码必须使用哈希算法如MD5、SHA-256加盐Salt后存储。即使使用MD5加盐也能极大提升安全性。3. 核心功能实现细节与代码剖析3.1 用户登录、会话管理与权限控制这是系统安全的门户实现上需要注意以下几点登录流程前端login.jsp提交用户名和密码POST请求密码字段需加密传输前端可做MD5后端仍需加盐哈希。LoginServlet接收请求调用UserService的login方法。UserService中根据用户名从数据库查询用户信息。先判断用户是否存在且状态正常。密码校验将用户提交的密码经过前端加密后的与数据库中存储的盐salt拼接再进行一次哈希运算将结果与数据库中存储的哈希值比对。切勿直接比对加密字符串// 伪代码示例密码校验 public boolean checkPassword(String inputPassword, String storedHash, String salt) { String hashedInput DigestUtils.md5Hex(inputPassword salt); // 使用Apache Commons Codec return hashedInput.equals(storedHash); }验证通过后将用户关键信息如user_id,username,role存入HttpSession对象中。HttpSession session request.getSession(); session.setAttribute(currentUser, userObject); // userObject是查询到的实体类 session.setAttribute(userRole, userObject.getRole()); // 设置会话超时时间可选在web.xml中配置全局默认 // session.setMaxInactiveInterval(30 * 60); // 30分钟根据用户角色role重定向response.sendRedirect()到对应的主页面如student/index.jsp。权限控制使用过滤器Filter是实现全局权限验证的最佳实践。创建一个AuthFilter在web.xml中配置其拦截的URL模式如/*拦截所有请求但需放行登录、注册、静态资源等。在AuthFilter的doFilter方法中检查当前请求的Session里是否存在用户信息。如果不存在则判断请求路径是否为需要登录的路径如果是则重定向到登录页。更细粒度的角色控制可以在Filter中进一步检查比如访问/admin/*路径的用户其Session中的role必须为admin否则跳转到无权限页面。// AuthFilter 伪代码片段 HttpServletRequest req (HttpServletRequest) request; HttpServletResponse resp (HttpServletResponse) response; HttpSession session req.getSession(false); // 获取现有session不创建新session String path req.getRequestURI(); // 放行公开资源登录、注册、CSS/JS/图片等 if (path.contains(/login) || path.contains(/static/)) { chain.doFilter(request, response); return; } if (session null || session.getAttribute(currentUser) null) { resp.sendRedirect(req.getContextPath() /login.jsp); return; } // 角色校验示例检查管理员路径 User user (User) session.getAttribute(currentUser); if (path.contains(/admin/) !admin.equals(user.getRole())) { resp.sendRedirect(req.getContextPath() /error/403.jsp); return; } chain.doFilter(request, response); // 放行实操心得与避坑指南Session超时与安全性务必在web.xml中配置Session超时时间session-configsession-timeout30/session-timeout/session-config并考虑用户主动退出时调用session.invalidate()清空Session。密码传输安全即使在局域网或开发环境也应养成好习惯。前端提交密码前可以用JS进行MD5加密但这仍不是HTTPS中间人攻击可截获MD5值。最安全的方式是部署HTTPS。验证码为防止暴力破解登录页面应增加图形验证码功能。将生成的验证码字符串存入Session提交登录时进行比对。SQL注入防范在DAO层使用PreparedStatement来执行所有SQL切勿用字符串拼接。这是铁律。3.2 选课与退课的业务逻辑与并发处理这是系统的业务核心逻辑复杂且对并发敏感。选课流程Servlet (SelectCourseServlet)参数获取与校验从请求中获取student_id可从Session取和course_id。进行基础的非空校验。业务逻辑层调用调用CourseSelectionService.selectCourse(studentId, courseId)。Service层核心逻辑 a.检查是否已选查询选课表是否存在该学生和课程的记录。 b.检查课程状态查询课程表确保课程处于可选状态如未满额、在选课时段内。 c.检查时间冲突获取该学生所有已选且未结课的课程时间与待选课程时间比对。 d.执行选课以上检查均通过后开启数据库事务。 e.插入选课记录向选课表插入一条新记录。 f.更新课程已选人数执行UPDATE course SET selected_count selected_count 1 WHERE course_id ? AND selected_count total_capacity。注意这个UPDATE语句的WHERE条件里包含了容量检查这是一个利用数据库原子性实现的乐观锁是防止超卖的关键g.判断更新结果检查上一步SQL执行后影响的行数executeUpdate()的返回值。如果为1说明更新成功课程名额-1如果为0说明在检查通过到执行UPDATE的瞬间课程已被别人选满选课失败。 h. 根据g的结果提交或回滚事务。结果返回将选课成功或失败的原因“成功”、“课程已满”、“时间冲突”、“已选过此课程”封装成JSON或设置到Request属性中转发到结果页面。退课流程与选课类似但更简单。主要检查是否在退课时间内然后在一个事务内删除选课记录并将课程selected_count减1。注意减1操作也要防止负数UPDATE course SET selected_count selected_count - 1 WHERE course_id ? AND selected_count 0。高并发场景下的深度思考 上述流程中的“检查后执行”模式在低并发下没问题但在高并发选课时会出大问题。想象一下课程还剩最后1个名额同时有100个请求通过了“检查课程状态”都看到selected_count99 capacity100然后都去执行插入和更新最终会导致严重超卖。解决方案就是利用数据库的悲观锁或乐观锁悲观锁行锁在事务开始时使用SELECT ... FOR UPDATE锁定要操作的课程记录。这样其他事务必须等待当前事务完成。这种方式保证强一致性但并发性能差容易造成死锁。START TRANSACTION; SELECT * FROM course WHERE course_id ? FOR UPDATE; -- 锁定该行 -- 然后进行检查和更新操作 COMMIT;乐观锁推荐这就是我们上面采用的方案在UPDATE语句的WHERE条件中加入容量判断。它假设冲突不常发生先执行操作再通过数据库返回的影响行数来判断是否成功。性能更好但需要客户端处理更新失败的情况通常提示用户“操作失败请重试”。这是电商防超卖、选课防超额的经典做法。3.3 数据库操作层DAO的封装与事务管理直接在每个Servlet中写JDBC代码是灾难。必须进行分层封装。DBUtil工具类负责提供数据库连接从连接池获取和关闭资源Connection, Statement, ResultSet的方法。关闭资源务必放在finally块中确保异常时也能释放。public class DBUtil { private static DataSource dataSource; // 连接池数据源 static { // 初始化连接池例如使用Apache DBCP BasicDataSource ds new BasicDataSource(); ds.setDriverClassName(com.mysql.cj.jdbc.Driver); ds.setUrl(jdbc:mysql://localhost:3306/course_system?useUnicodetruecharacterEncodingUTF-8serverTimezoneAsia/Shanghai); ds.setUsername(root); ds.setPassword(password); ds.setInitialSize(5); ds.setMaxTotal(20); dataSource ds; } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } public static void close(Connection conn, Statement stmt, ResultSet rs) { // 反向关闭 rs - stmt - conn try { if (rs ! null) rs.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (stmt ! null) stmt.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (conn ! null) conn.close(); } // 注意这里是放回连接池不是真正关闭 } }BaseDAO与泛型可以编写一个基础的BaseDAO封装通用的增删改查方法其他具体DAO如UserDAOImpl,CourseDAOImpl继承它。使用泛型和反射可以写出更通用的方法但对于初学者明确写出每个方法也更清晰。// 一个简单的查询示例UserDAO public User findUserByUsername(String username) { String sql SELECT * FROM user WHERE username ? AND status active; Connection conn null; PreparedStatement pstmt null; ResultSet rs null; User user null; try { conn DBUtil.getConnection(); pstmt conn.prepareStatement(sql); pstmt.setString(1, username); rs pstmt.executeQuery(); if (rs.next()) { user new User(); user.setId(rs.getInt(id)); user.setUsername(rs.getString(username)); user.setPasswordHash(rs.getString(password_hash)); user.setSalt(rs.getString(salt)); user.setRole(rs.getString(role)); // ... 设置其他字段 } } catch (SQLException e) { e.printStackTrace(); throw new RuntimeException(数据库查询失败, e); // 抛出自定义运行时异常 } finally { DBUtil.close(conn, pstmt, rs); } return user; }事务管理在Java Web中事务通常以数据库连接Connection为单位。确保多个DAO操作如先insert选课记录再update课程人数使用同一个Connection并且将其autoCommit设置为false在所有操作成功后手动commit()失败则rollback()。public class CourseSelectionService { public boolean selectCourse(int studentId, int courseId) { Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 开启事务 // 1. 检查逻辑... (使用同一个conn创建PreparedStatement) // 2. 插入选课记录 SelectionDAO selectionDao new SelectionDAOImpl(conn); // 将conn传入DAO selectionDao.insert(studentId, courseId); // 3. 更新课程人数 CourseDAO courseDao new CourseDAOImpl(conn); int affectedRows courseDao.incrementSelectedCount(courseId); if (affectedRows 1) { conn.commit(); // 提交事务 return true; } else { conn.rollback(); // 回滚事务 return false; } } catch (SQLException e) { if (conn ! null) { try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } throw new RuntimeException(选课事务执行失败, e); } finally { DBUtil.close(conn, null, null); // 关闭连接放回连接池 } } }注意这种手动管理事务的方式在复杂业务中容易出错。Spring框架的声明式事务管理Transactional是更优雅的解决方案但理解底层手动管理的方式至关重要。4. 前端页面交互与用户体验优化虽然核心是后端但一个友好的前端能让项目增色不少。4.1 使用JSTL与EL表达式净化JSP避免在JSP中写大量% ... %脚本。使用JSTLJSP Standard Tag Library和ELExpression Language来简化逻辑。% taglib prefixc urihttp://java.sun.com/jsp/jstl/core % % taglib prefixfmt urihttp://java.sun.com/jsp/jstl/fmt % !-- 循环显示课程列表 -- table c:forEach items${courseList} varcourse tr td${course.courseName}/td td${course.teacherName}/td td${course.credit}/td td${course.selectedCount} / ${course.totalCapacity}/td td c:choose c:when test${course.selectedCount course.totalCapacity} button disabled已满/button /c:when c:when test${studentHasSelectedThisCourse} button disabled已选/button /c:when c:otherwise button onclickselectCourse(${course.courseId})选课/button /c:otherwise /c:choose /td /tr /c:forEach /table在Servlet中将数据列表放入Request或Session属性request.setAttribute(courseList, courseList);然后转发到JSP。4.2 利用Ajax实现无刷新交互对于选课、退课这类操作使用Ajax可以极大提升体验无需刷新整个页面。前端JSP/HTMLfunction selectCourse(courseId) { if (!confirm(确定要选择这门课程吗)) return; // 使用Fetch API (现代) 或 $.ajax (jQuery) fetch(SelectCourseServlet, { method: POST, headers: { Content-Type: application/x-www-form-urlencoded }, body: actionselectcourseId courseId }) .then(response response.json()) .then(data { if (data.success) { alert(选课成功); // 更新页面局部内容如按钮状态、已选人数 document.getElementById(count-courseId).innerText data.newSelectedCount; document.getElementById(btn-courseId).outerHTML button disabled已选/button; } else { alert(选课失败 data.message); } }) .catch(error { console.error(Error:, error); alert(网络请求失败); }); }后端Servlet需要将处理结果封装成JSON格式返回。response.setContentType(application/json;charsetUTF-8); PrintWriter out response.getWriter(); // 使用Gson或Jackson库将Java对象转为JSON字符串更规范 // 这里简单拼接 String json String.format({\success\: %b, \message\: \%s\, \newSelectedCount\: %d}, isSuccess, message, newCount); out.print(json); out.flush();4.3 数据分页与查询优化当课程或学生数据很多时必须分页显示。后端分页逻辑在DAO层查询方法需要接收pageNo页码从1开始和pageSize每页条数参数。使用SQL的LIMIT子句。-- MySQL 分页查询 SELECT * FROM course WHERE status open ORDER BY course_id LIMIT ?, ?; -- 第一个?是 offset计算方式(pageNo - 1) * pageSize -- 第二个?是 pageSize同时需要另一个查询来获取总记录数SELECT COUNT(*) FROM course WHERE status open。将ListCourse数据和总记录数、总页数等信息一起返回给前端。前端分页组件根据后端返回的总页数动态生成页码按钮。点击页码时重新向服务器发起请求并传递pageNo参数。5. 项目部署、测试与常见问题排查5.1 环境搭建与项目部署准备环境安装JDK8或11、MySQL5.7或8.0、Tomcat9.x、IDEIntelliJ IDEA或Eclipse。导入项目将源码导入IDE配置项目为Web项目并指定Tomcat作为运行服务器。初始化数据库运行提供的SQL脚本通常名为course_system.sql创建数据库、表和初始化数据如管理员账户。配置数据库连接修改项目中的数据库配置文件如db.properties或DBUtil类中的连接字符串、用户名和密码确保与本地MySQL环境一致。部署与运行在IDE中配置Tomcat部署将项目添加到服务器然后启动Tomcat。访问http://localhost:8080/你的项目名/login.jsp。5.2 常见问题与调试技巧以下是我在开发和教学过程中学生最常遇到的问题问题一启动Tomcat时报“ClassNotFoundException”或“NoClassDefFoundError”。原因项目缺少必要的JAR包如MySQL驱动mysql-connector-java-x.x.x.jar、数据库连接池commons-dbcp2-x.x.x.jar、commons-pool2-x.x.x.jar等。解决确保所有依赖的JAR包都被放在WEB-INF/lib/目录下并且被正确添加到项目的构建路径Build Path中。在IDEA中检查File - Project Structure - Modules - Dependencies在Eclipse中右键项目Build Path - Configure Build Path - Libraries。问题二访问JSP页面出现“500 Internal Server Error”后台报“java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver”。原因MySQL驱动类未找到。即使lib下有JAR也可能因为Tomcat没有正确加载。解决将MySQL驱动JAR包直接复制到Tomcat安装目录的lib文件夹下不推荐长期这样做最好通过项目依赖管理。或者在IDEA中确保在“Artifact”设置里这些JAR包被包含在输出的WAR包中。问题三中文乱码问题问号“???”或乱码。全方位解决数据库层面创建数据库时指定字符集为UTF-8CREATE DATABASE course_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;。表字段也应为UTF-8。连接字符串在JDBC URL中指定字符集jdbc:mysql://localhost:3306/course_system?useUnicodetruecharacterEncodingUTF-8serverTimezoneAsia/Shanghai。Servlet层面在doGet/doPost方法最开始处设置请求和响应的编码request.setCharacterEncoding(UTF-8); response.setCharacterEncoding(UTF-8); response.setContentType(text/html;charsetUTF-8); // 对于JSP输出JSP页面在页面顶部添加% page contentTypetext/html;charsetUTF-8 languagejava pageEncodingUTF-8%。HTML页面确保meta charsetUTF-8。问题四选课操作成功但页面显示未更新刷新后才看到变化。原因使用了同步请求表单提交或链接跳转页面整体刷新。或者Ajax请求成功后前端JavaScript没有正确更新DOM。解决采用Ajax异步请求并在成功回调函数中精确地更新页面中需要变化的部分如按钮文字、已选人数。问题五在IDE中运行正常但打成WAR包部署到独立Tomcat后出错。原因路径问题、依赖缺失、配置文件路径不对。排查检查WAR包内的WEB-INF/lib是否包含所有JAR。代码中读取配置文件如db.properties不要用绝对路径要用ServletContext.getResourceAsStream(/WEB-INF/classes/db.properties)。所有前端资源CSS, JS, 图片的引用路径使用相对路径或使用${pageContext.request.contextPath}如link href${pageContext.request.contextPath}/static/css/style.css。5.3 基础安全加固建议密码加密如前所述使用加盐哈希如BCrypt它自动处理盐存储密码。SQL注入坚持使用PreparedStatement。XSS跨站脚本攻击对用户输入如课程描述、评论进行过滤或转义后再显示到页面上。JSTL的c:out value${userInput}默认会进行HTML转义比直接使用${userInput}安全。Session固定攻击用户登录成功后使旧的Session失效生成一个新的SessionIDsession.invalidate(); request.getSession(true);。权限控制除了前端的菜单隐藏后端每一个Servlet或API接口入口都必须进行角色权限校验通过Filter或Interceptor。这个JavaWeb学生选课管理系统项目虽然技术栈传统但它像一块完整的拼图把JavaWeb的核心技术点都串联了起来。从需求分析、数据库设计、到MVC分层、事务控制、前端交互最后到部署调试走完整个流程你对一个Web应用是如何运作的会有非常立体和深刻的认识。拿到源码后不要满足于能跑起来尝试去修改它增加一个“课程评价”功能、把JDBC换成MyBatis、或者用Vue.js重写前端界面。在动手改造的过程中你学到的会比单纯看代码多十倍。本文还有配套的精品资源点击获取