简介:这是一份面向高校计算机相关专业学生的Java Web课程设计参考项目,以同学录系统为主题,适合正在完成期末大作业或希望系统梳理Java Web开发流程的初学者。项目采用Servlet与JSP实现前后端交互,前端结合HTML/CSS/JavaScript、jQuery与Bootstrap构建页面,后端涉及MySQL数据库、SQL建表脚本以及MVC分层与Session登录状态管理,并包含权限控制等常见业务逻辑。压缩包共48个文件,约1021KB,涵盖java源码、class编译文件、jsp页面、jar依赖、sql脚本、css样式、properties配置及xml描述文件等,结构完整,便于直接导入Tomcat与IDE运行调试。目前已有4372人学习下载。通过研读源码与数据库脚本,读者可掌握前后端协同、数据库设计与部署运行的完整思路,并借鉴环境差异与路径配置等常见问题的排查方法,提升实际项目开发能力。
1. 同学录系统 JavaWeb 期末项目:一份能跑通的课程设计到底长什么样
每年到了期末周,总有一批人对着“JavaWeb 课程设计”这几个字发愁。选题要么太大做不完,要么太小撑不起报告,最后交上去的东西自己都不敢点开第二遍。这份“同学录系统 JavaWeb 期末项目”就是冲着这个场景来的——它把课程设计里最典型的增删改查、分页、登录鉴权、前后端数据交互串成了一条完整的线,适合正在赶课程设计的学生,也适合想拿一个干净小项目练手 JavaWeb 的开发者。同学录这个选题本身不复杂,但“不复杂”恰恰是它的优势:业务逻辑一眼能看懂,你才有精力去抠 Servlet 怎么配、JSP 怎么传值、数据库连接池怎么接这些真正要命的地方。下面我按“这东西是什么、怎么跑起来、哪里容易翻车、怎么改出自己的东西”这条线,把它拆开讲一遍。
2. 同学录系统的技术骨架:Servlet + JSP + JDBC 到底怎么搭
2.1 为什么课程设计偏爱这套“老技术栈”
先把这个项目的技术选型说清楚。它用的是 JavaWeb 里最经典的组合:Servlet 做控制器、JSP 做视图、JDBC 做数据访问,数据库一般是 MySQL。这套东西放到今天当然不算新,但课程设计选它有非常现实的原因。
第一,它不依赖任何重型框架,你不需要先搞懂 Spring 的 IoC 容器和 AOP 代理就能开始写业务。第二,它的分层结构足够清晰,Servlet 接收请求、调用 DAO、把结果塞进 request 域、转发给 JSP,这条链路走一遍,你对“前端后端怎么连起来”这件事就有了肌肉记忆。第三,答辩的时候老师问“你这个请求是怎么到数据库的”,你能一层一层说清楚,不会卡壳。
常见做法是把项目分成这么几层:实体类(entity)对应数据库表,数据访问层(dao)写 SQL,服务层(service)做业务组合,控制层(servlet)处理请求和跳转,视图层(jsp)负责展示。这个项目基本遵循这个结构,你拿到手之后先别急着改代码,先把目录结构看一遍,搞清楚每个包是干什么的。
提示:如果你的环境是 JDK 8 以上、Tomcat 8 或 9,基本可以直接跑。Tomcat 10 因为把 javax.servlet 改成了 jakarta.servlet,老项目直接部署会报 ClassNotFoundException,这是第一个要留神的地方。
2.2 数据库表结构与实体类的对应关系
同学录系统的核心数据就是“同学信息”和“用户账号”两块。典型表结构大概是这样:
| 表名 | 主要字段 | 用途 |
|---|---|---|
| user | id, username, password, role | 登录账号与权限 |
| classmate | id, name, gender, phone, qq, email, address, remark | 同学录条目 |
| class_info | id, class_name, grade, teacher | 班级信息(可选) |
实体类就是把这些字段映射成 Java 属性,注意类型对应:数据库的 int 对应 Integer,varchar 对应 String,date 对应 java.util.Date 或 java.sql.Date。这里有个新手常踩的坑——数据库字段名用下划线(比如 class_name),Java 属性用驼峰(className),如果你手动写 JDBC 映射,就得在 ResultSet 取值时一一对应,写错了不会报编译错误,只会在运行时给你一个 null。
// Classmate.java 实体类示例 public class Classmate { private Integer id; private String name; private String gender; private String phone; private String qq; private String email; private String address; private String remark; // 省略 getter/setter,实际项目里必须补全 }这段代码没什么玄学,但你要注意:实体类必须有无参构造方法,否则很多反射相关的操作会失败;getter/setter 必须完整,JSP 里用 EL 表达式取值时靠的就是它们。
2.3 从登录到列表:一条完整请求链路的拆解
把项目跑起来之后,最有价值的动作是跟一条完整链路。以“登录后查看同学列表”为例:
第一步,用户在 login.jsp 输入账号密码,表单提交到 LoginServlet。第二步,LoginServlet 的 doPost 方法里用 request.getParameter 拿到用户名密码,调用 UserDao 的查询方法。第三步,UserDao 通过 JDBC 连接数据库,执行 select 语句,返回 User 对象或 null。第四步,Servlet 判断如果查到就 session.setAttribute("user", user),然后重定向到列表页;查不到就转发回登录页并带上错误信息。第五步,列表页的 Servlet 调用 ClassmateDao 查出所有同学,setAttribute 到 request,转发给 list.jsp 渲染。
// LoginServlet 核心逻辑 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String username = request.getParameter("username"); String password = request.getParameter("password"); UserDao dao = new UserDao(); User user = dao.findByUsernameAndPassword(username, password); if (user != null) { request.getSession().setAttribute("user", user); response.sendRedirect("classmate?action=list"); } else { request.setAttribute("msg", "账号或密码错误"); request.getRequestDispatcher("login.jsp").forward(request, response); } }这段逻辑里有两个关键点:重定向用 sendRedirect,转发用 forward,前者会改变浏览器地址栏,后者不会;session 存用户信息是为了后续页面判断有没有登录,你可以在列表页开头加一段判断,session 里没有 user 就踢回登录页。
2.4 JDBC 工具类与连接池的接入方式
如果项目里每个 DAO 方法都自己写 DriverManager.getConnection,代码会又臭又长,而且频繁创建连接性能很差。合格的做法是抽一个 JDBCUtils 工具类,把驱动加载、连接获取、资源关闭封装起来。
// JDBCUtils.java 简化版 public class JDBCUtils { private static final String URL = "jdbc:mysql://localhost:3306/classmate_db?useSSL=false&serverTimezone=UTC"; private static final String USER = "root"; private static final String PASSWORD = "your_password"; static { try { Class.forName("com.mysql.cj.jdbc.Driver"); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } public static void close(Connection conn, Statement stmt, ResultSet rs) { // 依次关闭,注意判空和 try-catch } }参数说明:URL 里的 useSSL=false 是关掉 SSL 警告,serverTimezone=UTC 是解决时区报错,这两个不加经常会在控制台看到红色警告甚至连接失败。如果你的 MySQL 是 8.x,驱动类名是 com.mysql.cj.jdbc.Driver,5.x 则是 com.mysql.jdbc.Driver,写错了直接抛异常。
想再进一步,可以接 Druid 或 C3P0 连接池,把 getConnection 改成从 DataSource 取。课程设计里不接也能跑,但报告里写上连接池,是个加分项。
3. 把项目跑起来:环境配置、部署与功能验证
3.1 开发环境清单与版本匹配
在动手之前,先把环境对齐。这个项目对版本比较敏感,版本不匹配是“跑不起来”的头号原因。
| 组件 | 推荐版本 | 说明 |
|---|---|---|
| JDK | 8 或 11 | 不要用 17+ 配老 Tomcat |
| Tomcat | 8.5 或 9.0 | 10 以上包名变了,慎用 |
| MySQL | 5.7 或 8.0 | 8.0 要改驱动类和 URL |
| IDE | Eclipse / IDEA | IDEA 需配 Artifacts |
| 构建 | 纯手动 / Maven | 看项目里有没有 pom.xml |
先看项目根目录有没有 pom.xml。有就是 Maven 项目,导入后等依赖下载完;没有就是普通 Web 项目,需要手动把 mysql-connector-java 的 jar 包放进 WEB-INF/lib 目录,并且 Add to Build Path,否则运行时报 ClassNotFoundException。
3.2 数据库导入与连接参数修改
环境对齐后,第一步是建库建表。项目里一般会带一个 .sql 文件,用 Navicat 或命令行导入都行。
# 命令行导入示例 mysql -u root -p CREATE DATABASE classmate_db DEFAULT CHARACTER SET utf8mb4; USE classmate_db; source /path/to/classmate_db.sql;导入之后,回到 JDBCUtils 或 db.properties 文件,把数据库地址、用户名、密码改成你自己机器的。这一步看着简单,但十个人里有三个栽在这里:密码没改、端口不是 3306、数据库名拼错。改完先写一个 main 方法测一下能不能拿到连接,别等部署到 Tomcat 才发现连不上。
public static void main(String[] args) throws SQLException { Connection conn = JDBCUtils.getConnection(); System.out.println(conn != null ? "连接成功" : "连接失败"); }3.3 部署到 Tomcat 与访问路径确认
普通 Web 项目在 IDEA 里的部署步骤:File → Project Structure → Artifacts,添加 Web Application Exploded,把项目输出目录配好;然后 Run → Edit Configurations,新建 Tomcat Local,在 Deployment 里加上刚才的 Artifact,Application context 设成 /classmate。启动之后浏览器访问 http://localhost:8080/classmate/login.jsp。
Eclipse 相对简单,右键项目 Run As → Run on Server 即可。但要注意 Eclipse 默认可能把项目名当上下文路径,访问地址就是 http://localhost:8080/项目名/。
如果启动报 404,先检查三件事:Artifact 有没有配、web.xml 里的 welcome-file 是不是 login.jsp、访问路径有没有写错大小写。这三条排查完,基本能解决八成 404。
3.4 核心功能逐项验证:登录、增删改查、分页
项目跑起来之后,别急着改代码,先把功能过一遍,确认基线是好的。
登录功能:用数据库里预置的账号登录,看能不能跳转到列表页;故意输错密码,看有没有错误提示。如果输错也进去了,检查 Servlet 里的判断逻辑是不是写成了赋值。
新增同学:填完表单提交,看数据库里有没有多一条记录。如果没有,先看控制台有没有 SQL 异常,再看 DAO 的 insert 语句字段数量和占位符数量对不对。
修改和删除:修改要注意先根据 id 查出原数据回填到表单,删除一般用 URL 传 id,比如 classmate?action=delete&id=5。删除前最好加个 confirm 弹窗,这是体验分。
分页:这是课程设计的重头戏。核心是 limit 语句,select * from classmate limit (pageNo-1)*pageSize, pageSize。同时要查一次总数算总页数。分页最容易错的是页码边界,第一页再点上一页、最后一页再点下一页,都要做判断,否则要么报错要么显示空白。
4. 避坑排查:同学录项目里最容易翻车的五个地方
4.1 中文乱码:POST 和 GET 要分开治
现象:新增同学之后,数据库里名字变成问号或者乱码。
原因:POST 请求默认用 ISO-8859-1 解码,GET 请求在 Tomcat 8 之前也有同样问题。
解决:POST 在 Servlet 第一行加 request.setCharacterEncoding("UTF-8");GET 要么改 Tomcat 的 server.xml 加 URIEncoding="UTF-8",要么手动 new String(name.getBytes("ISO-8859-1"), "UTF-8")。数据库连接 URL 也要加 characterEncoding=utf8。三处都对齐,乱码才会彻底消失。
4.2 空指针异常:session 和 request 取值没判空
现象:访问列表页直接 500,控制台报 NullPointerException。
原因:要么 session 里的 user 已经过期,要么 DAO 查询返回 null 之后直接调了它的方法。
解决:在需要登录的页面开头加判断,session.getAttribute("user") == null 就重定向到登录页;DAO 返回的对象用之前先判空。这是血泪经验,答辩现场崩一次能记一辈子。
4.3 驱动加载失败:jar 包没进对地方
现象:启动或访问时报 ClassNotFoundException: com.mysql.cj.jdbc.Driver。
原因:mysql 驱动 jar 没放进 WEB-INF/lib,或者放了但没加到构建路径。
解决:确认 jar 在 WEB-INF/lib 下,IDEA 里还要在 Project Structure → Modules → Dependencies 里确认它被引用。Maven 项目检查 pom 里有没有 mysql-connector-java 依赖,scope 别写成 provided。
4.4 SQL 注入:拼接字符串的代价
现象:登录框输入 ' or '1'='1 也能进去。
原因:DAO 里用字符串拼接 SQL,比如 "select * from user where username='" + username + "'"。
解决:全部改成 PreparedStatement,用 ? 占位符。这不仅是安全问题,也是课程设计评分点,老师大概率会问。
// 正确写法 String sql = "select * from user where username=? and password=?"; PreparedStatement ps = conn.prepareStatement(sql); ps.setString(1, username); ps.setString(2, password); ResultSet rs = ps.executeQuery();4.5 分页页码越界:最后一页的后悔药
现象:在最后一页点下一页,页面报错或者显示空列表。
原因:计算总页数时用了整数除法,或者没判断当前页是否超过总页数。
解决:总页数用 (total + pageSize - 1) / pageSize 向上取整;Servlet 里拿到 pageNo 后先判断,小于 1 置为 1,大于总页数置为总页数。这个判断加上,分页就稳了。
5. 从能跑到能交:二次开发与答辩加分技巧
把项目跑通只是及格线,想让它真正变成你自己的东西,还得做几处改造。我一般会从三个方向下手,既不费太多时间,又能让答辩时有话可说。
第一个方向是加一个“按姓名模糊搜索”的功能。在列表页加一个搜索框,Servlet 里判断如果传了 keyword 就拼 like 条件。这个改动涉及前端表单、后端参数接收、DAO 动态 SQL,一条线全练到了。实现的时候注意 like 的参数要写成 "%" + keyword + "%",并且仍然用 PreparedStatement 的占位符,别为了省事又拼字符串。
第二个方向是把密码改成加密存储。现在数据库里密码是明文,这在报告里是个减分项。可以引入 MD5 或者更稳妥的 SHA-256,登录时把用户输入的密码加密后再比对。改动量很小,但答辩时老师问“密码安全怎么做的”,你就有东西讲了。
// 简易 SHA-256 加密 public static String sha256(String input) throws NoSuchAlgorithmException { MessageDigest md = MessageDigest.getInstance("SHA-256"); byte[] digest = md.digest(input.getBytes(StandardCharsets.UTF_8)); StringBuilder sb = new StringBuilder(); for (byte b : digest) { sb.append(String.format("%02x", b)); } return sb.toString(); }第三个方向是给列表加分页加排序。比如点击“姓名”表头按姓名排序,点击“ID”按 ID 排序。实现上就是在 SQL 后面拼 order by,但字段名不能直接来自前端参数,要做白名单校验,否则又是一个注入点。
验证改造是否成功,别只看页面。每次改完 DAO,先写个 main 方法单独测 SQL;改完 Servlet,用浏览器开发者工具的 Network 面板看请求参数和响应状态码;改完 JSP,先看页面有没有报错,再看数据对不对。这套流程走下来,你对自己改的每一行代码都有底。
从那以后我每次拿到一个课程设计项目,都强制自己先跑通基线、再跟一条完整链路、最后才动手改,绝不跳过验证直接写新功能。希望这份拆解能帮你少走点弯路,顺利把期末项目交出去。
本文还有配套的精品资源,点击获取