Java图书管理系统SWT实战:从环境搭建到避坑指南

发布时间:2026/9/25 4:57:54
Java图书管理系统SWT实战:从环境搭建到避坑指南 简介这是一份基于SWT构建的Java图书管理系统图形化界面项目源码面向具备Java基础、希望学习桌面GUI开发与数据库交互的开发者可用于课程设计、毕业设计或自学练手。压缩包共259个文件约10.07MB包含45个java源文件、107个class编译文件、80个ico图标、12张jpg图片、7个jar依赖包以及sql建库脚本、doc说明文档、exe可执行程序和Eclipse工程配置文件覆盖从源码到可运行程序的完整链路。项目围绕图书信息管理、借阅归还、用户管理与统计报表等模块展开涉及SWT组件、事件驱动、布局管理、Table表格展示、对话框及JDBC数据库操作等知识点并体现面向对象与模块化编程思路。目前已有483人学习下载适合对照源码理解SWT界面搭建与数据库交互流程快速掌握桌面应用开发方法。1. 图书管理系统用SWT做的JAVA图形化界面桌面端选型为什么还有人坚持打开招聘网站搜「Java 图形化界面」跳出来的大多是 JavaFX、Swing 甚至 Web 前端方案SWT 出现的频率低得可怜。但如果你翻一翻高校的 java课程设计案例源码或者去接一些中小型内部管理系统的外包会发现 SWT 依然活得挺滋润——尤其是图书管理系统这类「表单密集、表格为主、部署在 Windows 内网」的场景。原因不复杂SWT 直接调用操作系统的原生控件在 Windows 上跑出来的界面跟系统自带程序几乎没差别表格渲染快、内存占用低打包成一个 exe 双击就能用不需要用户装浏览器、配环境变量。这篇文章面向的是正在做 Java 课程设计、需要交一个能跑起来的桌面图书管理系统或者想用 SWT 搭一套内部工具但不知道从哪下手的人。我会按「环境怎么搭 → 界面怎么拆 → 数据怎么连 → 坑在哪 → 怎么验证」的顺序把一套可复现的 SWT 图书管理系统实现路径讲清楚。不依赖任何不存在的源码包所有代码你都能自己敲出来跑通。2. SWT 环境搭建与图书管理系统项目骨架2.1 为什么选 SWT 而不是 Swing三个实际对比维度在动手之前先把选型这件事说透。很多人做 java课程设计 时随手选了 Swing结果界面在 Windows 上跑出来是 Metal 风格跟系统格格不入答辩时被老师问「为什么不用原生控件」就卡住了。SWT 和 Swing 的核心差异在渲染方式上Swing 是纯 Java 绘制所有控件由 JVM 自己画SWT 通过 JNI 调用操作系统的原生控件库Windows 上调 Win32 APILinux 上调 GTK。对比维度SWTSwing控件渲染操作系统原生JVM 自绘启动速度快无额外绘制层较慢首次加载明显表格大数据量虚拟表支持好万行不卡需要手动优化跨平台一致性各平台外观不同各平台外观一致学习曲线需理解 Display/Shell 生命周期组件模型更简单打包体积需带平台相关 jar纯 Java 即可图书管理系统这个场景表格是绝对主角——图书列表、借阅记录、读者信息全是表格。SWT 的 Table 和 TableViewer 在渲染几千行数据时性能优势明显而且列宽拖拽、排序、编辑这些交互都是原生行为不用自己写。另一个现实因素是很多学校的 java课程设计 模板就是 SWT 的跟着做能少踩坑。但 SWT 也有代价。它的 Display 和 Shell 生命周期管理比 Swing 严格控件必须显式 dispose否则内存泄漏。而且 SWT 的 jar 包是分平台的Windows 64 位、Linux、macOS 各一套打包时要注意。这些后面会细讲。2.2 用 Maven 拉取 SWT 依赖并跑通第一个窗口SWT 不在 JDK 标准库里必须单独引入。最省事的方式是用 Maven通过 profile 区分平台。下面是一个最小可运行的 pom.xml 配置。project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd modelVersion4.0.0/modelVersion groupIdcom.example/groupId artifactIdlibrary-swt/artifactId version1.0-SNAPSHOT/version packagingjar/packaging properties maven.compiler.source17/maven.compiler.source maven.compiler.target17/maven.compiler.target project.build.sourceEncodingUTF-8/project.build.sourceEncoding swt.version3.124.0/swt.version /properties profiles !-- Windows 64 位 -- profile idwindows/id activation osfamilywindows/familyarchamd64/arch/os /activation dependencies dependency groupIdorg.eclipse.platform/groupId artifactIdorg.eclipse.swt.win32.win32.x86_64/artifactId version${swt.version}/version /dependency /dependencies /profile !-- Linux GTK 64 位 -- profile idlinux/id activation osfamilyunix/familyarchamd64/arch/os /activation dependencies dependency groupIdorg.eclipse.platform/groupId artifactIdorg.eclipse.swt.gtk.linux.x86_64/artifactId version${swt.version}/version /dependency /dependencies /profile /profiles /project这段配置的关键点SWT 的 artifactId 带平台后缀win32.win32.x86_64 是 Windows 64 位gtk.linux.x86_64 是 Linux GTK。用 profile 的 os 激活条件让 Maven 自动选避免手动改。swt.version 我写的是 3.124.0这是较新的稳定版你可以在 Maven Central 上查最新版本替换。注意 Java 版本我设的 17如果你本地是 Java 8把 source/target 改成 1.8 也能跑但 SWT 新版本对高版本 JDK 支持更好。依赖拉下来之后写一个最小窗口验证环境是否正常。import org.eclipse.swt.SWT; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Label; public class HelloSwt { public static void main(String[] args) { // Display 是 SWT 的事件循环核心每个应用只有一个 Display display new Display(); // Shell 是顶层窗口相当于 JFrame Shell shell new Shell(display); shell.setText(图书管理系统 - 环境验证); shell.setSize(400, 200); Label label new Label(shell, SWT.CENTER); label.setText(SWT 环境正常); label.setBounds(100, 60, 200, 30); shell.open(); // 事件循环不断从系统取事件分发直到窗口关闭 while (!shell.isDisposed()) { if (!display.readAndDispatch()) { display.sleep(); } } display.dispose(); } }逻辑说明Display 是 SWT 与操作系统事件队列的桥梁整个应用只能有一个。Shell 是窗口容器构造时传入 Display。readAndDispatch 是事件泵有事件就处理没有就 sleep 让出 CPU。这个循环是 SWT 程序的标准骨架所有界面都套这个结构。参数方面SWT.CENTER 是 Label 的对齐样式setBounds 用的是绝对定位——图书管理系统界面不复杂绝对定位够用但后面我会讲什么时候该换 Layout。跑通这个窗口说明依赖和 JDK 都没问题。如果报 UnsatisfiedLinkError八成是平台 jar 选错了检查你的操作系统和架构。2.3 图书管理系统的包结构与模块划分一个能交作业、也能实际用的图书管理系统代码不能全堆在一个类里。我一般按职责分四个包com.example.library.ui所有 SWT 界面类主窗口、图书管理面板、借阅面板、读者管理面板com.example.library.dao数据访问层负责增删改查屏蔽数据库细节com.example.library.model实体类Book、Reader、BorrowRecordcom.example.library.util工具类数据库连接池、日期格式化、弹窗封装主窗口用 TabFolder 把功能分区每个 Tab 对应一个 Composite 子面板。这样拆的好处是界面代码和业务代码分离答辩时能说清楚分层后期加功能只需要加一个 Tab不用动主窗口逻辑。图书管理系统的核心表就三张books图书、readers读者、borrow_records借阅记录字段设计后面会给。3. 图书管理系统核心界面表格、表单与事件绑定3.1 用 TableViewer 构建可排序的图书列表图书列表是整个系统的门面用户打开程序第一眼就看这个。SWT 原生的 Table 能用但要做排序、过滤、编辑就得自己写一堆监听。TableViewer 是 JFace 提供的封装配合 ContentProvider 和 LabelProvider能把一个 List 直接映射成表格。import org.eclipse.jface.viewers.*; import org.eclipse.swt.SWT; import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.widgets.*; public class BookTableViewer { public static TableViewer create(Composite parent) { // SWT.MULTI 支持多选SWT.FULL_SELECTION 整行选中 TableViewer viewer new TableViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL | SWT.FULL_SELECTION | SWT.BORDER); Table table viewer.getTable(); table.setHeaderVisible(true); // 显示表头 table.setLinesVisible(true); // 显示网格线 // 定义五列ISBN、书名、作者、库存、状态 String[] titles {ISBN, 书名, 作者, 库存, 状态}; int[] widths {120, 200, 150, 80, 80}; for (int i 0; i titles.length; i) { TableColumn column new TableColumn(table, SWT.LEFT); column.setText(titles[i]); column.setWidth(widths[i]); // 点击表头排序 final int index i; column.addSelectionListener(new SelectionAdapter() { boolean asc true; Override public void widgetSelected(SelectionEvent e) { viewer.setComparator(new ViewerComparator() { Override public int compare(Viewer v, Object o1, Object o2) { Book b1 (Book) o1, b2 (Book) o2; int result getField(b1, index).compareTo(getField(b2, index)); return asc ? result : -result; } }); asc !asc; } }); } // 内容提供者把 List 转成表格行 viewer.setContentProvider(new ArrayContentProvider()); // 标签提供者决定每列显示什么 viewer.setLabelProvider(new LabelProvider() { Override public String getColumnText(Object element, int columnIndex) { Book book (Book) element; switch (columnIndex) { case 0: return book.getIsbn(); case 1: return book.getTitle(); case 2: return book.getAuthor(); case 3: return String.valueOf(book.getStock()); case 4: return book.getStock() 0 ? 可借 : 已借完; default: return ; } } }); return viewer; } private static String getField(Book b, int index) { switch (index) { case 0: return b.getIsbn(); case 1: return b.getTitle(); case 2: return b.getAuthor(); case 3: return String.valueOf(b.getStock()); default: return ; } } }逻辑说明ArrayContentProvider 是最简单的提供者直接把数组或 List 的每个元素当成一行。LabelProvider 的 getColumnText 决定每列渲染什么文本这里根据 columnIndex 分支返回不同字段。排序部分每次点击表头重新 setComparator用 asc 布尔量翻转升降序。注意 comparator 里比较的是字符串库存列如果按字符串排会出现「10 2」的问题实际项目里应该转成整数比较这里为了简洁没展开。参数方面SWT.FULL_SELECTION 让整行可选中用户体验比只选单元格好。setLinesVisible(true) 画网格线图书列表数据密有网格线更易读。列宽我给的是一组经验值ISBN 13 位数字 120px 够书名可能较长给 200px你可以根据实际数据调整。3.2 借阅与归还表单校验和数据库事务的配合借阅功能是图书管理系统的业务核心涉及三张表的联动借阅记录插入一条、图书库存减一、读者借阅数加一。这三步必须在一个事务里否则库存扣了但记录没插进去数据就乱了。import java.sql.*; public class BorrowService { public boolean borrowBook(int bookId, int readerId) throws SQLException { String checkStock SELECT stock FROM books WHERE id ? FOR UPDATE; String insertRecord INSERT INTO borrow_records(book_id, reader_id, borrow_date, status) VALUES(?,?,?,?); String updateStock UPDATE books SET stock stock - 1 WHERE id ? AND stock 0; String updateReader UPDATE readers SET borrow_count borrow_count 1 WHERE id ?; Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 开启事务 // 1. 锁定图书行检查库存 try (PreparedStatement ps conn.prepareStatement(checkStock)) { ps.setInt(1, bookId); ResultSet rs ps.executeQuery(); if (!rs.next() || rs.getInt(stock) 0) { conn.rollback(); return false; // 库存不足 } } // 2. 插入借阅记录 try (PreparedStatement ps conn.prepareStatement(insertRecord)) { ps.setInt(1, bookId); ps.setInt(2, readerId); ps.setDate(3, new Date(System.currentTimeMillis())); ps.setString(4, BORROWED); ps.executeUpdate(); } // 3. 扣减库存AND stock 0 是双重保险 try (PreparedStatement ps conn.prepareStatement(updateStock)) { ps.setInt(1, bookId); if (ps.executeUpdate() 0) { conn.rollback(); return false; } } // 4. 更新读者借阅计数 try (PreparedStatement ps conn.prepareStatement(updateReader)) { ps.setInt(1, readerId); ps.executeUpdate(); } conn.commit(); return true; } catch (SQLException e) { if (conn ! null) conn.rollback(); throw e; } finally { if (conn ! null) { conn.setAutoCommit(true); conn.close(); } } } }逻辑说明SELECT ... FOR UPDATE 在事务里锁定图书行防止两个用户同时借同一本书导致超借。setAutoCommit(false) 开启事务任何一步失败都 rollback。UPDATE 语句里的 AND stock 0 是并发安全的兜底——即使前面的检查通过了如果库存刚好被另一个事务扣完这条 UPDATE 会影响 0 行触发回滚。参数方面borrow_date 用 java.sql.Date 存日期status 用字符串枚举 BORROWED/RETURNED。实际项目里读者借阅上限、借阅天数这些规则应该做成配置不要硬编码。归还逻辑是借阅的逆操作把 status 改成 RETURNED、库存加一、借阅数减一同样要事务包裹。界面层调用这个 service 时要在按钮的 SelectionListener 里做表单校验读者 ID 是否选中、图书是否选中、库存是否大于零。校验不通过用 MessageBox 弹提示不要用 System.out.println用户看不到控制台。3.3 用 GridLayout 做自适应表单告别绝对定位前面 Hello World 用了 setBounds 绝对定位做图书管理系统的新增图书表单时如果还用绝对定位窗口一拉伸控件就错位。SWT 的 GridLayout 是表单场景最常用的布局管理器。import org.eclipse.swt.SWT; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.*; public class BookForm { public static Composite create(Composite parent) { Composite form new Composite(parent, SWT.NONE); // 两列左边标签右边输入框 GridLayout layout new GridLayout(2, false); layout.marginWidth 20; layout.marginHeight 15; layout.horizontalSpacing 10; layout.verticalSpacing 8; form.setLayout(layout); // 第一行ISBN new Label(form, SWT.NONE).setText(ISBN); Text isbnText new Text(form, SWT.BORDER); isbnText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); // 第二行书名 new Label(form, SWT.NONE).setText(书名); Text titleText new Text(form, SWT.BORDER); titleText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); // 第三行作者 new Label(form, SWT.NONE).setText(作者); Text authorText new Text(form, SWT.BORDER); authorText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); // 第四行库存用 Spinner 限制只能输数字 new Label(form, SWT.NONE).setText(库存); Spinner stockSpinner new Spinner(form, SWT.BORDER); stockSpinner.setMinimum(0); stockSpinner.setMaximum(9999); stockSpinner.setSelection(1); stockSpinner.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); // 第五行按钮跨两列右对齐 Composite buttonBar new Composite(form, SWT.NONE); GridData barData new GridData(SWT.FILL, SWT.CENTER, true, false); barData.horizontalSpan 2; // 跨两列 buttonBar.setLayoutData(barData); buttonBar.setLayout(new GridLayout(2, true)); Button saveBtn new Button(buttonBar, SWT.PUSH); saveBtn.setText(保存); saveBtn.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); Button cancelBtn new Button(buttonBar, SWT.PUSH); cancelBtn.setText(取消); cancelBtn.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); return form; } }逻辑说明GridLayout(2, false) 表示两列等宽makeColumnsEqualWidth 为 false 时列宽由内容决定。GridData 的四个参数是 horizontalAlignment、verticalAlignment、grabExcessHorizontalSpace、grabExcessVerticalSpace。输入框用 SWT.FILL grab true窗口拉伸时输入框跟着变宽。按钮栏用 horizontalSpan 2 跨两列内部再套一个 GridLayout(2, true) 让两个按钮等宽。参数方面marginWidth 和 marginHeight 控制表单四周留白horizontalSpacing 和 verticalSpacing 控制控件间距。这些值没有标准答案20/15/10/8 是我调出来比较舒服的一组。Spinner 比 Text 适合库存输入自带上下箭头和范围限制省掉正则校验。4. 数据持久化从 JDBC 到 SQLite 的落地选择4.1 图书管理系统三张核心表的建表语句图书管理系统的数据模型不复杂但字段设计要考虑实际使用。下面是我常用的建表 SQL以 SQLite 为例换成 MySQL 只需改自增主键和日期类型。-- 图书表 CREATE TABLE books ( id INTEGER PRIMARY KEY AUTOINCREMENT, isbn TEXT NOT NULL UNIQUE, title TEXT NOT NULL, author TEXT, publisher TEXT, stock INTEGER NOT NULL DEFAULT 0, total INTEGER NOT NULL DEFAULT 0, create_time TEXT DEFAULT (datetime(now, localtime)) ); -- 读者表 CREATE TABLE readers ( id INTEGER PRIMARY KEY AUTOINCREMENT, card_no TEXT NOT NULL UNIQUE, name TEXT NOT NULL, phone TEXT, borrow_count INTEGER NOT NULL DEFAULT 0, max_borrow INTEGER NOT NULL DEFAULT 5 ); -- 借阅记录表 CREATE TABLE borrow_records ( id INTEGER PRIMARY KEY AUTOINCREMENT, book_id INTEGER NOT NULL, reader_id INTEGER NOT NULL, borrow_date TEXT NOT NULL, return_date TEXT, status TEXT NOT NULL DEFAULT BORROWED, FOREIGN KEY (book_id) REFERENCES books(id), FOREIGN KEY (reader_id) REFERENCES readers(id) ); -- 常用索引按书名和作者搜索 CREATE INDEX idx_books_title ON books(title); CREATE INDEX idx_records_reader ON borrow_records(reader_id, status);逻辑说明books 表里 stock 是当前可借数total 是总藏书量两个字段分开存是为了区分「借完了」和「馆藏就一本」。readers 表的 max_borrow 控制每个读者的借阅上限默认 5 本。borrow_records 的 status 用 BORROWED/RETURNED 两个状态return_date 为空表示未归还。外键约束在 SQLite 里默认不开启需要执行 PRAGMA foreign_keys ONMySQL 的 InnoDB 默认就支持。索引方面书名搜索是高频操作建 idx_books_title。借阅记录按读者和状态查「某人当前借了哪些书」建联合索引 idx_records_reader。这两个索引能让列表查询从全表扫描降到索引查找数据量上千后差别明显。4.2 SQLite 嵌入式方案为什么它比 MySQL 更适合课程设计做 java课程设计 时很多同学一上来就装 MySQL结果答辩换台电脑演示数据库连不上当场翻车。SQLite 是嵌入式数据库整个库就是一个文件跟着项目走拷到 U 盘就能演示。对于图书管理系统这种单机、低并发、数据量不大的场景SQLite 完全够用。import java.sql.*; public class DBUtil { private static final String DB_URL jdbc:sqlite:library.db; static { try { // SQLite JDBC 驱动Maven 依赖 org.xerial:sqlite-jdbc Class.forName(org.sqlite.JDBC); } catch (ClassNotFoundException e) { throw new RuntimeException(SQLite 驱动加载失败, e); } } public static Connection getConnection() throws SQLException { Connection conn DriverManager.getConnection(DB_URL); // 开启外键约束SQLite 默认关闭 try (Statement st conn.createStatement()) { st.execute(PRAGMA foreign_keys ON); } return conn; } // 首次启动时初始化表结构 public static void initSchema() { String[] ddl { CREATE TABLE IF NOT EXISTS books (...), CREATE TABLE IF NOT EXISTS readers (...), CREATE TABLE IF NOT EXISTS borrow_records (...) }; try (Connection conn getConnection(); Statement st conn.createStatement()) { for (String sql : ddl) { st.execute(sql); } } catch (SQLException e) { e.printStackTrace(); } } }逻辑说明DB_URL 里的 library.db 是相对路径程序运行时在当前目录创建。Class.forName 加载驱动SQLite JDBC 驱动类名是 org.sqlite.JDBC。PRAGMA foreign_keys ON 必须每次连接都执行因为它是连接级别的设置。initSchema 用 CREATE TABLE IF NOT EXISTS程序每次启动都调一次已存在就跳过不会重复建表。参数方面Maven 依赖是dependency groupIdorg.xerial/groupId artifactIdsqlite-jdbc/artifactId version3.45.1.0/version /dependency版本号可以在 Maven Central 查最新的。SQLite 的并发写性能有限但图书管理系统就一个管理员在操作不存在并发问题。如果确实要多用户同时用再换 MySQLDAO 层的代码基本不用改只换连接串和驱动。4.3 DAO 层封装把增删改查从界面里抽干净界面类里直接写 SQL 是新手最容易犯的错改一个字段要翻遍所有界面。正确的做法是 DAO 层统一封装界面只调方法。import java.sql.*; import java.util.ArrayList; import java.util.List; public class BookDao { public ListBook findAll() { ListBook list new ArrayList(); String sql SELECT id, isbn, title, author, publisher, stock, total FROM books ORDER BY id DESC; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { while (rs.next()) { list.add(mapRow(rs)); } } catch (SQLException e) { e.printStackTrace(); } return list; } public ListBook searchByTitle(String keyword) { ListBook list new ArrayList(); // 用 LIKE 模糊匹配参数化防止 SQL 注入 String sql SELECT * FROM books WHERE title LIKE ? OR author LIKE ? ORDER BY id DESC; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, % keyword %); ps.setString(2, % keyword %); ResultSet rs ps.executeQuery(); while (rs.next()) { list.add(mapRow(rs)); } } catch (SQLException e) { e.printStackTrace(); } return list; } public boolean insert(Book book) { String sql INSERT INTO books(isbn, title, author, publisher, stock, total) VALUES(?,?,?,?,?,?); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, book.getIsbn()); ps.setString(2, book.getTitle()); ps.setString(3, book.getAuthor()); ps.setString(4, book.getPublisher()); ps.setInt(5, book.getStock()); ps.setInt(6, book.getTotal()); return ps.executeUpdate() 0; } catch (SQLException e) { e.printStackTrace(); return false; } } private Book mapRow(ResultSet rs) throws SQLException { Book b new Book(); b.setId(rs.getInt(id)); b.setIsbn(rs.getString(isbn)); b.setTitle(rs.getString(title)); b.setAuthor(rs.getString(author)); b.setPublisher(rs.getString(publisher)); b.setStock(rs.getInt(stock)); b.setTotal(rs.getInt(total)); return b; } }逻辑说明findAll 查全部searchByTitle 用 LIKE 做模糊搜索insert 插入新书。mapRow 把 ResultSet 的一行映射成 Book 对象抽成私有方法避免重复代码。所有查询都用 PreparedStatement 参数化这是防 SQL 注入的基本功也是 java面试题 里高频考点。参数方面LIKE 的 % 通配符拼在 Java 层不要拼在 SQL 字符串里否则参数化就白做了。ORDER BY id DESC 让新加的图书排在最前面符合使用习惯。try-with-resources 自动关闭 Connection、Statement、ResultSet不用手写 finally这是 Java 7 之后的写法代码干净很多。5. SWT 图书管理系统避坑与排查清单5.1 界面卡死事件循环里做了耗时操作现象点击「查询」按钮后整个窗口无响应转圈几秒后才恢复。原因SWT 的事件循环是单线程的按钮的 SelectionListener 在 UI 线程里执行。如果里面直接调 DAO 查数据库查询期间 UI 线程被占住无法重绘和响应。解决耗时操作放到后台线程查完用 Display.asyncExec 回到 UI 线程更新表格。button.addSelectionListener(new SelectionAdapter() { Override public void widgetSelected(SelectionEvent e) { // 后台线程查数据 new Thread(() - { ListBook books bookDao.searchByTitle(keywordText.getText()); // 回到 UI 线程更新控件 Display.getDefault().asyncExec(() - { viewer.setInput(books); viewer.refresh(); }); }).start(); } });注意SWT 控件只能在创建它的线程UI 线程里访问后台线程直接调 viewer.setInput 会抛 SWTException。asyncExec 把 Runnable 投递到 UI 线程的事件队列是 SWT 跨线程更新的标准做法。5.2 内存泄漏控件没 dispose 导致句柄耗尽现象程序运行一段时间后界面越来越卡或者打开关闭某个对话框几百次后报「No more handles」。原因SWT 控件持有操作系统资源字体、颜色、图像、窗口句柄这些资源不受 JVM 垃圾回收管理必须显式 dispose。如果动态创建控件却不销毁句柄会耗尽。解决动态创建的 Color、Font、Image 用完就 dispose对话框关闭时销毁其上的所有控件。Color red new Color(display, 255, 0, 0); label.setForeground(red); // 用完必须释放 red.dispose();对于 Shell 里的控件关闭 Shell 时 SWT 会自动 dispose 子控件但 Color、Font、Image 这些资源对象不会自动释放必须手动管理。我一般用一个 Map 缓存常用 Color程序退出时统一 dispose避免频繁创建销毁。5.3 中文乱码编码没统一导致书名显示问号现象从数据库读出来的中文书名显示成「???」或方块。原因三个环节的编码不一致——源文件编码、JDBC 连接编码、数据库存储编码。SWT 本身对中文支持没问题问题出在数据链路。解决源文件统一 UTF-8SQLite 的 JDBC 连接默认就是 UTF-8不用额外设MySQL 要在连接串加 characterEncodingutf8。另外 Maven 的 project.build.sourceEncoding 必须设 UTF-8否则编译时就把中文搞坏了。properties project.build.sourceEncodingUTF-8/project.build.sourceEncoding /properties如果已经乱码了检查数据库里存的是不是已经坏了。用 SQLite 命令行sqlite3 library.db SELECT title FROM books LIMIT 5;看原始数据如果这里就是问号说明写入时就坏了得重新录入。5.4 打包后运行报 UnsatisfiedLinkError现象在 IDE 里跑得好好的打成 jar 双击运行报UnsatisfiedLinkError: no swt-win32-xxxx in java.library.path。原因SWT 的 jar 里包含平台相关的 .dll 或 .so 文件IDE 运行时能自动解压加载但打成可执行 jar 后如果没把平台 jar 一起打进去或者用了错误的平台 jar就找不到本地库。解决用 Maven Shade 插件打 fat jar确保对应平台的 SWT jar 被包含。或者用mvn dependency:copy-dependencies把依赖拷到 lib 目录运行时用java -cp app.jar;lib/*启动。plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-shade-plugin/artifactId version3.5.0/version executions execution phasepackage/phase goalsgoalshade/goal/goals /execution /executions /plugin注意SWT 的 fat jar 只能包含当前平台的 jar不能把 Windows 和 Linux 的都打进去否则会冲突。跨平台分发要打多个包或者用 profile 分别构建。5.5 表格数据不刷新setInput 后忘了 refresh现象新增一本书后数据库里有了但表格里看不到要重启程序才显示。原因TableViewer 的 setInput 会重新加载数据但如果传入的是同一个 List 对象引用TableViewer 认为内容没变不触发重绘。或者只调了 setInput 没调 refresh。解决每次查询返回新的 List 对象setInput 后调 refresh。ListBook books bookDao.findAll(); // 每次都是新对象 viewer.setInput(books); viewer.refresh(); // 强制重绘如果用的是 ObservableList 或手动维护的集合增删元素后要调 viewer.add() / viewer.remove() 而不是 setInput。我一般统一用「重新查询 setInput refresh」的模式简单可靠数据量不大时性能没问题。6. 进阶给图书管理系统加一个可复用的搜索栏和导出功能搜索栏是图书管理系统里复用率最高的组件——图书列表要搜、读者列表要搜、借阅记录也要搜。与其每个界面写一遍不如抽一个 SearchBar 组件传入回调函数哪里需要哪里放。import org.eclipse.swt.SWT; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.*; import java.util.function.Consumer; public class SearchBar { public static Composite create(Composite parent, String placeholder, ConsumerString onSearch) { Composite bar new Composite(parent, SWT.NONE); bar.setLayout(new GridLayout(3, false)); bar.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); Text input new Text(bar, SWT.BORDER | SWT.SEARCH); input.setMessage(placeholder); // 占位提示文字 input.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); Button searchBtn new Button(bar, SWT.PUSH); searchBtn.setText(搜索); Button resetBtn new Button(bar, SWT.PUSH); resetBtn.setText(重置); // 点击搜索或回车都触发 searchBtn.addSelectionListener(new SelectionAdapter() { Override public void widgetSelected(SelectionEvent e) { onSearch.accept(input.getText().trim()); } }); input.addListener(SWT.DefaultSelection, e - onSearch.accept(input.getText().trim())); resetBtn.addSelectionListener(new SelectionAdapter() { Override public void widgetSelected(SelectionEvent e) { input.setText(); onSearch.accept(); // 空关键字查全部 } }); return bar; } }逻辑说明Consumer 是 Java 8 的函数式接口调用方传入「拿到关键字后干什么」。SWT.SEARCH 样式让输入框带一个搜索图标Windows 上有效。SWT.DefaultSelection 监听回车键用户输完直接回车就能搜不用去点按钮。重置按钮清空输入并触发一次空搜索相当于刷新列表。参数方面setMessage 是 SWT 3.3 之后支持的占位文字比单独放一个 Label 省空间。Consumer 回调让 SearchBar 不依赖具体的 DAO图书列表传kw - viewer.setInput(bookDao.searchByTitle(kw))读者列表传读者 DAO 的查询一个组件服务三个界面。导出功能是答辩加分项。图书列表导出成 CSV用 SWT 的 FileDialog 选保存路径然后写文件。FileDialog dialog new FileDialog(shell, SWT.SAVE); dialog.setFilterExtensions(new String[]{*.csv}); dialog.setFileName(图书列表.csv); String path dialog.open(); if (path ! null) { try (PrintWriter writer new PrintWriter(new FileWriter(path))) { writer.println(ISBN,书名,作者,库存,状态); // 表头 for (Book b : currentBooks) { writer.printf(%s,%s,%s,%d,%s%n, b.getIsbn(), b.getTitle(), b.getAuthor(), b.getStock(), b.getStock() 0 ? 可借 : 已借完); } } catch (IOException e) { MessageBox box new MessageBox(shell, SWT.ICON_ERROR); box.setMessage(导出失败 e.getMessage()); box.open(); } }注意CSV 里如果书名含逗号要加引号包裹否则列会错位。简单处理是\ title.replace(\, \\) \。导出前先判断 currentBooks 是否为空空列表导出个只有表头的文件没意义弹个提示更好。这套方案我从课程设计做到内部工具反复用了很多次。SWT 的坑主要集中在资源管理和线程模型上把这两块理顺剩下的就是 CRUD 的体力活。我自己的习惯是每写一个界面类先在纸上画出控件树标好哪些是动态创建的、哪些需要 dispose再动手敲代码比边写边改省时间。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询