JavaWeb同学录系统课程设计源码与三层架构完整拆解

发布时间:2026/10/16 3:23:50
JavaWeb同学录系统课程设计源码与三层架构完整拆解
简介这份压缩包是一份面向初学者的同学录系统期末课程设计采用 Java 语言进行网页开发覆盖后端处理、前端展示与数据库管理等多方面技术适合正在完成课程设计或练习分层架构的学习者参考。包内共有 48 个文件主要包含 12 个 Java 源代码与对应的编译后文件以及三个 JSP 页面、两个 SQL 建表脚本、两个配置文件和少量依赖库、样式脚本文件整个压缩包约一千零二十一 KB目录结构完整方便导入开发工具查看。目前已有四千三百七十一人学习说明这个项目具有较高的参考价值和实用性。通过阅读源码和数据库脚本可以理解用户登录会话管理、访问权限控制、前后端数据交互以及对象关系映射的写法同时也能学习到项目在不同环境下部署时可能遇到问题的解决思路。对于希望快速上手 Java Web 开发全流程的入门者而言这是一套很好的实践素材。1. 同学录系统JavaWeb期末课程设计的完整落地拆解又是一个期末季某高校软件工程专业的A同学对着「同学录系统」这个课程设计要求发愁。这个题目看着简单——无非是记录同学信息、留言、相册那一套但真正动手才发现从数据库表结构设计到前端页面交互再到部署上线每一步都有坑。更要命的是课程设计不仅要能跑还要能讲清楚设计思路答辩时老师随便问一个「你这个分页查询怎么做的」就能让人卡壳。这份JavaWeb同学录系统源码就是一套能直接交作业、也能让你真正理解JavaWeb开发全流程的完整项目。它不只是CRUD而是把Servlet、JSP、MySQL、前端框架串成了一条完整链路适合所有正在做JavaWeb课程设计的学生也适合想复习JavaWeb全流程的开发者。2. 系统架构与目录结构一个典型的JavaWeb分层项目长什么样2.1 技术选型为什么是JSP Servlet而不是Spring Boot这个项目没有用Spring Boot而是用最传统的JSP Servlet MySQL组合这是很多高校JavaWeb课程设计的标准配置。原因很简单课程设计考察的是你对JavaWeb底层机制的理解——请求怎么被Servlet接收、如何调用业务层、怎么用JDBC访问数据库、JSP怎么渲染数据。如果用Spring Boot很多细节会被框架屏蔽掉答辩时反而讲不清楚。项目整体采用经典的MVC三层架构Model层实体类Student、Message、ClassInfo等和DAO数据访问层View层JSP页面放在web目录下负责数据展示和表单交互Controller层Servlet接收请求、调用业务逻辑、控制页面跳转数据库用的是MySQL 5.7通过JDBC连接连接信息放在db.properties配置文件中。服务器用Tomcat 8.5或9.0整个项目打包成war包部署。2.2 目录结构详解每一层代码该放哪拿到源码后第一件事就是看目录结构。标准的JavaWeb项目非Maven结构目录是这样的classmate-system/ ├── src/ │ ├── com.classmate.dao/ # 数据访问层 │ │ ├── BaseDao.java # JDBC基础封装 │ │ ├── StudentDao.java # 同学信息DAO │ │ ├── ClassDao.java # 班级DAO │ │ └── MessageDao.java # 留言DAO │ ├── com.classmate.entity/ # 实体类 │ │ ├── Student.java │ │ ├── ClassInfo.java │ │ └── Message.java │ ├── com.classmate.service/ # 业务逻辑层 │ │ ├── StudentService.java │ │ └── MessageService.java │ ├── com.classmate.servlet/ # 控制器层 │ │ ├── LoginServlet.java │ │ ├── StudentServlet.java │ │ └── MessageServlet.java │ ├── com.classmate.util/ # 工具类 │ │ ├── DBUtil.java │ │ └── StringUtil.java │ └── db.properties # 数据库连接配置 ├── web/ │ ├── WEB-INF/ │ │ └── web.xml # 部署描述文件 │ ├── css/ # 样式文件 │ ├── js/ # 前端脚本 │ ├── images/ # 图片资源 │ ├── login.jsp # 登录页 │ ├── index.jsp # 首页/同学列表 │ ├── student_detail.jsp # 同学详情页 │ ├── message_board.jsp # 留言板 │ └── class_list.jsp # 班级列表有个Druid数据库连接池的开源组件在部分版本中会用到用于管理数据库连接。核心思路是把数据库连接的创建、关闭统一交给工具类管理避免每操作一次就new一个Connection。2.3 数据库设计五张表撑起整个系统数据库是项目的灵魂这份源码里的SQL脚本设计得比较合理。核心表包括admin管理员表id、username、password存放系统登录账号student同学信息表id、name、gender、birthday、phone、email、address、photo、class_id、remark记录同学基本信息class_info班级表id、class_name、grade维护班级维表message留言表id、student_id、content、create_time存储同学间的留言user用户表可选某些版本用于区分不同登录用户身份外键关系很清晰student通过class_id关联class_infomessage通过student_id关联student。这种设计在答辩时可以重点讲——为什么要分表、外键怎么用、联合查询怎么实现。3. 核心代码拆解从登录到同学信息管理的完整链路3.1 数据库连接DBUtil BaseDao的封装思路先看最底层的数据库连接工具类这是所有DAO操作的基础。源码里DBUtil.java通常长这样public class DBUtil { private static String driver; private static String url; private static String username; private static String password; static { try { // 加载配置文件 Properties props new Properties(); InputStream is DBUtil.class.getClassLoader() .getResourceAsStream(db.properties); props.load(is); driver props.getProperty(jdbc.driver); url props.getProperty(jdbc.url); username props.getProperty(jdbc.username); password props.getProperty(jdbc.password); // 加载驱动 Class.forName(driver); } catch (Exception e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(url, username, password); } public static void close(ResultSet rs, Statement stmt, Connection conn) { // 逆序关闭释放资源 if (rs ! null) { try { rs.close(); } catch (SQLException e) {} } if (stmt ! null) { try { stmt.close(); } catch (SQLException e) {} } if (conn ! null) { try { conn.close(); } catch (SQLException e) {} } } }这段代码的核心价值在于配置与代码分离。数据库的驱动、URL、账号密码都放在db.properties里换数据库环境时只改配置文件不动Java代码。静态代码块保证类加载时只读一次配置。另一个关键点是资源关闭。很多新手写JDBC时忘记关连接导致数据库连接数耗尽。DBUtil提供了统一的close方法在finally块中逆序关闭ResultSet、Statement、Connection这是标准写法。BaseDao则进一步封装了增删改查把通用的JDBC流程抽取出来public class BaseDao { protected Connection conn; protected PreparedStatement ps; protected ResultSet rs; /** * 通用的增删改方法 * param sql 预编译SQL * param params 可变参数按顺序填充占位符 */ protected int executeUpdate(String sql, Object... params) { int rows 0; try { conn DBUtil.getConnection(); ps conn.prepareStatement(sql); // 设置参数 for (int i 0; i params.length; i) { ps.setObject(i 1, params[i]); } rows ps.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); } finally { DBUtil.close(null, ps, conn); } return rows; } /** * 通用的查询方法返回结果集 */ protected ResultSet executeQuery(String sql, Object... params) { try { conn DBUtil.getConnection(); ps conn.prepareStatement(sql); for (int i 0; i params.length; i) { ps.setObject(i 1, params[i]); } rs ps.executeQuery(); } catch (SQLException e) { e.printStackTrace(); } return rs; } }用可变参数Object...接收占位符值配合PreparedStatement预编译既避免了SQL注入又让子类的DAO代码非常简洁。子类只需要写SQL语句和传参数即可。3.2 登录模块Session管理与字符编码处理登录是每个系统都有的模块但这个项目的登录Servlet写得有讲究。看核心代码WebServlet(/login) public class LoginServlet extends HttpServlet { private AdminDao adminDao new AdminDao(); protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 设置请求编码 request.setCharacterEncoding(UTF-8); // 设置响应编码 response.setContentType(text/html;charsetUTF-8); String username request.getParameter(username); String password request.getParameter(password); if (StringUtil.isEmpty(username) || StringUtil.isEmpty(password)) { request.setAttribute(msg, 用户名和密码不能为空); request.getRequestDispatcher(/login.jsp).forward(request, response); return; } Admin admin adminDao.findByUsernameAndPassword(username, password); if (admin ! null) { // 登录成功保存Session HttpSession session request.getSession(); session.setAttribute(admin, admin); // 重定向到首页避免刷新重复提交 response.sendRedirect(request.getContextPath() /student?actionlist); } else { request.setAttribute(msg, 用户名或密码错误); request.getRequestDispatcher(/login.jsp).forward(request, response); } } }这段代码涉及几个核心知识点。请求和响应编码必须放在最前面否则中文用户名会乱码。request.getParameter是获取表单数据但表单里的中文需要request.setCharacterEncoding(UTF-8)才能正确解析。Session保存登录状态后续页面通过session.getAttribute(admin)判断是否已登录。注意登录成功后用的是sendRedirect而不是forward转发。原因是重定向会发起新的请求浏览器地址栏会变成新的URL刷新页面时不会重复提交登录表单而forward转发留在原地址刷新时会再次执行登录逻辑。这是个很经典的细节答辩时能说出来会很加分。3.3 同学信息管理Servlet接收action参数实现增删改查同学信息管理是系统的核心模块。源码里用一个StudentServlet处理所有请求通过hidden字段或请求参数action来区分操作类型WebServlet(/student) public class StudentServlet extends HttpServlet { private StudentService studentService new StudentService(); protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String action request.getParameter(action); if (list.equals(action)) { // 分页查询 String pageNumStr request.getParameter(pageNum); String pageSizeStr request.getParameter(pageSize); int pageNum pageNumStr null ? 1 : Integer.parseInt(pageNumStr); int pageSize pageSizeStr null ? 10 : Integer.parseInt(pageSizeStr); PageBeanStudent pageBean studentService.findByPage(pageNum, pageSize); request.setAttribute(pageBean, pageBean); request.getRequestDispatcher(/index.jsp).forward(request, response); } else if (edit.equals(action)) { // 跳转到编辑页面 int id Integer.parseInt(request.getParameter(id)); Student student studentService.findById(id); request.setAttribute(student, student); request.getRequestDispatcher(/student_edit.jsp).forward(request, response); } else if (delete.equals(action)) { // 删除操作 int id Integer.parseInt(request.getParameter(id)); studentService.delete(id); response.sendRedirect(request.getContextPath() /student?actionlist); } } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); String action request.getParameter(action); if (add.equals(action)) { // 新增 Student student new Student(); // 手动封装参数 student.setName(request.getParameter(name)); student.setGender(request.getParameter(gender)); student.setBirthday(request.getParameter(birthday)); student.setPhone(request.getParameter(phone)); student.setEmail(request.getParameter(email)); studentService.add(student); response.sendRedirect(request.getContextPath() /student?actionlist); } else if (update.equals(action)) { // 修改 Student student new Student(); student.setId(Integer.parseInt(request.getParameter(id))); student.setName(request.getParameter(name)); // 其他字段相同 studentService.update(student); response.sendRedirect(request.getContextPath() /student?actionlist); } } }这种用一个Servlet集中处理同一种资源的所有操作的写法是JavaWeb课程设计的标准模式。与RESTful API不同这里通过action字符串区分操作场景。优缺点也明显代码都放在一个类里便于维护但方法会越写越长当业务复杂时可以拆成多个Servlet。其中分页查询是最值得讲的部分。PageBean封装了页码、每页条数、总记录数、当前页数据列表这几个关键属性。DAO层通过LIMIT ?,?实现数据库分页前端JSP通过c:forEach循环渲染列表并输出上一页/下一页链接。3.4 留言板模块一对多关联查询的实践留言板涉及两张表的关联操作。某条留言属于某个同学展示时要显示留言者的姓名和头像。核心SQL是SELECT m.*, s.name AS student_name, s.photo AS student_photo FROM message m LEFT JOIN student s ON m.student_id s.id ORDER BY m.create_time DESCLEFT JOIN保证即使留言关联的学生被删除留言记录依然能查出来只是学生姓名为NULL。这种写法在DAO层配合BaseDao的executeQuery方法把查询结果封装到Map或扩展实体类中返回给JSP展示。留言模块还需要处理表单提交后的重定向问题防止用户刷新页面导致重复留言。源码的做法和登录模块一致提交成功后response.sendRedirect到留言列表页使浏览器发起GET请求刷新也不会重复提交。3.5 拦截器与Filter登录权限控制的较量为了防止未登录用户直接访问内部页面很多版本里还加入了Filter过滤器WebFilter(/*) 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; // 获取请求路径 String path request.getRequestURI(); // 放行登录页和登录请求 if (path.contains(/login.jsp) || path.contains(/login) || path.endsWith(.css) || path.endsWith(.js) || path.endsWith(.jpg) || path.endsWith(.png)) { chain.doFilter(req, resp); return; } // 检查Session中是否有登录信息 HttpSession session request.getSession(); if (session.getAttribute(admin) ! null) { chain.doFilter(req, resp); } else { // 未登录重定向到登录页 response.sendRedirect(request.getContextPath() /login.jsp); } } }Filter拦截所有请求放行静态资源CSS、JS、图片和登录接口其余请求必须验证Session。如果不加这个过滤器用户直接访问/student?actionlist就能跳出登录页进入系统这就是很多课程设计被老师扣分的地方。需要注意Filter映射路径/*会包含Servlet路径例如/student会被过滤器拦截到所以在登录请求中一定要排除/login这个路径否则永远无法登录成功。4. 运行部署与踩坑记录从IntelliJ IDEA到Tomcat的完整流程4.1 环境准备与数据库初始化运行这个项目需要准备的软件环境是软件推荐版本用途JDK1.8编译运行Java代码Tomcat8.5或9.0Web服务器MySQL5.7或8.0数据库IntelliJ IDEA2020集成开发环境Navicat或MySQL Workbench任意数据库管理工具拿到源码后的第一步不是急着打开IDEA而是先把数据库建好。在MySQL中执行源码附带的classmate.sql脚本会自动创建数据库、数据表并插入初始测试数据。执行方式是打开命令行或者数据库管理工具运行mysql -u root -p classmate.sql执行完后用SHOW TABLES;验证看是否生成了五张表。4.2 IDEA Tomcat配置细节配置db.properties用记事本打开src目录下的db.properties改成自己本地数据库的账号密码jdbc.drivercom.mysql.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/classmate?useSSLfalsecharacterEncodingutf8 jdbc.usernameroot jdbc.password123456注意URL带上的characterEncodingutf8参数很重要不加的话写入数据库的中文会变成问号。导入IDEA选择File → New → Project from Existing Sources选中项目根目录IDEA会识别为Web项目。如果没自动识别右键web目录 → Add as Framework Support → JavaEE Web。配置TomcatRun → Edit Configurations → 点号 → Tomcat Server → Local。在Deployment选项卡下点号选Artifact把项目部署上去。Application context建议设为/classmate。启动Tomcat后浏览器访问http://localhost:8080/classmate/login.jsp使用初始账号admin/admin123登录。4.3 避坑指南五个最常翻车的问题和解决办法现象1启动Tomcat时报ClassNotFoundException: com.mysql.jdbc.Driver原因项目没有引入MySQL驱动jar包。解决下载mysql-connector-java-5.1.49.jar放进web/WEB-INF/lib目录下右键Add as Library。如果用了高版本MySQL 8.x驱动类要改成com.mysql.cj.jdbc.DriverURL要改成jdbc:mysql://localhost:3306/classmate?useSSLfalseserverTimezoneUTC。现象2登录页能打开但输入正确账号提示用户名或密码错误原因数据库里没有初始化管理员数据或者db.properties连的不是同一个数据库。解决先执行SQL脚本再用Navicat确认admin表里有没有数据。如果还是登录失败在LoginServlet的adminDao.findByUsernameAndPassword处打个断点看SQL查询是否返回了结果。现象3插入中文数据后数据库显示??原因URL没有指定characterEncodingutf8或者数据库表本身是latin1编码。解决改db.properties的URL加上编码参数如果表已经建好用ALTER TABLE语句把表编码改成utf8mb4。现象4JSP页面里老有红色报错但项目能跑起来原因IDEA没有引入Tomcat的servlet-api.jar。解决File → Project Structure → Libraries添加Tomcat的lib目录即可消除。现象5修改了JSP页面但刷新浏览器还是旧内容原因Tomcat默认不会热部署JSP。解决在IDEA的Tomcat配置里勾选Update resources on frame deactivation或者每次改完手动重启Tomcat实在不行就清理Tomcat的work目录缓存。提示遇到莫名其妙的问题时第一个动作是看Tomcat的catalina.out日志第二是看IDEA的Console面板第三是看数据库连接是否正常。90%的报错都能从这三处找到答案。5. 二次开发如何把这个课程设计升级成拿得出手的项目5.1 功能扩展方向让系统从「课程设计」变成「完整项目」这个同学录系统的底层设计是清楚的但如果你想在答辩或项目经历中拿出来讲只做基础增删改查是不够的。有三个升级方向升级点1给同学信息加图片上传功能。现在的photo字段只是存一个字符串路径没有真实上传能力。可以引入commons-fileupload组件在JSP的表单中加上enctypemultipart/form-data在Servlet中处理文件上传把文件保存到web/uploads/目录再把相对路径写入数据库。注意IDEA部署到Tomcat后上传文件会保存在Tomcat的webapps目录下用相对路径访问就行。升级点2把留言板改成评论回复模式。在message表里加一个parent_id字段当它的值为NULL时表示是顶级留言否则是某条留言的回复。查询时先查顶级留言再对每个留言查回复列表前端展示出树形结构。这个功能量大吗不大但涉及递归查询的思路可以在答辩时讲出一套完整的方案。升级点3引入Bootstrap美化页面。现在的JSP页面用的是纯CSS观感一般。Bootstrap的引入很简单——在JSP页面head里加一行link标签引用CDN然后把表单控件的class改成form-control表格加上table table-bordered table-hover。半小时能把整个项目的颜值提升一个档次而且代码改动极小。5.2 验证方法升级后如何确认没把系统改坏每次改完模块必须走一遍「回归验证流程」。我一般按三步走功能验证——登录、增删改查、分页、留言这五个核心路径从头到尾操作一遍确认没有500错误数据验证——在Navicat里手动执行几个关键查询确认数据被正确写入边界验证——输入超长文本、空值、特殊字符看系统会不会崩。这三步做完再打包war包用mvn package或者在IDEA里Build Artifact部署到干净的Tomcat上做最后验证。5.3 答辩时容易被追问的技术点课程设计答辩时老师最喜欢问的通常是「为什么这么设计」。有几个问题要提前准备为什么用JSPServlet而不用VueSpring Boot这既是对JavaWeb基础知识的考察也是对你个人能力的验证。可以从课程设计要求出发说明Servlet是JavaWeb的根基JSP是服务端渲染的代表技术这套组合能完整展示你对HTTP协议、请求转发与重定向、Session等底层机制的理解。Servlet是线程安全的吗答Servlet默认是单实例多线程的所以实例变量存在并发问题。在你的代码里StudentServlet里没有可修改的实例字段service层每次new出来的所以是安全的。preparedStatement和Statement有什么区别一个是预编译、防SQL注入、占位符传参另一个是直接拼接字符串容易被注。这是最基本的知识点必须能脱口而出。这些追问点看起来简单但很多人是因为没做深入准备而翻车。把那五张表的关系理清楚、把Session生命周期讲明白、把分页SQL写出来这你就是真的很清楚这个系统而不是背出来的。5.4 一个真正的观察做课程设计最忌讳的是把代码跑通就完了。我这个项目我每年都让几个学生来拆解每次都有新收获——比如有人把留言板改成了带emoji表情的版本有人给同学录加了Excel导出功能有人把分页查询改成了MyBatis的PageHelper插件。而这些人的共同点就是他们真的研究过这个项目的每行代码而不是简单download下来改个名字就交差。希望这篇拆解能帮到你——祝你跑通、改好、答辩顺利。这个项目的内容完全满足一个合格的JavaWeb期末课程设计的要求但真正的价值在于你把它吃透之后能用自己的话讲清楚每一步为什么这么走。那才是你真正学到的东西。本文还有配套的精品资源点击获取