☰
Java Swing+MySQL教材管理系统实战指南
2026/10/8 4:04:55 网站建设 项目流程

简介:这是一套面向Java初学者与高校课程设计者的桌面端教材管理实战项目,基于Java Swing构建图形界面、MySQL实现数据持久化,帮助教育机构规范教材录入、分配与查询流程。资源包共53个文件,含18个Java源码文件(涵盖DAO层、MVC各模块及事件监听逻辑)、18个编译后class文件、2个MySQL数据库文件(含建表与初始化SQL)、2个Jar依赖库、6个GIF/JPG界面资源图及项目配置文件,整体3.39MB,结构完整,可直接导入Eclipse运行。已有1726人学习下载,配套代码严格遵循MVC分层架构与DAO模式,包含权限控制逻辑、异常处理机制及关键操作二次确认设计,便于理解Swing事件驱动编程、JDBC连接池实践及教育类系统数据库建模方法,是掌握Java桌面应用开发全流程的典型教学案例。

1. Java+Swing+MySQL学校教材管理系统:一个能跑通、能改、能交作业的桌面级教务工具

这不是一个“教学演示Demo”,也不是套壳的Java Swing练习题——它是一套真实可部署、带完整SQL建表语句、含DAO分层、支持多角色(管理员/教师)登录、教材增删改查+班级分配+借阅记录的轻量级教务管理桌面应用。我去年帮三所职业院校信息中心做系统迁移时,就是拿这个项目当原型快速落地的:不用装Tomcat、不依赖Web容器、双击jar就能启动,MySQL只要5.7+或8.0,连学生课程设计答辩都能直接演示数据流闭环。它解决的不是“能不能写”,而是“写完能不能用”——比如教材封面图路径存本地相对路径、删除前强制校验是否已被班级领用、Swing表格实时刷新不卡顿、中文字段名在MySQL里不乱码这些细节,全都在源码里埋了真实处理逻辑。适合Java初学者练手MVC分层,也适合教务老师临时搭个本地台账;更关键的是,它没用任何第三方UI框架(如JFormDesigner),所有Swing组件都是原生代码手写,你改界面、加字段、换数据库连接池,都不用猜黑匣子怎么调。


2. 从解压到运行:五步走通整个系统链路

2.1 解压结构解析:看清bin/src/sql/lib四大核心目录的真实分工

拿到学校教材管理系统.zip后,先别急着双击jar。打开压缩包,你会看到这四个关键目录:

  • src/:Java源码根目录,包含com.teaching.*包结构,model(实体类)、dao(数据库操作)、view(Swing界面)、controller(事件响应)四层清晰分离;
  • bin/:编译后的class文件目录,也是最终生成jar包的输出位置(注意:该目录下没有jar文件,需手动打包);
  • sql/:唯一可信的数据入口,内含teaching_material.sql—— 这是建库建表+初始化测试数据的完整脚本,不是示意伪代码;
  • lib/:仅含两个jar:mysql-connector-java-5.1.47.jar(驱动)和commons-dbcp-1.4.jar(连接池),无其他冗余依赖。

提示:不要试图用IDEA直接Import整个zip——它不是Maven项目,没有pom.xml。正确做法是新建空Java项目,将src设为source root,lib设为library,再手动配置JDK版本(建议1.8,因Swing组件兼容性与MySQL驱动版本强绑定)。

2.2 MySQL建库与初始化:执行teaching_material.sql前必须做的三件事

别跳过这一步。我见过太多人卡在“连接失败”,结果发现是SQL脚本里藏着隐性前提:

-- teaching_material.sql 开头片段(务必确认) CREATE DATABASE IF NOT EXISTS teaching_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE teaching_db; -- 注意:这里建表语句显式指定了 ENGINE=InnoDB 和 CHARSET=utf8mb4 CREATE TABLE `t_material` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(100) NOT NULL COMMENT '教材名称', `author` varchar(50) DEFAULT NULL COMMENT '作者', `publisher` varchar(100) DEFAULT NULL COMMENT '出版社', `isbn` varchar(20) DEFAULT NULL COMMENT 'ISBN号', `price` decimal(10,2) DEFAULT NULL COMMENT '定价', `stock` int(11) DEFAULT '0' COMMENT '库存数量', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

执行前请确认:

  1. MySQL服务已启动:Windows下运行net start mysql,Linux下sudo systemctl status mysql;
  2. 字符集必须为utf8mb4:执行SHOW VARIABLES LIKE 'character_set%';,若character_set_server不是utf8mb4,需修改my.cnf(Windows是my.ini):
    [mysqld] character-set-server = utf8mb4 collation-server = utf8mb4_unicode_ci
    修改后重启MySQL服务;
  3. 用户权限足够:默认脚本用root@localhost,若你用自定义账号(如teaching_user),需提前授权:
    CREATE USER 'teaching_user'@'localhost' IDENTIFIED BY 'YourPass123!'; GRANT ALL PRIVILEGES ON teaching_db.* TO 'teaching_user'@'localhost'; FLUSH PRIVILEGES;

执行脚本后,用SELECT COUNT(*) FROM t_material;验证是否插入了5条测试教材数据(脚本末尾有INSERT语句)。

2.3 编译与打包:用javac命令而非IDE一键构建,避开classpath陷阱

该系统未使用构建工具,必须手动编译。关键在于classpath顺序——lib/下的jar必须在src/之前被引用,否则编译时找不到Connection或BasicDataSource:

# 假设你在项目根目录(即含src/、lib/的目录) # Windows(注意分号分隔) javac -d bin -sourcepath src -cp "lib/mysql-connector-java-5.1.47.jar;lib/commons-dbcp-1.4.jar" src/com/teaching/view/MainFrame.java # Linux/macOS(注意冒号分隔) javac -d bin -sourcepath src -cp "lib/mysql-connector-java-5.1.47.jar:lib/commons-dbcp-1.4.jar" src/com/teaching/view/MainFrame.java

编译成功后,bin/目录下会生成完整的class文件树。接着打包成可执行jar:

# 生成MANIFEST.MF文件(关键!指定Main-Class和Class-Path) echo "Manifest-Version: 1.0" > MANIFEST.MF echo "Main-Class: com.teaching.view.MainFrame" >> MANIFEST.MF echo "Class-Path: lib/mysql-connector-java-5.1.47.jar lib/commons-dbcp-1.4.jar" >> MANIFEST.MF # 打包(注意jar命令参数顺序:c为创建,v为详细,f为文件名,m为清单) jar cvfm TeachingMaterialSystem.jar MANIFEST.MF -C bin .

逻辑说明:-C bin .表示把bin目录下所有内容(含包路径)打包进去;Class-Path中路径是相对于jar包所在目录的,所以lib必须和jar同级。若漏写-m MANIFEST.MF,双击jar会报“no main manifest attribute”。

2.4 运行与首次登录:绕过硬编码密码的三种方式

系统默认登录账号密码写死在com.teaching.dao.UserDaoImpl.java的login()方法里:

// UserDaoImpl.java 片段 public User login(String username, String password) { String sql = "SELECT * FROM t_user WHERE username = ? AND password = ?"; // ... PreparedStatement执行 }

但密码是明文存储的(测试用,非生产):

  • 管理员账号:admin/123456
  • 教师账号:teacher1/123456

首次运行前,你有三个选择:

  1. 直接用默认账号登录:启动jar后,在登录框输入即可;
  2. 修改数据库密码:用MySQL客户端执行:
    UPDATE t_user SET password = 'newpass123' WHERE username = 'admin';
  3. 改源码加MD5校验(推荐):在UserDaoImpl.login()中,对传入的password做MD5再比对:
    import java.security.MessageDigest; // ... 在查询前 String md5Pass = DigestUtils.md5Hex(password); // 或手写MD5工具类 ps.setString(2, md5Pass);
    对应地,初始化数据时也要用MD5加密插入(teaching_material.sql中t_user表的password字段值需同步更新)。

3. 源码级功能拆解:看懂MVC如何在Swing里真正落地

3.1 Model层:实体类与数据库表的双向映射细节

src/com/teaching/model/下的实体类不是简单POJO,每个字段都对应SQL建表语句中的COMMENT,且含业务约束:

// Material.java public class Material { private Integer id; // 对应t_material.id,AUTO_INCREMENT private String name; // NOT NULL,界面上必填校验触发点 private String author; // 允许NULL,Swing文本框允许为空 private BigDecimal price; // 使用BigDecimal而非double,避免价格计算精度丢失 private Integer stock; // 默认0,新增时界面默认显示"0"而非null // getter/setter省略... // 业务方法:判断是否可被借出(库存>0) public boolean canBorrow() { return stock != null && stock > 0; } }

参数说明:price用BigDecimal是硬性要求——Swing中JFormattedTextField配合NumberFormatter能精准控制小数位(见MaterialAddPanel.java),而double在19.99 + 0.01时可能得19.999999999999996,导致库存扣减错误。这是Java桌面开发里容易翻车的玄学点。

3.2 DAO层:DBCP连接池配置与SQL注入防护实操

src/com/teaching/dao/impl/中的DAO实现类,全部继承自BaseDao,其核心是BasicDataSource的静态初始化:

// BaseDao.java private static BasicDataSource dataSource; static { dataSource = new BasicDataSource(); dataSource.setDriverClassName("com.mysql.jdbc.Driver"); // 注意:MySQL 8.0+需改为"com.mysql.cj.jdbc.Driver" dataSource.setUrl("jdbc:mysql://localhost:3306/teaching_db?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=GMT%2B8"); dataSource.setUsername("root"); dataSource.setPassword("123456"); dataSource.setInitialSize(5); dataSource.setMaxActive(20); dataSource.setMaxWait(60000); }

关键参数说明:

  • serverTimezone=GMT%2B8:解决MySQL 8.0+时区报错(The server time zone value '...' is unrecognized);
  • useUnicode=true&characterEncoding=utf8mb4:确保中文存取不乱码,比只写utf8更严格(utf8在MySQL中实际是utf8mb3);
  • setMaxActive(20):连接池最大连接数,高于学校场景并发需求(通常<10人同时操作),避免耗尽MySQL连接数。

所有DAO方法均使用PreparedStatement,杜绝字符串拼接SQL:

// MaterialDaoImpl.java public int add(Material material) { String sql = "INSERT INTO t_material (name, author, publisher, isbn, price, stock) VALUES (?, ?, ?, ?, ?, ?)"; return update(sql, material.getName(), material.getAuthor(), material.getPublisher(), material.getIsbn(), material.getPrice(), material.getStock()); }

逻辑说明:update()是BaseDao提供的通用方法,内部自动获取连接、预编译、设参、执行、关闭资源。你不需要手动conn.close()——DBCP会回收到池中。

3.3 View层:Swing组件布局策略与事件响应链

界面不是用GUI Builder拖出来的,而是纯代码布局,采用BorderLayout+GridBagLayout混合:

// MainFrame.java 片段 public class MainFrame extends JFrame { private JMenuBar menuBar; private JMenu systemMenu, materialMenu, classMenu; private JMenuItem exitItem, addMaterialItem, listMaterialItem; public MainFrame() { setTitle("学校教材管理系统 v1.0"); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setLayout(new BorderLayout()); // 主窗体用BorderLayout // 顶部菜单栏 menuBar = new JMenuBar(); systemMenu = new JMenu("系统"); exitItem = new JMenuItem("退出"); exitItem.addActionListener(e -> System.exit(0)); // 直接监听,不走Controller systemMenu.add(exitItem); // 中央主面板(由CardLayout管理多个视图) cardPanel = new JPanel(new CardLayout()); cardPanel.add(new LoginPanel(), "login"); // 登录页 cardPanel.add(new AdminPanel(), "admin"); // 管理员页 cardPanel.add(new TeacherPanel(), "teacher"); // 教师页 add(cardPanel, BorderLayout.CENTER); } }

技术要点:CardLayout是Swing里切换视图的低成本方案(比JTabbedPane更轻量),LoginPanel提交后通过cardLayout.show(cardPanel, "admin")切换,避免反复new窗口对象。所有按钮事件监听器都注册在View层,但业务逻辑全部委托给Controller(如addMaterialItem.addActionListener(new MaterialAddController())),这才是MVC的Swing实践。

3.4 Controller层:事件驱动与业务逻辑解耦的关键枢纽

src/com/teaching/controller/下的Controller类,是Swing事件与DAO调用的翻译官:

// MaterialAddController.java public class MaterialAddController implements ActionListener { private MaterialAddPanel panel; // 持有View引用 private MaterialDao materialDao; // 持有DAO引用 public MaterialAddController(MaterialAddPanel panel) { this.panel = panel; this.materialDao = new MaterialDaoImpl(); } @Override public void actionPerformed(ActionEvent e) { // 1. 从View提取数据 Material material = panel.getMaterialFromForm(); // 2. 业务校验(非空、价格正数、ISBN格式) if (!validate(material)) { JOptionPane.showMessageDialog(panel, "请检查输入项!", "输入错误", JOptionPane.ERROR_MESSAGE); return; } // 3. 调用DAO持久化 try { int result = materialDao.add(material); if (result > 0) { JOptionPane.showMessageDialog(panel, "添加成功!", "提示", JOptionPane.INFORMATION_MESSAGE); panel.clearForm(); // 清空表单 panel.fireTableDataChanged(); // 刷新表格 } } catch (SQLException ex) { JOptionPane.showMessageDialog(panel, "数据库操作失败:" + ex.getMessage(), "错误", JOptionPane.ERROR_MESSAGE); } } }

逻辑说明:Controller不处理UI渲染(那是View的事),也不碰SQL(那是DAO的事),只做三件事:取数据、校验、调DAO。fireTableDataChanged()是AbstractTableModel的回调方法,通知JTable重绘——这是Swing MVC里View刷新的标准姿势,比table.setModel(new DefaultTableModel(...))更高效。


4. 避坑指南:Swing+MySQL组合下最常踩的五个深坑

4.1 现象:启动jar时报java.lang.ClassNotFoundException: com.mysql.jdbc.Driver

原因:MySQL 8.0+驱动类名已从com.mysql.jdbc.Driver改为com.mysql.cj.jdbc.Driver,但源码中仍用旧类名;且Class-Path在MANIFEST.MF中路径错误(如写成lib/mysql-connector-java-5.1.47.jar但实际jar在./lib/下)。
解决:

  • 修改BaseDao.java中setDriverClassName("com.mysql.cj.jdbc.Driver");
  • 确认MANIFEST.MF中Class-Path值为lib/mysql-connector-java-5.1.47.jar(无多余空格,路径斜杠统一为/);
  • 若用MySQL 8.0.21+,URL中必须加serverTimezone=GMT%2B8参数。

4.2 现象:中文字段存入MySQL后变成??或乱码

原因:MySQL服务端、数据库、表、连接URL四层字符集不一致;或JVM启动参数未指定-Dfile.encoding=UTF-8。
解决:

  • 执行ALTER DATABASE teaching_db CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;;
  • 对每个表执行ALTER TABLE t_material CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;;
  • 在MANIFEST.MF的Class-Path后追加-Dfile.encoding=UTF-8(需改用java -Dfile.encoding=UTF-8 -jar TeachingMaterialSystem.jar启动);
  • 检查teaching_material.sql建表语句是否含DEFAULT CHARSET=utf8mb4。

4.3 现象:Swing表格(JTable)新增数据后不刷新,需重启才显示

原因:未调用TableModel.fireTableRowsInserted()或fireTableDataChanged();或DefaultTableModel被重新new导致引用丢失。
解决:

  • 确保MaterialListPanel中使用的TableModel是自定义继承AbstractTableModel的类(如MaterialTableModel),并在DAO操作后显式调用fireTableDataChanged();
  • 不要用table.setModel(new DefaultTableModel(...))覆盖原模型,而应复用同一实例并调用addRow()或setDataVector()。

4.4 现象:删除教材时提示“外键约束失败”,无法删除

原因:t_material_assign(教材分配表)与t_material存在外键关联,但SQL脚本中未定义ON DELETE CASCADE。
解决:

  • 手动修改外键约束(MySQL 5.7+):
    ALTER TABLE t_material_assign DROP FOREIGN KEY fk_material_id; ALTER TABLE t_material_assign ADD CONSTRAINT fk_material_id FOREIGN KEY (material_id) REFERENCES t_material(id) ON DELETE CASCADE;
  • 或在DAO删除前,先执行DELETE FROM t_material_assign WHERE material_id = ?。

4.5 现象:教师登录后看不到“教材分配”功能菜单

原因:TeacherPanel.java中菜单构建逻辑缺失,或MainFrame中角色权限判断写死(如只对username.equals("admin")开放)。
解决:

  • 在MainFrame构造函数中,根据登录用户角色动态构建菜单:
    if ("admin".equals(user.getRole())) { menuBar.add(adminMenu); // 管理员专属菜单 } else if ("teacher".equals(user.getRole())) { menuBar.add(teacherMenu); // 教师专属菜单 }
  • 确保t_user表中有role字段(VARCHAR(10)),且初始化数据中teacher1的role值为teacher。

5. 进阶改造:让系统真正适配你的学校业务场景

5.1 加入教材封面图上传与本地存储

当前系统只存教材文字信息,但实际教务需管理封面图。改造步骤如下:

  1. 扩展数据库表:在t_material中增加cover_path VARCHAR(200)字段,存相对路径(如images/math1.jpg);
  2. 修改Material实体类:添加private String coverPath;及getter/setter;
  3. 改造MaterialAddPanel:加入JButton selectImageBtn和JLabel coverPreview;
    selectImageBtn.addActionListener(e -> { JFileChooser fc = new JFileChooser(); fc.setFileFilter(new FileNameExtensionFilter("图片文件", "jpg", "jpeg", "png")); if (fc.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) { File selected = fc.getSelectedFile(); String relativePath = "images/" + selected.getName(); // 存相对路径 coverPath = relativePath; // 复制文件到项目根目录/images/下 try { Files.copy(selected.toPath(), Paths.get(relativePath), StandardCopyOption.REPLACE_EXISTING); } catch (IOException ex) { JOptionPane.showMessageDialog(this, "图片保存失败:" + ex.getMessage()); } } });
  4. 在MaterialListPanel的表格渲染器中显示缩略图:重写TableCellRenderer,用ImageIcon加载coverPath指向的图片。

关键细节:路径必须用相对路径(避免绝对路径导致jar移植失败),图片复制到images/目录需在jar同级(即运行jar时,images/文件夹与jar平级)。这是桌面Java应用管理资源的常规做法,比存BLOB更轻量、易调试。

5.2 实现按学期筛选教材借阅记录

现有系统只有“教材列表”和“班级分配”,缺少按学期统计功能。需新增:

  • 数据库:在t_material_assign表中增加semester VARCHAR(20)字段(如2024-2025-1);
  • DAO层:在MaterialAssignDaoImpl中添加方法:
    public List<MaterialAssign> findBySemester(String semester) { String sql = "SELECT * FROM t_material_assign WHERE semester = ?"; return query(sql, MaterialAssign.class, semester); }
  • View层:在AdminPanel中添加JComboBox<String>供选择学期,并绑定ActionListener调用DAO;
  • Controller层:新建SemesterReportController,获取结果后用JTable展示,并支持导出Excel(用Apache POI)。

参数说明:学期字段用VARCHAR而非DATE,因学期是逻辑概念(如“2024-2025学年第一学期”),非时间点;query()是BaseDao封装的泛型查询方法,自动映射ResultSet到MaterialAssign对象。

5.3 将Swing界面升级为Dark Mode友好样式

Swing原生L&F(Metal)在高分屏上字体发虚,且无暗色主题。可引入FlatLaf(轻量级、无JNI依赖):

  1. 下载flatlaf-3.4.jar(最新版)放入lib/目录;
  2. 修改MainFrame构造函数首行:
    try { UIManager.setLookAndFeel(new FlatDarkLaf()); } catch (UnsupportedLookAndFeelException e) { e.printStackTrace(); }
  3. 为所有JTextField、JTextArea设置背景色(DarkLaf默认白色文字需深色背景):
    JTextField field = new JTextField(); field.setBackground(UIManager.getColor("TextField.background")); field.setForeground(UIManager.getColor("TextField.foreground"));

技术价值:FlatLaf支持系统级暗色模式检测(FlatLaf.isLafDark()),且体积仅300KB,比JavaFX或WebView方案更契合Swing项目定位。我一般会在MainFrame中加一个JCheckBox让用户手动切换明暗主题,状态存入PreferencesAPI。

从那以后我每次接手Swing项目,都会先检查三点:MySQL连接URL的时区参数、MANIFEST.MF的Class-Path路径、以及Swing表格的TableModel是否继承自AbstractTableModel——这三处不踩坑,剩下的就是业务逻辑堆砌了。希望帮到你。

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

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

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

立即咨询