☰
Swing+JDBC+MySQL毕业设计系统:轻量可控的Java桌面应用实践
2026/10/25 10:08:07 网站建设 项目流程

简介:本资源是一套基于Java桌面开发的毕业设计选题管理系统实战项目,面向计算机专业本科生、Java初学者及课程设计实践者,解决高校毕业设计环节中师生选题、命题、审核与数据管理的信息化需求。项目采用Swing构建图形界面,JDBC连接MySQL实现数据持久化,完整覆盖用户权限控制、题目发布、学生选题、管理员审核及查询统计等核心业务流程。压缩包共18个文件,含12个Java源码(涵盖UI、DAO、Entity等分层逻辑)、1份SQL建库脚本、1份详尽的数据库表设计文档(.doc与.md双格式)、1个可运行jar包及配套依赖jar,整体4.61MB,结构清晰,便于逐模块学习与调试。已有1532人下载学习,提供开箱即用的完整工程结构、规范的数据库设计说明与典型Swing+JDBC集成实践,是掌握Java GUI开发、数据库交互及多角色权限系统设计的优质教学案例。

1. 为什么用 Swing + JDBC + MySQL 做毕业设计选题管理系统,反而比 Spring Boot 更稳、更可控、更适合答辩?

这不是一个“过时技术堆砌”的怀旧项目,而是一类明确面向教学闭环、轻量级部署、零运维依赖的典型 Java 毕业设计落地场景:学生要独立完成从数据库建模、界面交互、业务逻辑到本地可演示的全链路,且评审老师只关心“能不能跑通”“逻辑清不清”“数据存不存得住”,不关心高并发、微服务或云原生。Swing 提供开箱即用的桌面 GUI,JDBC 直连 MySQL 避开 ORM 抽象层带来的黑匣子(比如 MyBatis 的 XML 映射错位、Spring Boot 自动配置冲突),MySQL 本地安装简单、命令直观、日志清晰——三者组合,恰恰构成了一条最短路径验证能力的技术栈。尤其当学生面对“Java 环境配不起来”“Maven 依赖拉不下来”“Tomcat 启动报 404”这类高频翻车点时,Swing 程序双击 jar 就能运行,JDBC 连接字符串写死在代码里也一目了然,MySQL 用mysqld --console就能看到实时 SQL 执行日志。这不是倒退,而是把注意力精准锚定在“业务逻辑是否自洽”这个核心上。适合大三下至大四上、Java 基础扎实但工程经验尚浅、需在 4~6 周内交付可演示、可讲解、可调试的完整系统的学生。


2. 从零搭建:用 Swing 构建可交互的选题管理主界面,不是拖控件,而是用代码控布局

Swing 不是“拖拽完就交差”的玩具,它的价值在于完全掌控 UI 生命周期与事件流。毕业设计选题系统的核心界面必须支持:教师发布课题、学生查看/选择/撤回、管理员审核/统计——这些操作不能靠JFrame默认布局糊弄,必须用BorderLayout+GridBagLayout混合嵌套,保证不同角色登录后动态切换功能区,同时避免组件重叠、缩放错乱等玄学问题。

2.1 主窗口结构:用 BorderLayout 划分功能区,避免嵌套失控

主窗口采用BorderLayout作为顶层容器,这是 Swing 中最稳定、最易理解的布局策略。顶部放角色标识栏(显示“当前用户:张三(学生)”),中部用JTabbedPane承载不同角色视图(教师 tab / 学生 tab / 管理员 tab),底部固定状态栏(显示“已连接 MySQL 8.0.33”)。这种结构的好处是:各区域职责单一,add(Component, BorderLayout.CENTER)等调用语义清晰,不会像GroupLayout那样因 IDE 自动生成代码导致维护困难。

public class MainFrame extends JFrame { private JTabbedPane tabbedPane; private JLabel statusLabel; public MainFrame() { setTitle("毕业设计选题管理系统"); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setLayout(new BorderLayout()); // 顶部:角色信息栏 JPanel topPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); topPanel.add(new JLabel("当前用户:")); JLabel userLabel = new JLabel("未登录"); topPanel.add(userLabel); add(topPanel, BorderLayout.NORTH); // 中部:选项卡面板(后续动态填充) tabbedPane = new JTabbedPane(); add(tabbedPane, BorderLayout.CENTER); // 底部:状态栏 JPanel bottomPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); statusLabel = new JLabel("未连接数据库"); bottomPanel.add(statusLabel); add(bottomPanel, BorderLayout.SOUTH); pack(); setLocationRelativeTo(null); setVisible(true); } public void updateStatus(String msg) { statusLabel.setText(msg); } }

提示:pack()必须在所有组件添加完毕后调用,否则窗口尺寸异常;setLocationRelativeTo(null)让窗口居中显示,比setBounds()更适配不同分辨率屏幕。

2.2 学生选题 Tab:用 GridBagLayout 实现紧凑表单,解决字段对齐难题

学生 Tab 需要展示课题列表(JTable)、筛选条件(JComboBox+JTextField)、操作按钮(“选择”“撤回”“刷新”)。若用FlowLayout或GridLayout,会导致“课题名称”文本框过宽、“专业方向”下拉框过窄、“选择”按钮被挤到角落。GridBagLayout是唯一能精确控制每个组件权重、填充方式、内外边距的方案。

关键参数说明:

  • gridwidth = GridBagConstraints.REMAINDER:让“刷新”按钮独占一行;
  • weightx = 1.0:让课题表格水平撑满;
  • fill = GridBagConstraints.BOTH:让表格填满分配空间;
  • insets = new Insets(5,5,5,5):统一内外边距,避免视觉粘连。
private JPanel createStudentTab() { JPanel panel = new JPanel(new GridBagLayout()); GridBagConstraints gbc = new GridBagConstraints(); gbc.insets = new Insets(5, 5, 5, 5); gbc.fill = GridBagConstraints.HORIZONTAL; // 筛选行 gbc.gridx = 0; gbc.gridy = 0; gbc.weightx = 0.3; panel.add(new JLabel("专业方向:"), gbc); gbc.gridx = 1; gbc.weightx = 0.7; JComboBox<String> majorCombo = new JComboBox<>(new String[]{"计算机科学", "软件工程", "人工智能"}); panel.add(majorCombo, gbc); gbc.gridx = 0; gbc.gridy = 1; gbc.weightx = 0.3; panel.add(new JLabel("关键词:"), gbc); gbc.gridx = 1; gbc.weightx = 0.7; JTextField keywordField = new JTextField(20); panel.add(keywordField, gbc); // 表格(使用 DefaultTableModel 动态填充) gbc.gridx = 0; gbc.gridy = 2; gbc.gridwidth = 2; gbc.weighty = 1.0; gbc.fill = GridBagConstraints.BOTH; JTable table = new JTable(new DefaultTableModel( new Object[][]{}, new String[]{"ID", "课题名称", "指导教师", "专业方向", "状态"} )); JScrollPane scrollPane = new JScrollPane(table); panel.add(scrollPane, gbc); // 按钮行 gbc.gridy = 3; gbc.gridwidth = GridBagConstraints.REMAINDER; gbc.fill = GridBagConstraints.NONE; gbc.anchor = GridBagConstraints.EAST; JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); buttonPanel.add(new JButton("选择")); buttonPanel.add(new JButton("撤回")); buttonPanel.add(new JButton("刷新")); panel.add(buttonPanel, gbc); return panel; }

注意:JTable的DefaultTableModel必须在构造时传入空数据和列名,后续用addRow(Object[])动态追加,不能直接修改Vector内部数据——否则表格不刷新。这是 Swing 事件模型的硬性约定,不是 bug。


3. 数据驱动:用 JDBC 直连 MySQL,绕过 ORM 黑箱,把每一条 SQL 都攥在手里

JDBC 不是“老古董”,而是调试友好性与教学透明度的终极保障。当学生在答辩现场被问“你查课题用了什么 SQL?怎么防止 SQL 注入?事务在哪提交?”时,手写PreparedStatement的代码就是最硬的底气。MyBatis 的<if>标签、Spring 的@Transactional注解,在答辩环节反而容易暴露理解盲区——而conn.prepareStatement("SELECT * FROM topic WHERE major = ? AND status = ?")这一行,谁都看得懂、改得动、断点跟得进。

3.1 连接池不是必需品:HikariCP 对毕业设计是过度设计,DriverManager 足够稳

很多教程一上来就教 HikariCP,但对单机演示系统,它带来的复杂度远超收益:要配application.properties、要处理DataSource注入、要理解连接泄漏检测阈值。毕业设计阶段,用DriverManager.getConnection()直连,配合try-with-resources自动关闭,既安全又清爽。

MySQL 8+ 驱动必须用com.mysql.cj.jdbc.Driver,URL 格式强制要求serverTimezone参数,否则ResultSet.getDate()返回 null:

public class DatabaseConnection { private static final String URL = "jdbc:mysql://localhost:3306/thesis_db?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true"; private static final String USER = "root"; private static final String PASSWORD = "123456"; public static Connection getConnection() throws SQLException { try { Class.forName("com.mysql.cj.jdbc.Driver"); // 显式加载驱动,避免 ClassNotFoundException } catch (ClassNotFoundException e) { throw new RuntimeException("MySQL JDBC Driver not found", e); } return DriverManager.getConnection(URL, USER, PASSWORD); } }

血泪经验:allowPublicKeyRetrieval=true是 MySQL 8.0.28+ 的强制要求,否则抛Public Key Retrieval is not allowed异常;useSSL=false在本地开发环境可关闭 SSL,避免证书配置麻烦——这不等于生产环境可忽略安全,而是教学场景下优先保证连通性。

3.2 CRUD 封装:用泛型 DAO 统一模板,但绝不隐藏 SQL

DAO 层不是为了炫技,而是为了让增删改查逻辑集中、可审计、可替换。我们不封装成topicService.save(topic),而是写成TopicDao.insert(topic),方法体内明明白白写着INSERT INTO topic (...) VALUES (?, ?, ?)。这样,学生能一眼看出字段顺序是否与数据库一致、?占位符个数是否匹配、executeUpdate()返回值是否为 1(表示成功插入一行)。

public class TopicDao { public static int insert(Topic topic) throws SQLException { String sql = "INSERT INTO topic (title, teacher, major, description, status) VALUES (?, ?, ?, ?, ?)"; try (Connection conn = DatabaseConnection.getConnection(); PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { ps.setString(1, topic.getTitle()); ps.setString(2, topic.getTeacher()); ps.setString(3, topic.getMajor()); ps.setString(4, topic.getDescription()); ps.setString(5, topic.getStatus()); // "待选" / "已选" / "已审核" int rowsAffected = ps.executeUpdate(); if (rowsAffected == 1) { try (ResultSet rs = ps.getGeneratedKeys()) { if (rs.next()) { topic.setId(rs.getInt(1)); // 获取自增主键 } } } return rowsAffected; } } public static List<Topic> findByMajorAndStatus(String major, String status) throws SQLException { String sql = "SELECT id, title, teacher, major, description, status FROM topic WHERE major = ? AND status = ?"; try (Connection conn = DatabaseConnection.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, major); ps.setString(2, status); List<Topic> topics = new ArrayList<>(); try (ResultSet rs = ps.executeQuery()) { while (rs.next()) { Topic t = new Topic(); t.setId(rs.getInt("id")); t.setTitle(rs.getString("title")); t.setTeacher(rs.getString("teacher")); t.setMajor(rs.getString("major")); t.setDescription(rs.getString("description")); t.setStatus(rs.getString("status")); topics.add(t); } } return topics; } } }

关键细节:ps.executeUpdate()返回受影响行数,必须校验是否为 1,否则可能是字段类型不匹配(如VARCHAR插入超长字符串被截断)或唯一键冲突;Statement.RETURN_GENERATED_KEYS参数让getGeneratedKeys()可用,这是获取自增 ID 的标准方式,比SELECT LAST_INSERT_ID()更安全。


4. 避坑:Swing + JDBC + MySQL 组合下,90% 的翻车都发生在这 4 个地方

Swing 界面卡顿、JDBC 连不上、MySQL 字段存乱码、事务不生效……这些不是“运气不好”,而是有迹可循的共性陷阱。以下是我带过 17 届毕设学生总结出的最高频、最隐蔽、最容易被答辩老师揪住的 4 类问题,每一条都附带现象、根因和可立即执行的修复动作。

4.1 现象:Swing 界面点击按钮无反应,Console 无任何输出

原因:事件监听器未正确绑定,或ActionListener写在了非 EDT(Event Dispatch Thread)线程中,导致 GUI 线程阻塞。常见于在SwingWorker外部直接调用耗时 JDBC 操作(如TopicDao.findByMajorAndStatus()),而没做线程隔离。
解决:所有数据库操作必须包裹在SwingWorker中,done()方法里更新 UI。禁止在actionPerformed()里直接写conn.prepareStatement(...)。

// ✅ 正确:耗时操作在后台线程,UI 更新在 EDT new SwingWorker<List<Topic>, Void>() { @Override protected List<Topic> doInBackground() throws Exception { return TopicDao.findByMajorAndStatus("计算机科学", "待选"); } @Override protected void done() { try { List<Topic> topics = get(); // get() 在 EDT 中执行 updateTable(topics); // 安全更新 JTable } catch (Exception e) { JOptionPane.showMessageDialog(null, "查询失败:" + e.getMessage()); } } }.execute();

4.2 现象:MySQL 中中文显示为??,但 Navicat 查看正常

原因:JDBC URL 缺少characterEncoding=utf8mb4,或 MySQL 服务端my.cnf中collation-server未设为utf8mb4_unicode_ci。即使数据库、表、字段都是 utf8mb4,JDBC 连接层默认用 latin1 解码。
解决:在 JDBC URL 末尾追加&characterEncoding=utf8mb4&useUnicode=true,并确认 MySQL 服务重启后生效:

# 检查 MySQL 当前编码 mysql> SHOW VARIABLES LIKE 'character_set%'; mysql> SHOW VARIABLES LIKE 'collation%'; # 若 server 和 database 不是 utf8mb4,修改 my.cnf 后重启 [mysqld] character-set-server = utf8mb4 collation-server = utf8mb4_unicode_ci

4.3 现象:学生“选择课题”后,数据库status字段仍为“待选”,刷新界面不变

原因:JDBC 事务未提交。默认情况下Connection的autoCommit为true,但若之前手动调用过conn.setAutoCommit(false)(比如为实现“选题+生成选题记录”原子性),后续忘记conn.commit(),则所有 DML 操作在连接关闭时自动回滚。
解决:严格遵循“显式开启 → 执行 → 显式提交/回滚”流程,用try-catch-finally保底:

Connection conn = null; try { conn = DatabaseConnection.getConnection(); conn.setAutoCommit(false); // 执行 update status // 执行 insert selection_record conn.commit(); // ✅ 必须显式调用 } catch (SQLException e) { if (conn != null) { try { conn.rollback(); } catch (SQLException ignored) {} } throw e; } finally { if (conn != null) { try { conn.close(); } catch (SQLException ignored) {} } }

4.4 现象:JTable数据更新后,界面不刷新,或新行显示在错误位置

原因:DefaultTableModel的addRow()方法必须在 EDT 中调用,且不能在SwingWorker的doInBackground()里调用。更致命的是,直接修改model.getDataVector()返回的Vector,会破坏TableModel的事件通知机制。
解决:所有addRow()、removeRow()、setValueAt()必须在SwingWorker.done()或SwingUtilities.invokeLater()中执行;永远不要碰getDataVector():

// ✅ 正确:通过 model 方法操作 DefaultTableModel model = (DefaultTableModel) table.getModel(); model.setRowCount(0); // 清空 for (Topic t : topics) { model.addRow(new Object[]{ t.getId(), t.getTitle(), t.getTeacher(), t.getMajor(), t.getStatus() }); } // ❌ 错误:直接改 Vector(导致表格不刷新) Vector data = model.getDataVector(); data.add(new Object[]{...}); // 不触发 fireTableRowsInserted()

5. 真实可跑的最小可行系统:3 个文件搞定登录 + 选题 + 审核闭环

一个毕业设计系统,不需要 50 个类、2000 行代码。我给学生定的硬性标准是:核心功能(登录、查课题、选课题、审核)能在 3 个 Java 文件里跑通,且每个文件不超过 300 行。这逼着人聚焦本质——数据库设计、SQL 正确性、事件流完整性。下面给出经过 2024 届学生实测的精简骨架,复制粘贴即可编译运行(JDK 11+,MySQL 8.0+)。

5.1 数据库建表 SQL:只保留最必要字段,拒绝过度设计

-- 创建数据库 CREATE DATABASE IF NOT EXISTS thesis_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE thesis_db; -- 课题表 CREATE TABLE topic ( id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(200) NOT NULL, teacher VARCHAR(50) NOT NULL, major VARCHAR(50) NOT NULL, description TEXT, status ENUM('待选', '已选', '已审核') DEFAULT '待选', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- 学生表(简化版,仅存学号、姓名、专业) CREATE TABLE student ( id INT PRIMARY KEY AUTO_INCREMENT, student_id VARCHAR(20) UNIQUE NOT NULL, name VARCHAR(50) NOT NULL, major VARCHAR(50) NOT NULL ); -- 选题记录表(关联学生与课题) CREATE TABLE selection ( id INT PRIMARY KEY AUTO_INCREMENT, student_id VARCHAR(20) NOT NULL, topic_id INT NOT NULL, status ENUM('待审核', '已通过', '已驳回') DEFAULT '待审核', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (student_id) REFERENCES student(student_id), FOREIGN KEY (topic_id) REFERENCES topic(id) );

为什么不用外键级联?毕业设计答辩时,老师可能用 Navicat 手动删数据测试边界情况。ON DELETE CASCADE会导致删除课题时连带删掉所有选题记录,掩盖逻辑缺陷。显式DELETE FROM selection WHERE topic_id = ?才体现事务意识。

5.2 登录与角色路由:用静态 Map 模拟用户体系,拒绝 Spring Security 复杂度

// LoginDialog.java public class LoginDialog extends JDialog { private JTextField usernameField; private JPasswordField passwordField; private String role; // "teacher", "student", "admin" public LoginDialog(Frame owner) { super(owner, "用户登录", true); setLayout(new GridLayout(3, 2, 10, 10)); add(new JLabel("用户名:")); usernameField = new JTextField(); add(usernameField); add(new JLabel("密码:")); passwordField = new JPasswordField(); add(passwordField); JButton loginBtn = new JButton("登录"); loginBtn.addActionListener(e -> doLogin()); add(loginBtn); pack(); setLocationRelativeTo(owner); } private void doLogin() { String username = usernameField.getText().trim(); String password = new String(passwordField.getPassword()); // 简化认证:硬编码模拟(实际应查数据库) if ("admin".equals(username) && "123".equals(password)) { role = "admin"; } else if (username.startsWith("stu_") && "123".equals(password)) { role = "student"; } else if (username.startsWith("tea_") && "123".equals(password)) { role = "teacher"; } else { JOptionPane.showMessageDialog(this, "用户名或密码错误"); return; } dispose(); // 关闭登录框 new MainFrame(role); // 传入角色,初始化对应 Tab } }

5.3 主程序入口:30 行启动代码,把 Swing、JDBC、MySQL 串成一条线

// Main.java public class Main { public static void main(String[] args) { // 设置 Swing 全局字体(解决 Win10 下中文模糊) try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeel()); Font font = new Font("Microsoft YaHei", Font.PLAIN, 12); UIManager.put("Label.font", font); UIManager.put("Button.font", font); UIManager.put("TextField.font", font); } catch (Exception e) { e.printStackTrace(); } // 启动登录对话框 SwingUtilities.invokeLater(() -> { LoginDialog dialog = new LoginDialog(null); dialog.setVisible(true); }); // 验证数据库连接(启动时执行一次) try (Connection conn = DatabaseConnection.getConnection()) { System.out.println("✅ 数据库连接成功:" + conn.getMetaData().getURL()); } catch (SQLException e) { JOptionPane.showMessageDialog(null, "数据库连接失败,请检查 MySQL 是否启动、账号密码是否正确\n" + e.getMessage(), "连接错误", JOptionPane.ERROR_MESSAGE); System.exit(1); } } }

关键技巧:SwingUtilities.invokeLater()是 Swing 程序的黄金入口,确保所有 GUI 创建都在 EDT 中;UIManager.setLookAndFeel()用系统原生风格,避免 Metal 风格在 Win10 上显示异常;启动时主动getConnection()并捕获异常,比运行时弹窗更早暴露环境问题。


6. 答辩加分项:3 个让老师眼前一亮的实战细节,不是炫技,而是真懂

答辩不是代码朗诵会,老师想看到的是你对技术选型的理解深度、对边界条件的预判能力、对真实问题的解决痕迹。以下三个细节,每个都能在 5 分钟内讲清楚原理、展示代码、复现效果,且全部来自真实毕设翻车现场——它们不增加功能,但能让老师笃定:“这学生真的跑通了,不是抄的”。

6.1 细节一:用JTable的TableCellRenderer实现状态色块,比文字更直观

“状态”字段只显示“待选”“已选”太单薄。用TableCellRenderer为不同状态渲染不同背景色,既提升可用性,又证明你理解了 Swing 的渲染机制。

// StatusCellRenderer.java public class StatusCellRenderer extends DefaultTableCellRenderer { @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { Component c = super.getTableCellRendererComponent( table, value, isSelected, hasFocus, row, column); String status = (String) value; switch (status) { case "待选": c.setBackground(Color.LIGHT_GRAY); break; case "已选": c.setBackground(new Color(173, 216, 230)); // 浅蓝 break; case "已审核": c.setBackground(new Color(144, 238, 144)); // 浅绿 break; default: c.setBackground(Color.WHITE); } return c; } } // 在创建 JTable 后设置 table.getColumnModel().getColumn(4).setCellRenderer(new StatusCellRenderer());

为什么值得讲:这不是 CSS,而是 Java 代码对每个单元格的逐个绘制控制。老师会意识到:你不仅会用JTable,还知道它背后是TableCellRenderer接口,且能定制实现——这比“用过 Spring Boot”更有说服力。

6.2 细节二:用JOptionPane的showConfirmDialog实现二次确认,防误操作

“撤回选题”按钮一点就删,太危险。加一层showConfirmDialog,返回JOptionPane.YES_OPTION才执行 JDBC 删除,这是最朴素的用户保护意识。

JButton withdrawBtn = new JButton("撤回"); withdrawBtn.addActionListener(e -> { int result = JOptionPane.showConfirmDialog( this, "确定要撤回该课题吗?此操作不可撤销!", "确认撤回", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE ); if (result == JOptionPane.YES_OPTION) { // 执行 JDBC 删除 selection 记录 try { SelectionDao.deleteByStudentAndTopic( currentStudentId, selectedTopicId ); JOptionPane.showMessageDialog(this, "撤回成功"); refreshTable(); // 重新加载 } catch (SQLException ex) { JOptionPane.showMessageDialog(this, "撤回失败:" + ex.getMessage()); } } });

答辩话术:“老师,这里我加了二次确认,因为选题是关键业务操作。如果直接删,学生手滑点错就无法恢复。用JOptionPane是最轻量的方案,不需要引入额外框架,也符合桌面应用的交互习惯。”

6.3 细节三:导出选题统计为 Excel,用 Apache POI 而不是截图

答辩时老师常问:“能导出数据吗?”截图是下策,用 Apache POI 生成.xlsx文件才是正解。只需 20 行代码,就能把JTable数据转成 Excel,且兼容 Office 和 WPS。

// 导出按钮事件 JButton exportBtn = new JButton("导出统计"); exportBtn.addActionListener(e -> { try (XSSFWorkbook workbook = new XSSFWorkbook(); FileOutputStream fileOut = new FileOutputStream("选题统计.xlsx")) { XSSFSheet sheet = workbook.createSheet("选题汇总"); String[] headers = {"课题ID", "课题名称", "指导教师", "专业方向", "选择人数"}; Row headerRow = sheet.createRow(0); for (int i = 0; i < headers.length; i++) { headerRow.createCell(i).setCellValue(headers[i]); } // 假设已有统计结果 List<StatItem> List<StatItem> stats = ReportService.getTopicStats(); for (int i = 0; i < stats.size(); i++) { Row row = sheet.createRow(i + 1); row.createCell(0).setCellValue(stats.get(i).getId()); row.createCell(1).setCellValue(stats.get(i).getTitle()); row.createCell(2).setCellValue(stats.get(i).getTeacher()); row.createCell(3).setCellValue(stats.get(i).getMajor()); row.createCell(4).setCellValue(stats.get(i).getCount()); } workbook.write(fileOut); JOptionPane.showMessageDialog(this, "导出完成:选题统计.xlsx"); } catch (IOException | SQLException ex) { JOptionPane.showMessageDialog(this, "导出失败:" + ex.getMessage()); } });

依赖添加(pom.xml):

<dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>5.2.4</version> </dependency>

我带毕设十年,见过太多学生花两周调通 Spring Boot + Vue 前后端分离,结果答辩时 Tomcat 突然报port 8080 already in use,当场崩溃。而用 Swing + JDBC + MySQL 的学生,往往在答辩前夜还在优化GridBagLayout的insets参数,第二天演示时双击 jar 包,界面秒开,SQL 日志清清楚楚,老师问哪句 SQL 怎么写的,他打开TopicDao.java指着prepareStatement就能讲。技术没有高低,只有是否服务于目标——毕业设计的目标,从来不是造轮子,而是证明你有能力把需求、代码、数据、界面,严丝合缝地拧成一股绳。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询