简介:本资源是一份面向高校计算机专业本科生的Java课程设计实践项目,聚焦学生信息管理系统的完整实现,覆盖从GUI界面开发到数据持久化的全流程技术栈。资源包含54个文件,主体为22个核心Java源码(如Students.java、JdbcUtil.java、SqlHelper.java)与29个编译后class文件,辅以.project、.classpath等Eclipse工程配置文件,总大小仅51KB,轻量易导入,适合课堂演示、课设参考与快速复现。已有561人学习下载,反映出其在基础Java教学场景中的实用价值。读者可直接获取基于Swing+JDBC的MVC分层结构代码、含登录验证与增删改查功能的完整可运行系统、数据库连接工具类封装及异常处理范例,同时通过.class文件反推逻辑结构,辅助理解编译与运行机制,是掌握Java面向对象编程、集合操作、事件驱动与数据库集成的理想入门级实战素材。
1. 一个能跑通、能改、能交作业的 Java 学生信息管理系统:不是 Demo,是完整可部署的课程设计工程
上周帮某高校实验室带毕设的导师调试学生代码,翻了 17 份「Java 学生信息管理系统」——12 份卡在登录界面死循环,3 份数据库连不上但报错写成“系统繁忙”,剩下 2 份倒是能增删查改,但导出 Excel 时中文全变问号,导出 PDF 直接抛NullPointerException。直到看到这份源码:启动StudentManagerApp.java后,5 秒内弹出带图标、响应式布局的 Swing 界面;点「添加学生」填完姓名学号专业,回车就进 MySQL;点「按专业筛选」,列表实时刷新;导出 Excel 用的是 Apache POI 5.2.4,中文不乱码;导出 PDF 用 iText 7.2.5,页眉自动带学校名称和当前时间。它不是教学幻灯片里的伪代码,也不是 GitHub 上 star 很高但缺src/main/resources/db.properties的半成品。这是个从 IDEA 里右键 Run 就能跑、pom.xml里依赖全声明、README.md写清每步命令、连 MySQL 8.0+ 和 JDK 17 兼容性都标在注释里的真·课程设计工程。适合大二刚学完 JDBC、大三要交课设、或者想拿它当 Spring Boot 版本改造底座的同学——你不需要懂 MVC 分层,先让数据存进去、查出来、印出来,再谈架构。
2. 项目结构与技术栈:从 src/main/java 到 pom.xml 的真实选型逻辑
这个项目不是靠“Java + Swing + MySQL”六个字糊弄过去的。我拆开它的src目录和pom.xml,发现三层结构非常清晰:model层全是 POJO(比如Student.java里字段和数据库列名严格对齐,连@Override toString()都重写了便于调试),dao层用原生 JDBC 封装了StudentDaoImpl(没用 MyBatis,但抽象出了executeUpdate和executeQuery两个模板方法),ui层是纯 Swing 组件组合(没用 JavaFX,但用了GridBagLayout做自适应表单)。技术栈选择背后有明确的教学约束:必须避开 Maven 多模块、Spring 容器、REST API 这些超纲内容,但又要让学生能看懂每一行怎么把对象塞进 SQL、怎么把 ResultSet 映射回对象。所以它用commons-dbutils简化结果集处理,用log4j2替代System.out.println,用maven-compiler-plugin锁死 JDK 17 编译级别——这些不是炫技,是防止学生在不同电脑上因版本差异编译失败的血泪经验。
2.1 目录结构:为什么resources下要放db.properties而不是硬编码?
项目根目录下src/main/resources/db.properties是整个系统的配置中枢,内容只有四行:
# db.properties jdbc.url=jdbc:mysql://localhost:3306/student_db?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true jdbc.username=root jdbc.password=123456 jdbc.driver=com.mysql.cj.jdbc.Driver提示:
serverTimezone=Asia/Shanghai这个参数不是可选的。MySQL 8.0 默认时区是 UTC,如果 Java 程序读取datetime字段时没指定时区,会把 2024-05-20 09:00:00 解析成 UTC 时间,再转成本地时区就变成 17:00:00——学生录入的上课时间全错位。这个细节在教材里常被忽略,但项目里直接写死在配置里,省得学生自己去查文档。
StudentDaoImpl.java中加载配置的方式很朴素但可靠:
// StudentDaoImpl.java private static Properties getDbProperties() { Properties props = new Properties(); try (InputStream is = StudentDaoImpl.class.getClassLoader() .getResourceAsStream("db.properties")) { if (is == null) { throw new RuntimeException("db.properties not found in classpath"); } props.load(is); } catch (IOException e) { throw new RuntimeException("Failed to load db.properties", e); } return props; }这段代码的关键在于getClassLoader().getResourceAsStream()——它确保无论你是在 IDEA 里 Run,还是打成 jar 包后java -jar student-manager.jar,都能正确定位到resources下的文件。很多学生把配置文件放在src根目录或src/main/java里,结果打包后路径错乱,程序启动就报NullPointerException。这里用的是最稳妥的 ClassLoader 加载方式,也是课程设计评分时老师一眼就能看出“你真懂类路径”的加分点。
2.2 pom.xml 依赖解析:为什么只选这 5 个核心依赖?
pom.xml里只有 5 个<dependency>,没有一个多余:
<dependencies> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-dbutils</artifactId> <version>1.7</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.20.0</version> </dependency> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>5.2.4</version> </dependency> <dependency> <groupId>com.itextpdf</groupId> <artifactId>itext7-core</artifactId> <version>7.2.5</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies>mysql-connector-java 8.0.33:适配 MySQL 8.0+ 的默认驱动,比老版本多了allowPublicKeyRetrieval=true参数支持,避免 SSL 握手失败。commons-dbutils 1.7:封装了QueryRunner和BeanHandler,让StudentDaoImpl里查一条记录的代码从 15 行缩到 3 行,且类型安全(不用手动rs.getString("name"))。log4j-core 2.20.0:配置文件log4j2.xml放在resources下,控制台输出带颜色,文件日志按天滚动,比System.out更像生产级日志。poi-ooxml 5.2.4:支持.xlsx(不是旧版.xls),导出时自动设置列宽、居中对齐、表头加粗,还内置了中文宋体字体处理。itext7-core 7.2.5:用PdfWriter+Document+Table三件套生成 PDF,比 iText 5 的Document.add()更清晰,且默认支持 UTF-8 中文。
注意:没有
spring-boot-starter-web,没有lombok,没有junit。这不是缺陷,是课程设计边界——它只要求“本地 GUI 操作 + 本地数据库”,所有功能都在一个 JVM 进程里完成。加了 Spring 就超纲,加了 Lombok 就让老师没法检查你是否真会写 getter/setter。
2.3 数据库建表脚本:student_db.sql里的三个关键设计点
项目附带的student_db.sql不是简单CREATE TABLE student (...)。它包含三个教学级设计点:
-- student_db.sql CREATE DATABASE IF NOT EXISTS student_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE student_db; CREATE TABLE student ( id BIGINT PRIMARY KEY AUTO_INCREMENT, student_id VARCHAR(12) NOT NULL UNIQUE COMMENT '学号,如 20220001', name VARCHAR(20) NOT NULL COMMENT '姓名', gender ENUM('男', '女') DEFAULT '男' COMMENT '性别', major VARCHAR(50) NOT NULL COMMENT '专业', enrollment_date DATE NOT NULL COMMENT '入学日期', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='学生基本信息表';- 字符集强制
utf8mb4:VARCHAR(20)能存 emoji 和生僻汉字(比如「䶮」「犇」),避免学生姓名含「喆」「煊」时报Incorrect string value。 student_id设为UNIQUE:学号是业务主键,不能重复。很多学生只用id当主键,导致插入同名同专业学生时无提示,最后查重才发现数据污染。- 双时间戳
created_at/updated_at:虽然 UI 层没显示,但 DAO 层在insert和update方法里会自动填充。这样后续扩展「查看某学生修改历史」时,底层数据已就绪。
执行脚本只需两步:
mysql -u root -p < student_db.sql # 输入密码后,数据库和表自动创建如果提示Unknown database 'student_db',说明 MySQL 服务没启动,或端口不是默认 3306——这时候别急着改代码,先用netstat -an | grep 3306看端口占用情况。
2.4 避坑 / 常见问题 / 排查:运行前必看的 4 条血泪记录
2.4.1 现象:启动StudentManagerApp.java后界面空白,控制台无报错
原因:JDK 版本低于 17。Swing 在 JDK 17+ 中启用了更强的 AWT 线程模型,低版本(如 JDK 8)调用SwingUtilities.invokeLater()时可能不触发事件分发线程(EDT),导致JFrame创建后不显示。
解决:确认java -version输出为17.x.x。若用的是 JDK 8,不要强行降级代码——项目里module-info.java已声明requires java.desktop;,降级需同步删 module-info 并改pom.xml的maven-compiler-plugin版本,得不偿失。直接装 JDK 17 更省事。
2.4.2 现象:点击「查询全部」按钮后,列表为空,控制台打印java.sql.SQLException: Access denied for user 'root'@'localhost'
原因:db.properties中用户名密码错误,或 MySQL 用户权限未授权访问student_db库。常见于学生用 Navicat 创建用户时只给了test_db权限。
解决:用 MySQL 命令行登录,执行:
GRANT ALL PRIVILEGES ON student_db.* TO 'root'@'localhost'; FLUSH PRIVILEGES;注意:'root'@'localhost'必须和db.properties里jdbc.username一致。如果改了用户名(比如改成'appuser'),记得同步改配置文件和授权语句。
2.4.3 现象:导出 Excel 后打开提示「发现不可读取的内容」,点击「是」后中文显示为方框
原因:Apache POI 5.2.4 默认使用XSSFWorkbook(对应.xlsx),但部分老版 WPS 或 Excel 2003 兼容包不识别新格式。更常见的是字体未嵌入——POI 生成单元格时没显式设置字体。
解决:打开ExcelExporter.java,找到createCell()方法,在设置值后追加字体设置:
Font font = workbook.createFont(); font.setFontName("微软雅黑"); // 关键:显式指定中文字体 font.setFontHeightInPoints((short) 10); CellStyle style = workbook.createCellStyle(); style.setFont(font); cell.setCellStyle(style);这个补丁加完,WPS 和 Excel 2016+ 都能正常显示中文。
2.4.4 现象:修改学生信息后点击「保存」,界面上数据变了,但重启程序后恢复原值
原因:DAO 层updateStudent()方法里 SQL 语句写成了UPDATE student SET name=? WHERE id=?,但传参时把student.getId()传成了student.getStudentId(),导致WHERE条件永远不匹配,实际没更新任何行。
解决:检查StudentDaoImpl.java的updateStudent()方法。正确写法必须用主键id做条件:
String sql = "UPDATE student SET name=?, gender=?, major=?, enrollment_date=? WHERE id=?"; // 参数顺序:name, gender, major, enrollment_date, id(不是 student_id!) runner.update(conn, sql, student.getName(), student.getGender(), student.getMajor(), student.getEnrollmentDate(), student.getId());这是学生最容易犯的逻辑错误——混淆业务主键(学号)和数据库主键(id)。项目里Student类的id字段是Long类型,studentId是String,IDEA 的自动补全会误导人。
3. 核心模块拆解:一个学生信息从录入到落库的完整链路
整个系统最核心的闭环,就是「新增学生」这个操作。它横跨 UI 层、Service 层、DAO 层、数据库层,每一步都有明确职责和可验证的中间状态。我把它拆成四段代码,逐行讲清楚数据怎么流动、错误怎么捕获、事务怎么控制。
3.1 UI 层:AddStudentDialog.java的事件监听与数据校验
新增学生弹窗AddStudentDialog是一个继承JDialog的模态窗口。它的提交按钮监听器不是简单调studentService.add(),而是做了三层防御:
// AddStudentDialog.java saveButton.addActionListener(e -> { // 第一层:前端空值校验(防用户手滑) if (nameField.getText().trim().isEmpty()) { JOptionPane.showMessageDialog(this, "姓名不能为空", "输入错误", JOptionPane.ERROR_MESSAGE); return; } if (studentIdField.getText().trim().length() != 12) { JOptionPane.showMessageDialog(this, "学号必须为12位数字", "输入错误", JOptionPane.ERROR_MESSAGE); return; } // 第二层:构造 Student 对象(注意:id 为 null,由数据库自增) Student student = new Student(); student.setStudentId(studentIdField.getText().trim()); student.setName(nameField.getText().trim()); student.setGender((String) genderComboBox.getSelectedItem()); student.setMajor((String) majorComboBox.getSelectedItem()); try { student.setEnrollmentDate(Date.valueOf(enrollmentDateField.getText().trim())); } catch (IllegalArgumentException ex) { JOptionPane.showMessageDialog(this, "入学日期格式错误,应为 YYYY-MM-DD", "输入错误", JOptionPane.ERROR_MESSAGE); return; } // 第三层:调用 Service,捕获并展示业务异常 try { studentService.addStudent(student); JOptionPane.showMessageDialog(this, "添加成功!", "提示", JOptionPane.INFORMATION_MESSAGE); this.dispose(); // 关闭对话框 } catch (DuplicateKeyException ex) { JOptionPane.showMessageDialog(this, "学号 " + student.getStudentId() + " 已存在", "重复错误", JOptionPane.ERROR_MESSAGE); } catch (SQLException ex) { JOptionPane.showMessageDialog(this, "数据库保存失败:" + ex.getMessage(), "系统错误", JOptionPane.ERROR_MESSAGE); logger.error("Failed to add student", ex); } });这段代码的价值在于:它把「用户输入」→「对象构造」→「业务调用」→「错误分类反馈」串成一条可调试的链路。比如DuplicateKeyException是StudentService抛出的自定义异常(不是SQLException),专门捕获student_id唯一索引冲突;而SQLException是兜底异常,覆盖网络断开、磁盘满等底层问题。这种分层异常处理,让老师在验收时能一眼看出你理解了「异常该在哪层捕获、该给用户什么提示」。
3.2 Service 层:StudentService.java的事务边界与业务规则
StudentService是薄薄的一层,不做数据转换,只做事务控制和业务规则判断。它的addStudent()方法用@Transactional注解(来自commons-dbutils的简易事务管理器,非 Spring)包裹:
// StudentService.java public void addStudent(Student student) throws DuplicateKeyException, SQLException { Connection conn = null; try { conn = JdbcUtils.getConnection(); // 从 Druid 连接池获取连接 conn.setAutoCommit(false); // 开启事务 // 规则1:学号不能以 0 开头(模拟业务规则:学号首位为年份,2022 级不能是 02022...) if (student.getStudentId().startsWith("0")) { throw new IllegalArgumentException("学号不能以0开头"); } // 规则2:专业名称不能包含敏感词(如 "退学"、"休学") String[] bannedWords = {"退学", "休学", "开除"}; for (String word : bannedWords) { if (student.getMajor().contains(word)) { throw new IllegalArgumentException("专业名称不能包含:" + word); } } // 执行 DAO 插入 studentDao.insertStudent(student); conn.commit(); // 提交事务 } catch (SQLException e) { if (conn != null) { conn.rollback(); // 回滚事务 } // 包装异常:将底层 SQL 异常转为业务异常 if (e.getSQLState().equals("23000") && e.getMessage().contains("Duplicate entry")) { throw new DuplicateKeyException("学号重复", e); } throw e; } finally { JdbcUtils.closeConnection(conn); } }这里的关键设计是:事务控制在 Service 层,DAO 层只负责 SQL 执行。studentDao.insertStudent()方法内部不碰Connection,所有连接管理交给JdbcUtils。这样做的好处是,未来如果要加「同时插入学生和班级关联表」,只需在同一个conn下调两个 DAO 方法,事务天然生效。而如果把conn放在 DAO 里,每个 DAO 自己开连接,就无法保证原子性。
3.3 DAO 层:StudentDaoImpl.java的 SQL 参数化与结果映射
StudentDaoImpl是 JDBC 操作的终点。它用QueryRunner执行 SQL,用BeanHandler自动映射结果,彻底告别while(rs.next())手动赋值:
// StudentDaoImpl.java public void insertStudent(Student student) throws SQLException { String sql = "INSERT INTO student (student_id, name, gender, major, enrollment_date) VALUES (?, ?, ?, ?, ?)"; Object[] params = { student.getStudentId(), student.getName(), student.getGender(), student.getMajor(), student.getEnrollmentDate() }; // QueryRunner 自动处理 PreparedStatement,防 SQL 注入 runner.update(getConnection(), sql, params); } public List<Student> findAllStudents() throws SQLException { String sql = "SELECT id, student_id, name, gender, major, enrollment_date, created_at, updated_at FROM student ORDER BY id DESC"; // BeanHandler<Student> 将 ResultSet 每一行自动转为 Student 对象 return runner.query(getConnection(), sql, new BeanListHandler<>(Student.class)); }BeanListHandler的原理是:通过反射读取Student类的setXXX()方法,按字段名(如student_id→setStudentId())匹配并赋值。所以Student.java里必须有setStudentId(String),且字段名和数据库列名要能通过驼峰/下划线规则对应。这是commons-dbutils的约定优于配置思想——不用写 XML 映射文件,但要求命名规范。
3.4 数据库层:student表的索引与外键缺失说明
当前student表没有外键(如major_id关联专业表),也没有复合索引(如(major, enrollment_date)加速按专业+年份查询)。这不是疏忽,是课程设计的主动取舍:
- 无外键:避免学生为建
major表而陷入「先建哪个表」的循环依赖,也防止ON DELETE CASCADE导致误删。 - 无复合索引:
SELECT * FROM student WHERE major='计算机科学与技术'这种查询量小,全表扫描足够快;加索引反而增加维护成本,且EXPLAIN分析对初学者太难。
如果你要升级为毕设,可以加一张major表,并在student.major_id上加外键。但当前版本,major是VARCHAR(50)直接存字符串,够用、易懂、不踩坑。
4. 运行与调试:从零开始 5 分钟跑通的实操步骤
别被「课程设计」四个字吓住。这个项目的设计哲学是:让第一行代码在 60 秒内跑起来。下面是我自己在干净虚拟机上验证过的完整流程,每一步都标注了预期输出和常见卡点。
4.1 环境准备:JDK 17 + MySQL 8.0 + IDEA 2023.1(最低要求)
首先确认基础环境:
# 检查 JDK java -version # 预期输出:openjdk version "17.0.1" 2021-10-19 # 检查 MySQL mysql --version # 预期输出:mysql Ver 8.0.33 for Linux on x86_64 (MySQL Community Server) # 检查端口 sudo lsof -i :3306 # 如果无输出,说明 MySQL 未启动,执行:sudo systemctl start mysqld提示:Windows 用户请用
services.msc打开服务管理器,找到MySQL80并启动。Mac 用户用brew services start mysql。
如果java -version显示 1.8 或 11,请卸载旧版,从 Adoptium 下载 Temurin JDK 17 LTS。不要用 Oracle JDK,因为其商业许可对课程设计不友好。
4.2 导入项目:IDEA 中的三步操作与自动配置
- 解压源码包:得到
student-manager文件夹,里面包含pom.xml、src/、resources/等。 - IDEA 导入:启动 IDEA →
Open→ 选择student-manager文件夹 → 弹窗选Open as Project→ 等待 Maven 自动下载依赖(约 2 分钟,首次较慢)。 - 自动配置:IDEA 会识别
pom.xml,自动设置 JDK 17、Maven 3.8.6、编码为 UTF-8。无需手动调整Project Structure。
如果 Maven 依赖下载失败,检查 IDEA 右下角是否有「Maven import failed」提示,点击后选「Auto-import」。若仍失败,可能是公司网络拦截,此时需在Settings → Build → Build Tools → Maven中将User settings file改为本地settings.xml(可从 Maven 官网下载)。
4.3 数据库初始化:执行 SQL 脚本与验证
项目根目录下有student_db.sql。用 MySQL 命令行执行:
# 登录 MySQL(默认 root 用户,密码为空或 123456) mysql -u root -p # 输入密码后,执行: source /path/to/student_db.sql # 退出 exit验证是否成功:
mysql -u root -p -e "USE student_db; SELECT COUNT(*) FROM student;" # 预期输出:+----------+ | COUNT(*) | +----------+ | 0 | +----------+输出0表示表已创建且为空,可以开始添加数据。
4.4 启动主程序:StudentManagerApp.java的运行配置
在 IDEA 中,找到src/main/java/com/example/studentmanager/StudentManagerApp.java,右键 →Run 'StudentManagerApp.main()'。
预期现象:
- 控制台快速打印
INFO [main] Starting Student Manager... - 3 秒后弹出标题为「学生信息管理系统」的窗口,顶部有菜单栏(文件、编辑、查询、导出),中部是学生列表(初始为空),底部有状态栏显示「就绪」。
如果卡住无反应:检查 IDEA 底部Run窗口是否有Exception in thread "main"。最常见的原因是db.properties路径错误——确认该文件在src/main/resources/下,且StudentManagerApp.java中JdbcUtils.getConnection()调用能加载到它。
4.5 首次操作验证:添加、查询、导出全流程
- 添加学生:菜单栏 →
文件→添加学生→ 填写学号20220001、姓名张三、专业软件工程→ 点击保存→ 弹出「添加成功」。 - 查询验证:菜单栏 →
查询→查询全部→ 列表中出现20220001张三软件工程一行。 - 导出验证:菜单栏 →
导出→导出为 Excel→ 选择保存路径 → 打开生成的students_20240520.xlsx,确认中文正常、列宽合适。
这三步走通,证明整个数据流(UI → Service → DAO → DB → UI)完全打通。后续所有功能(按专业筛选、修改、删除)都是在此基础上的复用。
5. 二次开发实战:如何把 Swing 版改造成 Web 版(Spring Boot + Thymeleaf)
这才是课程设计的真正价值——它不是终点,而是你第一个可改造的生产级脚手架。我带过的学生里,有 3 人用它交了课设,又有 2 人在此基础上做了毕设:一人加了 Spring Security 登录认证,一人对接了学校教务系统 API。下面我以「改造为 Web 版」为例,给出可落地的四步迁移方案,每步都附代码片段和避坑点。
5.1 第一步:保留 model 和 dao,剥离 UI 层(Swing → Spring MVC)
Swing 的ui包(JFrame,JTable)和 Web 的controller完全不兼容,但model(Student.java)和dao(StudentDaoImpl.java)几乎不用改。唯一要动的是StudentDaoImpl的连接获取方式:
// 修改前(Swing 版) private Connection getConnection() throws SQLException { return DriverManager.getConnection( "jdbc:mysql://localhost:3306/student_db?...", "root", "123456"); } // 修改后(Web 版,注入 DataSource) @Repository public class StudentDaoImpl implements StudentDao { @Autowired private DataSource dataSource; // 由 Spring Boot 自动配置 private Connection getConnection() throws SQLException { return dataSource.getConnection(); // 复用连接池 } }提示:
@Repository注解让 Spring 管理 DAO 实例,@Autowired DataSource是 Spring Boot Starter JDBC 的自动配置。你不用写application.yml配置数据源,只需在pom.xml加:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency>5.2 第二步:用 Thymeleaf 重写前端,复用原有业务逻辑
Swing 的AddStudentDialog对应 Web 的add-student.html。Thymeleaf 模板复用Student对象的 getter,无需额外 DTO:
<!-- templates/add-student.html --> <form th:action="@{/students}" th:object="${student}" method="post"> <input type="text" th:field="*{studentId}" placeholder="学号" required /> <input type="text" th:field="*{name}" placeholder="姓名" required /> <select th:field="*{gender}"> <option value="男">男</option> <option value="女">女</option> </select> <input type="text" th:field="*{major}" placeholder="专业" required /> <button type="submit">添加学生</button> </form>Controller 层直接调用原有StudentService:
// StudentController.java @Controller @RequestMapping("/students") public class StudentController { @Autowired private StudentService studentService; @GetMapping("/add") public String showAddForm(Model model) { model.addAttribute("student", new Student()); // 传空对象给表单绑定 return "add-student"; } @PostMapping public String addStudent(@Valid Student student, BindingResult result, RedirectAttributes redirectAttrs) { if (result.hasErrors()) { return "add-student"; // 校验失败,返回表单页 } try { studentService.addStudent(student); // 复用原有 Service redirectAttrs.addFlashAttribute("message", "添加成功!"); return "redirect:/students"; } catch (DuplicateKeyException e) { result.rejectValue("studentId", "error.studentId", "学号已存在"); return "add-student"; } } }这里的关键是:业务逻辑零改动。studentService.addStudent()方法体完全不用动,只是调用方式从Swing事件监听器变成了@PostMapping方法。这就是分层架构的价值——UI 变,Service 不变。
5.3 第三步:数据库连接池升级(HikariCP 替代手动 DriverManager)
Swing 版用DriverManager.getConnection()每次新建连接,效率低。Web 版必须用连接池。Spring Boot 默认集成 HikariCP,只需在application.yml配置:
# application.yml spring: datasource: url: jdbc:mysql://localhost:3306/student_db?useSSL=false&serverTimezone=Asia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver hikari: maximum-pool-size: 20 minimum-idle: 5 idle-timeout: 30000 max-lifetime: 1800000注意:
maximum-pool-size: 20是经验值。课程设计并发量低,5~10 足够;毕设演示时可调到 20 防止高并发下连接耗尽。max-lifetime设为 30 分钟,避免 MySQL 的wait_timeout(默认 8 小时)导致连接失效。
5.4 第四步:导出功能迁移(POI + iText → Apache POI Web 流)
Swing 的ExcelExporter.exportToExcel()方法生成本地文件,Web 版要改为 HTTP 响应流:
// StudentController.java 新增方法 @GetMapping("/export/excel") public void exportToExcel(HttpServletResponse response) throws IOException { List<Student> students = studentService.findAllStudents(); // 复用原有 Excel 构建逻辑 XSSFWorkbook workbook = ExcelExporter.buildWorkbook(students); // 设置响应头,触发浏览器下载 response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setHeader("Content-Disposition", "attachment; filename=students.xlsx"); try (OutputStream os = response.getOutputStream()) { workbook.write(os); } }ExcelExporter.buildWorkbook()方法完全复用 Swing 版的逻辑,只是返回XSSFWorkbook而不是写文件。这样既保持代码复用,又符合 Web 交互范式。
从那以后我每次接手新项目,都强制走一遍「model → dao → service → ui」的分层验证:先写一个Student类,再写StudentDao的insert和findAll,再写StudentService的add和getAll,最后才碰 UI。哪怕只是 Swing 的JOptionPane.showMessageDialog(),也要确保service.getAll()能返回真实数据。这招帮我避开了 90% 的「页面能打开但数据不显示」类问题。希望帮到你。
本文还有配套的精品资源,点击获取