拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Java Web学生信息管理系统课程设计:数据库设计与Servlet+JSP实现

简介基于Java Web的学生信息管理系统课程设计资料面向Java初学者与课程设计开发者围绕学生信息管理的典型场景覆盖登录验证、系统主界面、添加/维护/删除学生、按学号查找、更新信息、更改管理员密码及退出系统等完整操作流程。压缩包共包含78个文件核心为16个Java源码、12个JSP页面、5个JAR依赖包和2个SQL数据库脚本另配CSS/JS前端交互文件、docx说明文档、项目配置文件及图片资源整体仅2.53MB目录按Java Web常见结构整理便于定位和修改。截至目前已有922人学习下载。说明文档详细梳理了功能结构、项目架构、包及Java类职责和数据库设计项目导入IDE后可结合SQL脚本快速建表运行适合作为课程设计、毕业设计或期末实训的完整参考也可在此基础上二次开发。对于需要完成学生信息管理项目的开发者是一份即取即用的高质量资源。1. 学生信息管理系统选型为什么课程设计用 Java Web 而不是桌面程序学生信息管理系统是数据库课程设计和 Java Web 课程里出现频率最高的一类题目但很多人在动手前把精力花在“写界面”而不是“划边界”上。一个基于 Java Web 的学生信息管理系统本质上是一个围绕“学生、课程、成绩”三类数据做增删改查的 web 工程它要解决的从来不是业务复杂度而是分层、连接管理和 CRUD 的规范性。选择 Java Web 而不是 Java Swing 或 C/S 架构是因为它能把数据库访问逻辑和页面展示拆开而且部署后任何能开浏览器的机器都能访问更接近真实企业应用的形态。这篇文章按我自己做课程设计的套路从数据库设计、MVC 骨架、核心业务代码到部署排错一步步讲完适合正在写期末作业或准备面试项目的读者。2. 数据库表设计学生、课程、成绩三张表如何建才不烂数据库课程设计的评分点往往不在功能多而在 ER 图和表结构是否规范。很多学生一上来就建一张大表把学生、课程、成绩全部塞进去结果更新一个学生姓名要连带改好几行。正确的做法是先识别实体和关系再对照范式拆表。学生信息管理系统的核心就三个东西学生、课程、成绩。成绩是一个关联实体它连接学生和课程属于多对多关系的中间表。2.1 先画 ER 图再建表课程设计里最容易漏的外键关系画 ER 图时先定义实体学生Student、课程Course、成绩Score。学生和课程之间是典型的多对多关系因为一个学生可以选多门课一门课也可以被多个学生选而成绩就挂在这个多对多关系上。所以成绩表不能脱离学生表和课程表单独存在它的两个业务字段 student_id 和 course_id 必须分别引用学生表与课程表的主键。见过许多课程设计把成绩表设计成stu_name、course_name这种冗余字段这会带来三范式里说的更新异常。比如课程改名了成绩表里的课程名不会自动变查询时还得额外维护一致性。正确做法是成绩表只存储外键和分数需要展示名称时通过 JOIN 去查。这也是数据库课程设计答辩时老师最爱追问的点“你的表用了第几范式哪里体现了第三范式”2.2 三张核心表的建表 SQL 与字段说明下面这套建表语句是 Java Web 课程设计里最常见的做法存储引擎选 InnoDB字符集统一 utf8mb4。utf8mb4 支持表情符号和中文避免出现 MySQL 5.7 以下版本 utf8 字符集不够用的乱码问题。CREATE TABLE student ( id INT AUTO_INCREMENT PRIMARY KEY, student_no VARCHAR(20) NOT NULL UNIQUE COMMENT 学号, name VARCHAR(50) NOT NULL COMMENT 姓名, gender TINYINT DEFAULT 0 COMMENT 0-未知 1-男 2-女, birth_date DATE COMMENT 出生日期, class_name VARCHAR(50) COMMENT 班级, phone VARCHAR(20) COMMENT 联系电话, created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE course ( id INT AUTO_INCREMENT PRIMARY KEY, course_no VARCHAR(20) NOT NULL UNIQUE COMMENT 课程编号, course_name VARCHAR(100) NOT NULL COMMENT 课程名称, credit DECIMAL(2,1) DEFAULT 0 COMMENT 学分, teacher VARCHAR(50) COMMENT 任课教师 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE score ( id INT AUTO_INCREMENT PRIMARY KEY, student_id INT NOT NULL, course_id INT NOT NULL, score DECIMAL(5,2) COMMENT 成绩, UNIQUE KEY uk_student_course (student_id, course_id), CONSTRAINT fk_score_student FOREIGN KEY (student_id) REFERENCES student(id), CONSTRAINT fk_score_course FOREIGN KEY (course_id) REFERENCES course(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;字段设计说明表名字段类型约束说明studentstudent_noVARCHAR(20)NOT NULL UNIQUE学号是业务唯一键即使主键 id 存在也要加唯一约束studentgenderTINYINTDEFAULT 0用数字存性别避免字符串存储占用空间coursecreditDECIMAL(2,1)DEFAULT 0学分可能是 1.5 这样的值不能设计成 INTscorescoreDECIMAL(5,2)无成绩保留两位小数最高 999.99覆盖期中期末等场景scoreuk_student_course联合唯一索引UNIQUE防止同一个学生同一门课被录两次外键约束在课程设计中建议保留虽然有些生产环境为了高并发会禁用外键但课程设计就是要体现关系完整性。MyISAM 引擎不支持外键所以千万别图省事把三张表都建成 MyISAM。2.3 为什么主键用自增而不是学号学号看起来能唯一标识学生但不建议直接当主键。原因有两个一是学号属于业务字段如果学校教务处调整学号规则主键改动会牵动所有外键引用二是 InnoDB 的聚簇索引按主键顺序组织数据用自增 INT 主键插入时只需要追加写入效率高。而如果使用随机或无序的字符串主键插入时会引起页分裂性能明显下降。课程设计的数据量不大性能影响感知不到但这是 Java 面试题里常考的“为什么主键建议用自增”的落地点。2.4 成绩表为什么要用联合唯一键score 表中的uk_student_course联合唯一索引保证了同一学生同一课程只能有一条成绩记录。这样做的业务含义很直接一次考试对应一个分数。如果在 Java 代码里不先查重直接 insert数据量稍大就会出现重复选课成绩。联合唯一键是在数据库层做兜底比代码里手动判断可靠得多。如果你用的是 Spring Boot JPA可能会考虑UniqueConstraint注解但底层原理和 SQL 一样。课程设计讲清楚“唯一约束在数据库层保证了数据不可重复”比单纯写一堆 CRUD 更能拿分。3. 用 Servlet JSP 搭出 MVC 骨架从实体类到 DAO 的一步步实现Java Web 课程的经典技术栈是 Servlet JSP JDBC虽然现在企业里更多用 Spring Boot但课程设计往往是两三代技术并存。用 Servlet 手写一遍 MVC能让你彻底搞明白请求是怎么从浏览器走到数据库再返回 JSP 的。下面这套结构是我认为最适合课程设计复用的。3.1 课程设计的项目结构包名和目录别乱起一个标准的 Maven Web 项目包名按com.xxx.student的风格下面至少分五个子包com.xxx.student ├── entity // 实体类对应数据库表 ├── dao // 数据访问对象写 SQL ├── servlet // 控制器接收请求 ├── filter // 过滤器处理编码和登录 └── util // 工具类比如数据库连接WEB-INF 目录下放 JSP 页面不能直接通过浏览器地址栏访问必须经过 Servlet 转发。这是用 Servlet JSP 做 MVC 的一个隐含好处页面被保护在 WEB-INF 里访问入口统一。JSP 文件命名避免中文用student_list.jsp、student_edit.jsp方便 IDE 识别和后期维护。3.2 实体类字段和数据库列一一对应实体类的作用是把数据库表映射成 Java 对象。Student 实体对应 student 表每个属性对应表的每一列。属性命名用驼峰数据库列名用下划线MyBatis 会自动映射但手写 JDBC 时需要在代码里手动 getString(student_no) 或 getString(studentNo)取决于 SQL 中列别名的写法。为了不让 DAO 代码写得太别扭我通常给查询列起别名比如SELECT id, student_no AS studentNo, name FROM student。public class Student { private Integer id; private String studentNo; private String name; private Integer gender; private String birthDate; private String className; private String phone; // 无参构造必须有 public Student() {} // getter 和 setter 省略IDE 自动生成 }注意 gender 用 Integer 而不是 int目的是区分“没填”和“性别未知”。int 的默认值是 0如果数据库里 gender 是 NULL用 int 接收会直接抛异常。这种细节在很多 Java 面试题里会以“包装类与基本类型返回值”的形式出现。3.3 数据库连接类封装 JDBC 的 URL、用户名、密码课程设计最常见的连接方式还是 DriverManager虽然生产环境会用连接池但先跑通再优化。写一个 DBUtil 工具类把连接参数集中管理。public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/student_db?useUnicodetruecharacterEncodingUTF-8useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue; private static final String USER root; private static final String PASSWORD 123456; static { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { throw new ExceptionInInitializerError(加载数据库驱动失败); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } public static void close(Connection conn, PreparedStatement ps, ResultSet rs) { try { if (rs ! null) rs.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (ps ! null) ps.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (conn ! null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }URL 参数中useUnicodetruecharacterEncodingUTF-8解决中文乱码serverTimezoneAsia/Shanghai解决 MySQL 8 的时区报错allowPublicKeyRetrievaltrue解决 MySQL 8 加上 useSSLfalse 后可能出现的连接失败。如果你用的是 MySQL 5.6驱动用com.mysql.jdbc.DriverURL 里可以不写 serverTimezone。判断版本最直接的方式是看驱动 jar 包名mysql-connector-java 5.x 用旧类名8.x 必须用新类名。3.4 DAO 模式把 SQL 从 Servlet 里剥出去DAO 的作用是隔离数据访问逻辑。如果直接把 SQL 写在 Servlet 里页面跳转和 SQL 耦合在一起改一句 SQL 就要改控制器非常难维护。StudentDAO 里每个方法对应一个数据库操作常见的四个方法如下public class StudentDAO { public ListStudent findAll() { String sql SELECT id, student_no AS studentNo, name, gender, birth_date AS birthDate FROM student ORDER BY id DESC; ListStudent list new ArrayList(); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { while (rs.next()) { Student s new Student(); s.setId(rs.getInt(id)); s.setStudentNo(rs.getString(studentNo)); s.setName(rs.getString(name)); s.setGender(rs.getInt(gender)); s.setBirthDate(rs.getString(birthDate)); list.add(s); } } catch (SQLException e) { e.printStackTrace(); } return list; } public Student findById(int id) { // 使用 WHERE 条件返回单个对象 } public boolean insert(Student student) { // 略 } public boolean update(Student student) { // 略 } public boolean deleteById(int id) { // 略 } }这里用了 try-with-resourcesConnection、PreparedStatement、ResultSet 都能自动关闭比手写 finally 更简洁。注意findAll方法里 ORDER BY 一定要有否则数据库查出来的顺序不稳定页面列表会出现“上次打开和这次打开顺序不一样”的怪问题。3.5 Servlet 控制器接收请求、调 DAO、转发到 JSPServlet 负责让请求和数据层解耦。下面这个典型的列表 Servlet控制流程只有三步设置编码调 DAO 拿数据把数据塞进 request 作用域转发给 JSP。WebServlet(/student/list) public class StudentListServlet extends HttpServlet { private final StudentDAO studentDAO new StudentDAO(); Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); ListStudent students studentDAO.findAll(); req.setAttribute(students, students); req.getRequestDispatcher(/WEB-INF/jsp/student_list.jsp).forward(req, resp); } }注释路径WebServlet(/student/list)是 Java Servlet 3.0 之后的注解方式不用再去 web.xml 配 servlet 映射。forward 和 redirect 的区别你要清楚forward 是服务器内部转发地址栏不变可以带 request 属性redirect 会发起新的请求地址栏变化如果要跳过权限或者防止表单重复提交才用 redirect。课程设计里不要在doGet里写数据库操作以外的逻辑过滤、校验都放到 Filter 或 Service 层。3.6 JSP 展示层用 JSTL 遍历学生列表JSP 里尽量不写% %脚本片段用 EL 表达式和 JSTL 标签。页面头部引入 JSTL 核心库遍历列表的写法如下% taglib prefixc urihttp://java.sun.com/jsp/jstl/core % html body table border1 tr th学号/th th姓名/th th性别/th /tr c:forEach items${students} vars tr td${s.studentNo}/td td${s.name}/td td c:choose c:when test${s.gender 1}男/c:when c:when test${s.gender 2}女/c:when c:otherwise未知/c:otherwise /c:choose /td /tr /c:forEach /table /body /htmlJSTL 的c:forEach能自动处理 null 集合比脚本片段加 for 循环安全得多。这里特别强调很多课程设计项目没有在 pom.xml 中添加 jstl 依赖结果 JSP 一运行就报javax.servlet.jsp.JspException。如果你想避免这个坑要么引入jakarta.servlet.jsp.jstl依赖Tomcat 10要么用最原始的方式拼接 HTML。4. 增删改查与分页搜索学生信息管理系统的核心业务代码CRUD 是系统的基础但课程设计不能只有 CRUD。把分页与模糊搜索做进去功能完整度立刻上一个台阶。这一章给出可以直接复制使用的代码套路同时把参数含义和容易出问题的地方讲透。4.1 统一的返回格式与页面跳转方式前后端分离开发中接口返回 JSON但 Servlet JSP 里最常见的做法是操作完成后重定向到列表页。比如新增成功后用resp.sendRedirect(req.getContextPath() /student/list)避免用户按 F5 重复提交。操作失败则直接 forward 回编辑页并携带错误消息页面通过 EL 表达式拿到 request 里的 error 信息。为了方便管理成功和失败提示可以在 servlet 基类里定义一个简单的状态方法但课程设计没必要上 Spring MVC 的 Result 对象。用request.setAttribute(error, 保存失败)就足够了页面里判断${not empty error}显示提示。4.2 新增学生PreparedStatement 如何防止 SQL 注入新增学生的 SQL 写在 StudentDAO 的 insert 方法里使用 PreparedStatement 的占位符完成参数填充。千万不要用字符串拼接 SQL那是 Java 面试八股文里反复强调的 SQL 注入大忌。public boolean insert(Student student) { String sql INSERT INTO student (student_no, name, gender, birth_date, class_name, phone) VALUES (?, ?, ?, ?, ?, ?); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, student.getStudentNo()); ps.setString(2, student.getName()); ps.setInt(3, student.getGender()); ps.setDate(4, (student.getBirthDate() null || student.getBirthDate().isEmpty()) ? null : Date.valueOf(student.getBirthDate())); ps.setString(5, student.getClassName()); ps.setString(6, student.getPhone()); return ps.executeUpdate() 0; } catch (SQLException e) { e.printStackTrace(); return false; } }参数说明参数位置对应列类型处理1student_nosetString 直接传2namesetString 直接传3gendersetInt页面单选框 value 转 int4birth_date用 Date.valueOf 将 2000-01-01 转为 java.sql.Date5class_namesetString6phonesetString注意第 4 参数HTML 表单里 birth_date 是字符串数据库字段是 DATE。如果你用ps.setString(4, birthDate)也可以MySQL 会自动转换但可读性差。建议在 Servlet 里先转换好再用 setDate 设置。如果日期字符串为空要传 null 而不是空字符串否则 SQLException 会报Cannot parse date。4.3 更新与删除通过主键定位记录更新需要先查询出原数据再在表单中回显提交时用主键 id 定位要修改的记录。更新语句只设置需要改的字段即可但课程设计里把所有字段都 UPDATE 一遍更直观不会有字段漏更的问题。public boolean update(Student student) { String sql UPDATE student SET student_no ?, name ?, gender ?, birth_date ?, class_name ?, phone ? WHERE id ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, student.getStudentNo()); ps.setString(2, student.getName()); ps.setInt(3, student.getGender()); ps.setObject(4, student.getBirthDate() null ? null : java.sql.Date.valueOf(student.getBirthDate())); ps.setString(5, student.getClassName()); ps.setString(6, student.getPhone()); ps.setInt(7, student.getId()); return ps.executeUpdate() 0; } catch (SQLException e) { e.printStackTrace(); return false; } }删除建议用逻辑删除而不是物理删除。可以在 student 表加一个deleted TINYINT字段查询全部数据时加条件WHERE deleted 0删除时执行 UPDATE。这样误删后可以从数据库里找回答辩时是一个加分点。当然如果题目没要求物理删除也没有大问题。4.4 分页查询LIMIT 与 page 参数的传法分页查询的核心是 MySQL 的LIMIT ?, ?。第一个参数是偏移量第二个是每页条数。偏移量不能直接由前端传必须通过(page - 1) * pageSize计算出来否则会有人把 page 设为负数直接把偏移量变成负数。public ListStudent findByPage(int pageNum, int pageSize) { int offset (pageNum - 1) * pageSize; String sql SELECT id, student_no AS studentNo, name, gender FROM student ORDER BY id DESC LIMIT ?, ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, offset); ps.setInt(2, pageSize); try (ResultSet rs ps.executeQuery()) { ListStudent list new ArrayList(); while (rs.next()) { // 组装对象 } return list; } } catch (SQLException e) { e.printStackTrace(); return null; } } public int countAll() { String sql SELECT COUNT(*) FROM student; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { if (rs.next()) { return rs.getInt(1); } } catch (SQLException e) { e.printStackTrace(); } return 0; }在 Servlet 中需要计算总页数int page 1; int pageSize 10; try { page Integer.parseInt(req.getParameter(page)); } catch (NumberFormatException e) { // 默认第 1 页 } int total studentDAO.countAll(); int totalPages (total pageSize - 1) / pageSize; if (page totalPages) page totalPages; if (page 1) page 1; ListStudent students studentDAO.findByPage(page, pageSize); req.setAttribute(students, students); req.setAttribute(currentPage, page); req.setAttribute(totalPages, totalPages);这段代码把边界情况都处理了page 解析失败默认第一页page 超过总页数时强制落到最后一页page 小于 1 回退到第一页。很多从没接触过分页的课程设计学生会在totalPages上犯晕公式(total pageSize - 1) / pageSize是整数除法向上取整的经典写法面试也常考。4.5 按姓名和学号模糊搜索的 SQL 拼接搜索功能至少要覆盖学号和姓名两个维度。在 DAO 中新增一个带条件的分页查询方法SQL 用WHERE拼接注意使用LIKE时通配符由参数传递。public ListStudent search(String keyword, int pageNum, int pageSize) { int offset (pageNum - 1) * pageSize; String sql SELECT id, student_no AS studentNo, name, gender FROM student WHERE student_no LIKE ? OR name LIKE ? ORDER BY id DESC LIMIT ?, ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { String pattern % keyword %; ps.setString(1, pattern); ps.setString(2, pattern); ps.setInt(3, offset); ps.setInt(4, pageSize); // 执行查询 } }搜索参数和分页参数同时存在Servlet 里接收时需要对 keyword 做空判断如果 keyword 为空字符串就直接调用不分词的 findByPage否则调用 search。JSP 里的搜索表单如下form action${pageContext.request.contextPath}/student/list methodget input typetext namekeyword placeholder学号或姓名 value${param.keyword} button typesubmit搜索/button /form注意在 JSP 中回填搜索关键词用${param.keyword}否则搜索完下一页页面上的关键字就清空了体验不好。分页链接要把当前关键词带过去a href${pageContext.request.contextPath}/student/list?page1keyword${param.keyword}首页/a如果只写page1点击下一页后搜索条件就丢了这是很多课程设计答辩前才发现的问题。5. 部署到 Tomcat 的三个常见坑war 包、乱码、JDBC 驱动缺失功能写完了很多学生在本地 IDE 里跑得好好的一拿去给老师演示就出各种环境问题。问题多半出在部署方式和运行环境不一致上。这一章讲三个最常见、最容易踩的坑能帮你少熬一个通宵。5.1 IDEA 导出 war 包与部署到 Tomcat 的路径IDEA 中如果是 Maven 项目直接在右侧 Maven 面板执行package会在 target 目录下生成 war 包。如果没有 Maven可以使用 IDEA 的 Artifacts 功能File - Project Structure - Artifacts - 点加号 - Web Application: Archive - Build。构建完成之后把 war 包复制到 Tomcat 的webapps目录下启动 Tomcat它就会自动解压成同名文件夹。访问路径默认是http://localhost:8080/{war包名}/student/list。如果你的 war 包叫student.war项目上下文就是/student。如果你希望访问时不带前缀把 war 包改名为ROOT.war替换掉 Tomcat 默认的 ROOT 应用。有时候修改了 Java 代码只重新打包 war 并覆盖Tomcat 可能不会自动清除旧的解压目录带来缓存问题所以部署前最好先停掉 Tomcat删掉旧的解压文件夹再放新 war 包。5.2 中文乱码的过滤器与 JSP 页面编码乱码有几个来源HTTP 请求编码、响应编码、JSP 文件编码、数据库连接编码。最省事的方法是全局加一个编码过滤器在最前面统一设置 UTF-8。WebFilter(/*) public class CharacterEncodingFilter implements Filter { Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { req.setCharacterEncoding(UTF-8); resp.setContentType(text/html;charsetUTF-8); chain.doFilter(req, resp); } }除了过滤器JSP 文件第一行必须有pageEncoding声明% page contentTypetext/html;charsetUTF-8 languagejava pageEncodingUTF-8 %很多乱码问题是因为 JSP 文件本身编码就是 GBK而页面声明 UTF-8。IDEA 里可以通过 File - File Encoding 检查当前文件的编码并把项目全局编码统一设置为 UTF-8。数据库字符集也得是 utf8mb4JDBC URL 中再带上characterEncodingUTF-8三层都对齐了乱码才不会出现。5.3 数据库驱动版本不匹配的报错与解决课程设计里最经典的报错是java.lang.ClassNotFoundException: com.mysql.jdbc.Driver如果你用的是 MySQL 8 的驱动 jar类名应该是com.mysql.cj.jdbc.Driver。MySQL 5.x 驱动才用旧类名。检查WEB-INF/lib目录下是否真的有驱动 jar 包。IDE 里在Project Structure的 Libraries 中添加依赖只对编译期有效运行期 Tomcat 需要看到WEB-INF/lib下的 jar。这也是为什么 Maven 项目中scopeprovided/scope会导致驱动不打包进 war 的原因不要对 mysql-connector-java 用 provided。如果自测时发现 Tomcat 启动报错Public Key Retrieval is not allowed在 JDBC URL 后面加上allowPublicKeyRetrievaltrue。很多人第一次用 MySQL 8 都会遇到这个不是代码问题是连接参数问题。5.4 使用 web.xml 配置 Servlet 映射的过渡方案有些学校要求使用低版本 Tomcat不支持注解WebServlet那就必须在 web.xml 里写 Servlet 声明。web.xml 的头部 schema 要和你 Tomcat 版本对应。给一个兼容 Tomcat 8 的标准配置示例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_4_0.xsd version4.0 servlet servlet-nameStudentListServlet/servlet-name servlet-classcom.xxx.student.servlet.StudentListServlet/servlet-class /servlet servlet-mapping servlet-nameStudentListServlet/servlet-name url-pattern/student/list/url-pattern /servlet-mapping /web-appweb.xml 中的servlet-mapping和注解WebServlet同时配置会报重复映射错误所以确认一个方案后另一个就要删掉。如果是 Tomcat 10注意包名不是javax.servlet而是jakarta.servlet旧代码拷过来会直接编译失败。6. 让课程设计更像正式项目连接池、权限过滤与说明文档写法课程设计拿到及格分容易想要优秀就得在工程性上做文章。最后这一章讲三个每位老师都会看的东西数据库连接池、登录权限控制、说明文档本身的内容组织。不用全部做完挑两个改进就能让整个项目看起来比同班同学高一档。6.1 用 Druid 连接池替换 DriverManagerDriverManager 每次请求都会创建新连接、使用完又释放在并发场景下开销很大。Java 面试题里问到“数据库连接池为什么用连接池”时标准回答是连接复用、管理生命周期、避免频繁建立销毁。课程设计里推荐用 Druid因为它自带监控和日志配置也不复杂。在 WEB-INF/lib 放入 druid-1.2.x.jar 后把 DBUtil 改成import com.alibaba.druid.pool.DruidDataSourceFactory; public class DBUtil { private static DruidDataSource dataSource; static { try (InputStream in DBUtil.class.getClassLoader().getResourceAsStream(druid.properties)) { Properties props new Properties(); props.load(in); dataSource (DruidDataSource) DruidDataSourceFactory.createDataSource(props); } catch (Exception e) { throw new ExceptionInInitializerError(初始化连接池失败); } } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } }druid.properties 文件放在 src/main/resources 下driverClassNamecom.mysql.cj.jdbc.Driver urljdbc:mysql://localhost:3306/student_db?useUnicodetruecharacterEncodingUTF-8useSSLfalseserverTimezoneAsia/Shanghai usernameroot password123456 initialSize5 maxActive20 maxWait10000参数说明参数默认值含义initialSize0启动时初始化的物理连接数maxActive8最大连接池数量maxWait-1获取连接时最大等待毫秒数-1 为无限等待minIdle0最小空闲连接数课程设计项目没必要配置validationQuery那些能跑通连接池、并在代码中展示DruidDataSource的监控页面要点就够了。使用连接池后原来的 DBUtil.close 释放连接时会自动归还到池中而不是物理关闭不存在泄漏风险。6.2 用 Filter 拦截未登录请求登录功能几乎是必做项。在 Filter 中拦截所有请求只放行登录相关的 Servlet 和 JSP。下面这是一个简单的登录拦截 FilterWebFilter(/*) public class LoginFilter implements Filter { Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpReq (HttpServletRequest) req; HttpServletResponse httpResp (HttpServletResponse) resp; HttpSession session httpReq.getSession(false); String uri httpReq.getRequestURI(); if (uri.endsWith(/login) || uri.endsWith(/login.jsp)) { chain.doFilter(req, resp); return; } if (session ! null session.getAttribute(admin) ! null) { chain.doFilter(req, resp); } else { httpResp.sendRedirect(httpReq.getContextPath() /login.jsp); } } }登录成功后需要把用户信息放进 sessionreq.getSession().setAttribute(admin, adminUser);这里的 admin 可以是一个简单的User对象或字符串。关键是把“是否登录过”这个状态保存在 session 中而不是每次请求都查数据库。Filter 里的放行名单要和项目实际路径一致否则会出现登录页加载了点击验证码刷新时也被拦截的死循环。6.3 说明文档该写哪些内容需求、ER 图、接口列表课程设计的说明文档不是代码注释的堆积而是让老师能快速理解项目结构的说明书。按下面五部分组织最稳系统概述写这个系统面向谁解决什么问题运行环境JDK 版本、Tomcat 版本、MySQL 版本。需求分析画用例图或写功能列表列出管理员登录、学生管理、成绩管理、查询分页这几大模块。数据库设计放 ER 图对每张表的字段做说明包括主键、外键、唯一约束和索引设计。核心功能实现说明用文字描述一个完整请求的生命周期比如“新增学生时表单提交到 StudentSaveServletServlet 调用 StudentDAO.insert数据库执行 INSERT 后重定向到列表”。测试过程与结果附上功能测试的截图展示登录失败提示、分页效果、搜索空结果处理。说明文档中最好附一段启动步骤例如“先用 student.sql 初始化数据库再用 IDEA 打开项目配置 Tomcat修改 DBUtil 中的密码运行即可访问”。这能降低老师实际运行你项目时的沟通成本很多项目被扣分不是因为代码差而是老师根本无法跑起来。6.4 一个加分小技巧分页条用 JSP 标签封装如果项目有多张表都需要分页每张页面复制粘贴一长串分页 HTML 很容易出错。推荐把分页条抽成一个 JSP 片段命名为pagination.jsp在其他页面用静态包含引入jsp:include page/common/pagination.jsp jsp:param namecurrentPage value${currentPage} / jsp:param nametotalPages value${totalPages} / jsp:param namekeyword value${param.keyword} / /jsp:include分页片段内部根据 totalPages 判断是否显示“上一页”“下一页”按钮。如果 totalPages 小于等于 1就不显示分页条。将这个逻辑集中到一个文件里后修一次所有页面都生效也显得你确实理解了模块化开发。最后一件事项目里如果包含了源码、数据库 SQL 文件和说明文档最好在根目录写一个 README.md把目录结构和启动步骤写清楚。老师打开项目第一眼看的就是 README它会直接影响整体评分印象。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门