
简介这是一套面向JavaWeb初学者与课程设计学习者的教务管理系统项目源码基于MVC设计模式与DAO模式搭建采用Servlet、JSP、Filter与MySQL数据库实现运行于Windows系统下的Tomcat 7与JDK1.8环境。系统按角色划分为登录、学生、教师、教务员与系统管理员四大部分涵盖登录、找回与修改密码、注销以及学生成绩查询、选修与考级报名、学籍信息查看修改、教师个人信息维护、教务员成绩管理与报名名单管理、管理员用户管理等功能结构完整适合作为课程设计或毕业设计的参考模板。资源包共223个文件包含82个class、82个java、38个jsp、6个jar及若干xml、sql与配置文件压缩包约2.6MB源码与编译产物齐全导入Eclipse并配置数据库即可运行。目前已有11656人学习下载。下载后建议先阅读README.txt了解启动流程项目仍有部分功能待完善便于在此基础上继续扩展新模块是理解JavaWeb分层开发与数据库交互的实用练手素材。1. 教务管理系统 JavaWeb 项目从源码到跑通我踩过的那些坑很多同学做毕设或者课程设计一搜“教务管理系统 javaweb”出来的全是付费源码或者残缺不全的压缩包。我拿到这份资源的时候第一反应是终于有个能跑通的了。它是一套基于原生 JavaWeb 技术栈Servlet JSP JDBC的教务管理系统没有过度封装代码结构清晰适合拿来改毕设、做课程设计或者单纯想搞懂 MVC 分层是怎么回事的人。核心功能覆盖了学生管理、教师管理、课程管理、选课退课、成绩录入这几个教务场景里最常被要求实现的模块。数据库用的是 MySQL前端是 JSP 加一点 jQuery部署在 Tomcat 上。如果你正在找一份能直接导入 IDEA 跑起来的 JavaWeb 完整案例这份东西值得你花时间拆一遍。我前后在三个不同环境里部署过它下面把整个流程和踩过的坑一次说清楚。2. 环境搭建与项目导入IDEA 配置和数据库初始化2.1 技术栈选型与版本匹配这套项目没有用 SpringBoot是纯 Servlet JSP 的经典 JavaWeb 结构。为什么这点重要因为现在网上很多“JavaWeb 项目完整案例”其实都是 SpringBoot 套壳你拿过来发现根本看不懂 Servlet 的映射逻辑。这份资源保留了web.xml配置和注解混合的方式对理解 JavaWeb 请求流转很有帮助。我用的环境组合是JDK 1.8、Tomcat 8.5、MySQL 5.7、IDEA 2021.3。这几个版本是经过验证能跑通的。JDK 不建议上 11 或 17因为项目里有些地方用了javax.servlet而不是jakarta.servletTomcat 10 会直接报 404。MySQL 5.7 和 8.0 在 JDBC 驱动类名上有区别5.7 用com.mysql.jdbc.Driver8.0 用com.mysql.cj.jdbc.Driver这个后面配置的时候要注意。项目导入 IDEA 的步骤不复杂但有几个地方容易翻车。先File - New - Project from Existing Sources选到项目根目录IDEA 会自动识别出是 Web 项目。然后去Project Structure里检查三件事SDK 是不是 1.8Web模块下的Web Resource Directory有没有指向src/main/webappArtifacts里有没有生成war exploded。这三步缺一个Tomcat 启动后就是 404。2.2 数据库建表与连接配置数据库初始化是第一个大坑。项目里一般会带一个.sql文件但很多资源包里的 SQL 文件编码是 GBK直接导入会乱码。我一般先用Notepad打开转成 UTF-8 无 BOM 格式再去 MySQL 里执行。-- 创建数据库字符集必须指定 utf8mb4 CREATE DATABASE school_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; -- 切换到该数据库 USE school_system; -- 导入表结构注意检查 sql 文件里的外键约束顺序 -- 如果导入报错 1215先把外键检查关掉 SET FOREIGN_KEY_CHECKS 0; SOURCE /path/to/school_system.sql; SET FOREIGN_KEY_CHECKS 1;执行完 SQL 之后去项目里找数据库配置文件。通常叫db.properties或者直接在DBUtil.java里硬编码。我见过最坑的情况是作者把密码写成了root但你的 MySQL 密码是123456启动就报Access denied。# db.properties 典型配置 jdbc.drivercom.mysql.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/school_system?useUnicodetruecharacterEncodingutf8useSSLfalse jdbc.usernameroot jdbc.password你的实际密码这里useSSLfalse在 MySQL 5.7 以上版本必须加否则控制台会一直刷警告。characterEncodingutf8不加的话中文姓名存进去就是问号。改完配置在 IDEA 里右键pom.xml或者lib目录确认mysql-connector-java的 jar 包已经加到Artifacts的WEB-INF/lib下。很多人代码没问题就是启动报ClassNotFoundException九成是 jar 包没部署进去。2.3 Tomcat 部署与上下文路径Tomcat 配置里有一个细节Application context建议设成/或者/school。如果设成/school那你访问的地址就是http://localhost:8080/school/login.jsp。我习惯设成/这样直接http://localhost:8080/login.jsp就能进。启动之前检查web.xml里的welcome-file-list。有些项目默认欢迎页是index.jsp但实际登录页叫login.jsp你访问根路径就会看到目录列表或者 404。改一下就行welcome-file-list welcome-filelogin.jsp/welcome-file /welcome-file-list启动 Tomcat 后如果控制台没有报错但页面样式全丢那是静态资源被DispatcherServlet或者过滤器拦截了。这份项目里如果用了CharacterEncodingFilter一般只拦截/*不会影响 css 和 js。但如果你的web.xml里配了多个过滤器注意url-pattern别写成/*把静态资源也拦了。3. 核心模块拆解登录鉴权、选课逻辑与成绩录入3.1 登录鉴权与 Session 管理登录模块是理解整个项目鉴权机制的入口。代码逻辑很直白LoginServlet接收用户名密码调用UserDao查库查到了就把User对象塞进session然后重定向到main.jsp。但这里有一个经典漏洞很多学生写的项目只做了登录没做拦截。你直接在浏览器地址栏输http://localhost:8080/main.jsp照样能进。正确的做法是加一个LoginFilter// LoginFilter.java 核心逻辑 public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; String uri request.getRequestURI(); // 放行登录页、登录接口和静态资源 if (uri.endsWith(login.jsp) || uri.endsWith(LoginServlet) || uri.contains(/css/) || uri.contains(/js/)) { chain.doFilter(req, resp); return; } // 检查 session 中是否有用户 Object user request.getSession().getAttribute(user); if (user null) { response.sendRedirect(request.getContextPath() /login.jsp); } else { chain.doFilter(req, resp); } }然后在web.xml里注册这个过滤器url-pattern写/*。这样任何未登录的请求都会被踢回登录页。参数说明request.getContextPath()拿到的是项目上下文路径避免硬编码/school导致换环境就失效。Session 超时时间也建议在web.xml里显式设置默认 30 分钟但有些 Tomcat 默认是 30 分钟没错可如果你在session里存了大量对象内存吃紧的时候会被动失效。设成 60 分钟比较稳妥session-config session-timeout60/session-timeout /session-config3.2 选课模块的并发问题与事务处理选课是教务系统里最考验代码质量的地方。这份项目里选课逻辑大概是先查课程余量如果大于 0就插入选课记录然后更新课程余量减一。问题来了如果两个学生同时选同一门课余量只剩 1两个线程都查到余量是 1都去插入记录最后余量变成 -1。这是典型的并发超卖。解决办法有两种一是用数据库行锁SELECT ... FOR UPDATE二是在更新余量的时候加条件UPDATE course SET remain remain - 1 WHERE id ? AND remain 0然后判断受影响行数。// 选课核心代码用乐观锁方式防止超卖 public boolean selectCourse(int studentId, int courseId) { Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 开启事务 // 第一步扣减余量带条件更新 String updateSql UPDATE course SET remain remain - 1 WHERE id ? AND remain 0; PreparedStatement ps conn.prepareStatement(updateSql); ps.setInt(1, courseId); int affected ps.executeUpdate(); if (affected 0) { conn.rollback(); return false; // 余量不足选课失败 } // 第二步插入选课记录 String insertSql INSERT INTO sc (student_id, course_id) VALUES (?, ?); ps conn.prepareStatement(insertSql); ps.setInt(1, studentId); ps.setInt(2, courseId); ps.executeUpdate(); conn.commit(); return true; } catch (Exception e) { if (conn ! null) try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } e.printStackTrace(); return false; } finally { DBUtil.close(conn); } }参数说明setAutoCommit(false)开启事务保证扣减余量和插入记录要么都成功要么都回滚。remain 0这个条件放在UPDATE的WHERE里利用数据库的行级锁保证原子性。如果affected 0说明余量已经被别人扣完了直接回滚返回失败。这套逻辑比先查再改靠谱得多也是面试里常问的“如何防止超卖”的标准答案。3.3 成绩录入与分页查询实现成绩录入模块本身逻辑不复杂就是UPDATE sc SET score ? WHERE student_id ? AND course_id ?。但这里有一个权限校验容易被忽略教师只能录入自己教的课程成绩。如果前端传过来的course_id不属于当前登录教师后端必须拦截。// 成绩录入前的权限校验 public boolean hasPermission(int teacherId, int courseId) { String sql SELECT COUNT(*) FROM course WHERE id ? AND teacher_id ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, courseId); ps.setInt(2, teacherId); ResultSet rs ps.executeQuery(); if (rs.next()) { return rs.getInt(1) 0; } } catch (SQLException e) { e.printStackTrace(); } return false; }分页查询是另一个高频需求。学生列表、成绩列表都需要分页。这份项目里用的是LIMIT偏移量方式SELECT * FROM student LIMIT ?, ?第一个参数是偏移量(pageNum - 1) * pageSize第二个参数是每页条数。注意 MySQL 的LIMIT偏移量越大性能越差数据量上万以后要改用WHERE id ? LIMIT ?的方式。不过教务系统一般学生数量在几千以内LIMIT完全够用。分页还需要一个计算总页数的查询SELECT COUNT(*) FROM student然后在 Java 里totalPages (totalCount pageSize - 1) / pageSize。这个公式里的 pageSize - 1是为了向上取整比如 101 条数据每页 10 条算出来是 11 页而不是 10 页。4. 避坑与排查从 404 到乱码的五个血泪教训4.1 启动报 404web.xml 版本与注解冲突现象Tomcat 启动无报错但访问任何页面都是 404。 原因web.xml的version属性写的是 2.3 或 2.4而代码里用了WebServlet注解。Servlet 3.0 以下不支持注解容器会忽略注解配置。 解决把web.xml的version改成 3.1metadata-complete设为false或者直接删掉这个属性。web-app xmlnshttp://xmlns.jcp.org/xml/ns/javaee xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd version3.14.2 中文乱码请求参数与响应编码现象表单提交的中文姓名存到数据库变成???或者页面显示乱码。 原因Tomcat 8 默认的URIEncoding是 UTF-8但POST请求体的编码取决于request.setCharacterEncoding()有没有调用。GET请求在 Tomcat 8 以后默认 UTF-8但有些老项目在 Tomcat 7 上跑就会乱。 解决在CharacterEncodingFilter里统一设置request.setCharacterEncoding(UTF-8)和response.setContentType(text/html;charsetUTF-8)。数据库连接 URL 里加characterEncodingutf8。JSP 页面顶部加% page contentTypetext/html;charsetUTF-8 languagejava %。三处都对齐乱码基本就没了。4.3 JDBC 连接报错驱动类与时区问题现象java.sql.SQLException: The server time zone value ?D1ú±ê×?ê±?? is unrecognized。 原因MySQL 8.0 的驱动需要指定时区不指定就报这个错。 解决连接 URL 里加serverTimezoneAsia/Shanghai。完整 URL 长这样jdbc.urljdbc:mysql://localhost:3306/school_system?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai如果是 MySQL 5.7不加serverTimezone也能跑但加上没坏处。4.4 静态资源 404过滤器拦截了 css 和 js现象登录页能打开但样式全无F12 看控制台一堆 404。 原因LoginFilter的url-pattern配了/*把/css/login.css和/js/jquery.js也拦截了未登录状态下直接重定向到登录页导致静态资源加载失败。 解决在过滤器里放行静态资源路径或者把静态资源放到webapp/static/目录下过滤器里判断uri.contains(/static/)就放行。4.5 数据库连接池耗尽Connection 未关闭现象系统跑一段时间后报Too many connections或者页面卡死。 原因Dao层代码里Connection、PreparedStatement、ResultSet没有在finally块里关闭每次请求都泄漏一个连接。 解决用try-with-resources语法自动关闭或者老老实实在finally里close()。如果项目里用了DBCP或C3P0连接池检查配置文件里的maxActive和maxWait参数别设太大也别设太小一般maxActive20够用了。5. 二次开发与进阶技巧从能跑到好用5.1 把 JDBC 换成 MyBatis 的渐进式改造这份项目用的是原生 JDBC代码里到处是PreparedStatement和ResultSet的手工映射。如果你想把它改造成更现代的写法又不想推翻重来可以渐进式引入 MyBatis。先加mybatis和mybatis-spring的依赖然后写一个mybatis-config.xml把DBUtil替换成SqlSessionFactory。原来的UserDao接口不用动写一个UserMapper.xml把 SQL 搬过去就行。!-- mybatis-config.xml 核心配置 -- configuration environments defaultdevelopment environment iddevelopment transactionManager typeJDBC/ dataSource typePOOLED property namedriver valuecom.mysql.jdbc.Driver/ property nameurl valuejdbc:mysql://localhost:3306/school_system?useUnicodetrueamp;characterEncodingutf8amp;useSSLfalse/ property nameusername valueroot/ property namepassword value你的密码/ /dataSource /environment /environments mappers mapper resourcemapper/UserMapper.xml/ /mappers /configuration改造的时候注意MyBatis 的#{}和${}区别很大#{}是预编译占位符能防 SQL 注入${}是字符串拼接能不用就不用。原来 JDBC 里ps.setInt(1, id)的地方在 Mapper 里写成#{id}就行。5.2 用 Filter 实现操作日志记录教务系统里谁改了成绩、谁退了课最好有日志。加一个LogFilter在chain.doFilter前后记录请求路径、参数、耗时和当前登录用户。日志可以写到文件也可以插到数据库的operation_log表里。// LogFilter 核心片段 long start System.currentTimeMillis(); chain.doFilter(req, resp); long cost System.currentTimeMillis() - start; HttpServletRequest request (HttpServletRequest) req; Object user request.getSession().getAttribute(user); String username user ! null ? ((User) user).getUsername() : anonymous; String uri request.getRequestURI(); String query request.getQueryString(); // 写入日志表或文件 System.out.println(String.format([%s] %s %s?%s 耗时%dms, username, request.getMethod(), uri, query, cost));这个 Filter 放在LoginFilter之后执行保证拿到的user是已经登录的。日志表建一个id、username、operation、params、cost、create_time就够了。5.3 前端页面用 Bootstrap 快速美化JSP 页面默认样式比较朴素如果毕设需要演示花半小时套个 Bootstrap 就能提升一个档次。去 Bootstrap 官网下载编译好的 css 和 js放到webapp/static/下然后在 JSP 的head里引入。link relstylesheet href${pageContext.request.contextPath}/static/css/bootstrap.min.css script src${pageContext.request.contextPath}/static/js/jquery-3.6.0.min.js/script script src${pageContext.request.contextPath}/static/js/bootstrap.min.js/script注意${pageContext.request.contextPath}这个 EL 表达式它能动态获取项目上下文路径避免你换 Tomcat 配置后路径写死导致 404。表格加classtable table-striped按钮加classbtn btn-primary表单加classform-control基本就顺眼了。5.4 验证部署是否成功的三个检查点部署完之后别急着点页面按这三步走一遍能省很多排查时间。第一步看 Tomcat 启动日志里有没有Deployment of web application archive成功字样有SEVERE或ERROR先解决。第二步访问http://localhost:8080/看能不能跳到登录页跳不过去就查welcome-file-list和LoginFilter。第三步用错误密码登录一次看有没有提示再用正确密码登录看能不能进主页。这三步过了基本功能就没问题。从那以后我每次拿到一份 JavaWeb 源码都强制自己先跑一遍这三步检查再去看业务代码。很多所谓的“跑不起来”其实都是环境配置没对齐。希望帮到你。本文还有配套的精品资源点击获取