☰
SWT图书管理系统:Java桌面端GUI开发与SQLite数据库实战解析
2026/10/20 14:30:45 网站建设 项目流程

简介:面向Java桌面开发学习者,这套基于SWT的图书管理系统完整项目提供了从界面到数据层的可运行示例,适合需要理解Java GUI与数据库如何协同工作的学生或初级开发者。压缩包内共259个文件,包含45个Java源文件、107个编译后的class文件、80个ico图标、7个jar依赖库、SQL数据库脚本与Eclipse工程文件,整体约10.07MB,导入后即可查看、编译和运行。编译后的class文件便于直接启动验证,java源码可供逐行学习,ico与jpg则是界面设计所需的图标和图片素材,SQL脚本可用于快速建库。项目覆盖SWT常用组件、GridLayout布局、Table数据展示、事件监听、JDBC数据库操作、DAO分层设计、内部类与资源管理等核心知识点,并包含图书管理、读者管理、借阅与删除等模块的界面代码和实现思路,帮助读者掌握桌面应用从界面绘制到数据持久化的完整流程。已有483人学习下载,项目结构清晰,适合课程设计、毕业设计或自学实践,也可作为二次开发的起点;整体目录组织明确,从入口MainFrame到各DAO类分工清晰,便于定位与修改。

1. SWT图书管理系统:一个值得下载的Java桌面端参考实现

很多刚学完 Java SE 的同学,第一次做图形化界面都会撞上同一个问题:Swing 写出来的界面又老又丑,JavaFX 的资料又多又杂,到底用哪个才能又快又体面地交差?如果你打开过 Eclipse,观察过它那套原生风格的窗口,你其实已经见过答案了——SWT。这篇笔记要拆的,就是一个用 SWT 做的图书管理系统,它把图书增删改查、借书还书、读者管理、逾期处理这些图书馆日常业务,全部放进了一套由原生控件组成的图形界面里。它不像 Spring 系项目那样需要一堆环境,一个 JDK、一个能跑 SWT 的 IDE 就能启动,界面响应快,观感接近原生桌面软件。如果你正准备做课程设计、毕业设计,或者只是想把 Java 语法真正落到一个看得见摸得着的窗口程序上,这份源码值得你下载下来跑一遍,再照着改。

它最让我意外的点是数据存储没有用 MySQL,而是走了 SQLite——不需要安装数据库服务,JDBC 驱动直接连本地文件,所以项目压缩包解压后导入 IDE 就能跑,几乎零配置。接下来我会从技术选型、目录结构、核心模块代码到踩坑记录,一层层把这个项目拆开。

2. 技术选型与整体架构:为什么是SWT,为什么不用Swing

2.1 SWT 与 Swing 的核心差异

先聊一个很多人纠结过的问题:同样是 Java 官方生态里的 GUI 工具包,为什么选 SWT 而不是 Swing?SWT 的全称是 Standard Widget Toolkit,它和 Swing 最大的区别在于——Swing 是纯 Java 绘制的模拟控件,而 SWT 直接调用操作系统原生的窗口控件。你在 Windows 上运行,看到的就是 Windows 风格的按钮和输入框;在 Linux 上运行,看到的就是 GTK 风格的控件。这意味着它不需要像 Swing 那样反复重绘,内存占用更低,启动速度也更直观。

当然 SWT 也有它的代价:依赖平台相关的 native 库,跨平台打包时需要针对不同操作系统准备不同的 jar 片段,而且官方文档的资料量远不如 Swing 丰富。但对于图书管理系统这种以表格、表单、按钮为主的业务型桌面应用来说,SWT 的 Table、Text、Button 控件配合 GridLayout 布局,写起来比 Swing 干净利落得多。

这个项目选择 SWT,我猜测还有另一层原因——它结构上很像 Eclipse 插件的写法,如果今后你想接触 Eclipse RCP 开发,这份代码可以当跳板。从实际体验来看,这套界面在缩放窗口时,表格列和输入框的拉伸行为比 Swing 的 BorderLayout 组合要顺手。

2.2 整体分层:界面、业务、数据访问三层

展开源码包之后,包结构是典型的三个层次,跟我平时写小项目的习惯基本一致:

com.library.model 实体类:Book、Reader、BorrowRecord com.library.dao 数据访问层:SQLite 的增删改查封装 com.library.ui 界面层:LoginShell、MainShell、BookShell 等 com.library.util 工具类:数据库连接、日期格式化、ID 生成 com.library.core 启动入口:Application.java

界面层只管创建控件和接收用户事件,拿到用户输入后调用 DAO 层,不直接写 SQL。DAO 层负责把 SQLite 表映射成模型对象,业务规则放在哪一层?这个项目里比较简单的事务逻辑(比如借书时检查库存、同一读者不能重复借同一本书)直接写在 DAO 方法内部,Service 层没有单独拆出来。如果你打算二次开发,我建议在 ui 和 dao 之间补一个 service 包,把借书、还书、逾期计费这些复合操作独立出去,否则 UI 监听器里会越写越肿。

2.3 数据库设计:SQLite 单文件,三张表搞定核心业务

数据库用的是 SQLite,连接方式是 JDBC 驱动 org.sqlite.JDBC,连接串类似 jdbc:sqlite:data/library.db。项目里包含一个 library.db 文件,路径相对于项目根目录。表结构设计并不复杂,但够用:

  • book 表:id、book_name、author、publisher、category、stock、borrowed_count
  • reader 表:id、reader_name、phone、max_borrow
  • borrow_record 表:id、book_id、reader_id、borrow_date、due_date、return_date、fine_amount

设计上有个细节值得注意:book 表的 stock 和 borrowed_count 是分开的,stock 表示总库存,borrowed_count 表示当前被借出的数量,可用数量 = stock - borrowed_count。还书时更新 borrowed_count,而不是直接改 stock,这样能保留“总共有多少本”这个信息。逾期费用是在还书时根据 due_date 和当前日期计算,写入 borrow_record 的 fine_amount 字段,而不是每天跑定时任务,省事。

3. 核心模块拆解:从登录窗口到借还书完整链路

3.1 启动入口与登录窗口:SWT事件模型的第一次接触

项目的主入口是 Application.java。它不是直接打开主窗口,而是先弹登录框,登录成功后再销毁登录 Shell,创建 MainShell。这个流程对应实际使用场景:图书管理员登录后进入管理界面,不同权限账号看到的功能菜单不一样。

public static void main(String[] args) { Display display = new Display(); LoginShell loginShell = new LoginShell(display); loginShell.open(); while (!loginShell.isDisposed()) { if (!display.readAndDispatch()) { display.sleep(); } } display.dispose(); }

这段代码是 SWT 的固定骨架:先创建 Display 对象代表与操作系统 GUI 线程的会话,再创建 Shell(窗口),然后进入事件循环——readAndDispatch() 不断从操作系统事件队列里取事件并分发,没有事件时 sleep 减少 CPU 占用。Shell 被 dispose 后循环退出,display 也随之释放。新手最容易漏掉的就是最后的 display.dispose(),在长运行的桌面程序里,不释放 Display 会导致资源泄漏。

登录窗口内部的核心逻辑在“登录”按钮的监听器里:读取用户名和密码输入框,调用 DAO 验证,成功则关掉当前 Shell 并打开 MainShell。这里有个实现细节,供参考:

btnLogin.addListener(SWT.Selection, e -> { String username = txtUsername.getText().trim(); String password = new String(txtPassword.getPassword()); if (username.isEmpty() || password.isEmpty()) { MessageBox box = new MessageBox(shell, SWT.ICON_WARNING); box.setMessage("用户名和密码不能为空"); box.open(); return; } User user = userDao.findByUsernameAndPassword(username, password); if (user == null) { MessageBox box = new MessageBox(shell, SWT.ICON_ERROR); box.setMessage("用户名或密码错误"); box.open(); return; } shell.dispose(); MainShell mainShell = new MainShell(display, user); mainShell.open(); });

注意这里密码框用的是 txtPassword.getPassword(),返回的是 char[] 而不是 String,这是避免字符串常量驻留在内存中留下密码痕迹的常见做法,转成 String 之后用完还能置空。

3.2 图书管理界面:Table 控件与增删改查的落地写法

主窗口打开后,左侧是一个 List 或 Tree 做的导航菜单,右侧是一个 Composite 区域,根据选中的菜单项切换不同的子界面。图书列表用的控件是 SWT 的 Table,配合 TableColumn 和 TableItem。

查询逻辑非常直白:加载所有图书,遍历结果集,往 Table 里逐行塞数据。核心代码大概是这个形态:

Table table = new Table(parent, SWT.FULL_SELECTION | SWT.BORDER); table.setHeaderVisible(true); table.setLinesVisible(true); String[] columns = {"编号", "书名", "作者", "出版社", "分类", "总库存", "已借出"}; for (int i = 0; i < columns.length; i++) { TableColumn col = new TableColumn(table, SWT.NONE); col.setText(columns[i]); col.setWidth(i == 1 ? 200 : 90); // 书名列宽一点 } List<Book> books = bookDao.findAll(); for (Book b : books) { TableItem item = new TableItem(table, SWT.NONE); item.setText(new String[]{ String.valueOf(b.getId()), b.getBookName(), b.getAuthor(), b.getPublisher(), b.getCategory(), String.valueOf(b.getStock()), String.valueOf(b.getBorrowedCount()) }); }

这段代码有两个值得注意的坑。第一个是 TableItem.setText 接收的是字符串数组,数组顺序必须和前面创建 TableColumn 的顺序一致,否则数据串列。第二个是表格没有设置 SWT.VIRTUAL 时,数据量一大(超过几千行)界面会卡顿——这个项目的数据量不大所以没问题,但二次开发如果导入上万条图书数据,就必须切换成虚拟表加懒加载模式。

新增和编辑图书共用同一个对话框:一本模型对象传到 Shell,校验书名不为空、库存为正整数,然后调用 bookDao.insert() 或 bookDao.update(),操作完成后刷新 Table。删除则更直接,选中一行,拿 id 调 bookDao.deleteById(),删除前弹 MessageBox 二次确认。

3.3 借书与还书:事务处理与并发控制

借书是整个项目里业务最重的一个环节。用户选中一本书、选中一个读者,点击借书时系统要做三件事:检查这本书当前可借数量是否大于 0,检查这个读者有没有未归还的借阅记录,然后插入一条 borrow_record 并给 book 表的 borrowed_count 加 1。

这三步如果分开执行,一旦中途出错,数据就会不一致。这个项目里用的是 SQLite 原生事务:

public boolean borrowBook(int bookId, int readerId) { Connection conn = null; try { conn = DBUtil.getConnection(); conn.setAutoCommit(false); Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery( "SELECT stock, borrowed_count FROM book WHERE id = " + bookId); if (!rs.next()) { return false; } int stock = rs.getInt("stock"); int borrowed = rs.getInt("borrowed_count"); if (stock - borrowed <= 0) { return false; // 库存不足 } stmt.executeUpdate("UPDATE book SET borrowed_count = borrowed_count + 1 WHERE id = " + bookId); stmt.executeUpdate("INSERT INTO borrow_record (book_id, reader_id, borrow_date, due_date) VALUES (" + bookId + ", " + readerId + ", date('now'), date('now', '+30 day'))"); conn.commit(); return true; } catch (SQLException e) { if (conn != null) { try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } throw new RuntimeException("借书失败", e); } finally { if (conn != null) { try { conn.setAutoCommit(true); } catch (SQLException ignored) {} try { conn.close(); } catch (SQLException ignored) {} } } }

setAutoCommit(false) 之后,所有语句必须等 commit 才真正落盘,任何一步抛异常就 rollback 全部撤销。这里用的是拼接 SQL,项目本身没问题,但如果你拿去二次开发,强烈建议换成 PreparedStatement,防止图书名称里带单引号或特殊字符直接干崩 SQL。

还书逻辑是对称的:根据 borrow_record 的 id 更新 return_date,计算逾期天数,然后把 fine_amount 写进去,同时把 book 表的 borrowed_count 减 1。注意还书更新和库存回写同样应该放进一个事务。

3.4 环境准备与启动流程:从零到跑通的步骤

跑这个项目只需要三样东西:JDK 8 或更高版本、Eclipse IDE(带 SWT 插件的标准版即可)、以及源码包里的 jar 依赖。SWT 的 jar 可以直接在 Eclipse 的 Target Platform 里找到,也可以从 Maven 仓库拉 org.eclipse.swt 对应平台的版本;sqlite-jdbc 的 jar 同样可以从 Maven 仓库下载。

导入项目的步骤如下:打开 Eclipse,选择 File > Import > Existing Projects into Workspace,选中项目根目录,勾选 Copy projects into workspace,Finish。然后右键项目 > Properties > Java Build Path > Libraries,检查 SWT 和 sqlite-jdbc 是否在列表中,不在就通过 Add External JARs 手动添加。最后选中 Application.java,右键 Run As > Java Application。

一个容易绊倒新人的点:如果你在 macOS 上运行,必须给 JVM 加启动参数 -XstartOnFirstThread,不然 SWT 在初始化 Display 时会直接抛异常崩溃。Eclipse 里的配置位置是 Run Configurations > Arguments > VM Arguments,填上这一行参数再运行。Windows 和 Linux 不需要这个参数。

登录账号密码在项目里是写死的管理员账号,用户名 admin,密码 admin,进去之后是管理员权限,可以看到全部菜单。如果你想换自己的账号,直接改数据库 user 表的记录,或者看 userDao 里的初始化 SQL。

4. 避坑与常见问题排查:SWT开发里容易翻车的5个地方

SWT 开发跟 Web 开发完全两个脾气,很多在 Java Web 里想当然的写法,放到 SWT 里就是运行时报错。下面这几条是我拆这个项目时实际踩过或者见过的坑,每条都很具体,照着排查能省不少时间。

4.1 Invalid thread access:后台线程碰了UI控件

现象:代码在后台线程里调用了 table.setItem() 或 label.setText(),运行到一半抛出 org.eclipse.swt.SWTException: Invalid thread access。

原因:SWT 的控件不是线程安全的,所有对控件的操作必须在创建它的那个线程(通常是 main 线程,也叫 UI 线程)里执行。后台线程直接操作控件,SWT 会拒绝执行并抛异常。

解决:用 Display.asyncExec() 把 UI 操作切回 UI 线程。常见做法是封装一个工具方法,比如 display.asyncExec(() -> table.setItem(...))。需要强调的是,连接数据库这种耗时操作可以放在后台线程,但拿到结果后更新 Table 的那段代码必须包进 asyncExec。

4.2 表格里的数据串列

现象:Table 显示的时候,书名跑到了编号列,作者跑到了书名列,整体数据错位一位。

原因:TableItem.setText(String[]) 的数组元素顺序和 TableColumn 的创建顺序不一致。你前面建列的顺序是编号、书名、作者,后面 setText 的顺序是书名、编号、作者,就会出现这种现象。

解决:把两个顺序对齐,或者更稳妥的做法是定义一个列索引常量数组,创建列和填充数据都引用同一组常量,避免手写字符串数组顺序出错。这个项目的代码里为了省事直接写字符串数组,二次开发时建议改成常量。

4.3 打包成jar后找不到SQLite驱动

现象:在 Eclipse 里运行一切正常,但导出 Runnable JAR 后运行,报 ClassNotFoundException: org.sqlite.JDBC 或 SQLite 的 native 库加载失败。

原因:sqlite-jdbc 是个 fat jar,里面包含了针对不同操作系统的 native 库文件。Eclipse 导出 Runnable JAR 时如果选择“Extract required libraries into generated JAR”,有时候 native 库的加载路径会被破坏。

解决:导出时选择 Package required libraries into generated JAR,或者用 Maven 的 shade 插件做 fat jar。如果还不行,把 sqlite-jdbc 的 jar 单独放在项目外的 lib 目录,运行时通过 -cp 参数指定,而不是全部塞进一个 jar。

4.4 高DPI屏幕下界面发虚或字体过小

现象:在 Windows 上开了 125% 或 150% 缩放的屏幕上运行,SWT 界面整体偏模糊,文字小到看不清。

原因:SWT 在不同版本的 Windows 上对高 DPI 的支持程度不一致。老版本的 SWT 不会自动适配系统缩放比例,界面按 100% 渲染后被系统强制拉伸,自然就糊了。

解决:给 JVM 加启动参数 -Dswt.autoScale=exact,或者 -Dswt.autoScale=up。前者按精确比例缩放,后者只放大不缩小。实测在 150% 缩放下,加了这个参数后界面清晰度明显改善。

4.5 Linux 下中文显示成方块

现象:在 Ubuntu 这类 Linux 发行版上运行,界面中文全部变成方框。

原因:SWT 渲染中文依赖系统的中文字体。Linux 最小化安装往往没有安装中文字体包,字体回退失败就显示方框。

解决:这不是代码问题,是系统环境问题。安装 fonts-wqy-zenhei 或 fonts-noto-cjk 后重新运行即可。项目里如果想更稳,可以在启动时通过 SWT 的 FontData 指定一个字体候选列表,但本质上还是系统安装字体最省事。

5. 二次开发思路:把这个项目改成你自己的课程设计或工具

拿到这份源码之后,如果你不想只是跑通了事,我建议按下面这条路改造一轮,每一步都能在课上讲出东西来。先做权限分级:现在登录后所有菜单都可见,你可以给 user 表加一个 role 字段,管理员能看到读者管理和图书管理,普通操作员只能看到借书还书和图书查询。改动量不大,就是 MainShell 里创建菜单时根据 user.getRole() 做条件判断。

然后是数据访问层的升级:把 DAO 里的 Statement 全部换成 PreparedStatement,这是安全性的刚需改造,也是面试和答辩时最高频的一个提问点。换完 PreparedStatement 之后,再把 borrowBook 里那段拼接 SQL 的事务改成 try-with-resources 写法,代码会简洁很多。紧接着可以加一个 Service 层,把借书、还书、逾期计算从 UI 监听器里挪出来,让监听器只做一件事:调 service 方法,刷新表格。界面层会立刻瘦一圈。

如果你想往深了做,可以考虑把 SQLite 换成 MySQL。数据库连接串、驱动类名、方言差异是主要的改动点,DAO 层的方法参数不变,界面层几乎不用动。需要注意的是 SQLite 的 date('now') 在 MySQL 里对应 CURDATE() 或 NOW(),字段类型方面 SQLite 的灵活类型到 MySQL 会严格一些,建表语句得重写。再做一层的话,可以在借书时增加一个“读者当前借阅数不能超过 max_borrow”的校验,这个字段 reader 表里已经有了,只是 DAO 层还没用上。

最后建议你加一个好用的功能:图书搜索。在图书列表上方加一个 Text 输入框和一个“搜索”按钮,按书名或作者模糊查询,SQL 用 LIKE '%keyword%',查完刷新 Table。这个功能浏览量最高、实现成本最低,也是演示时最出效果的一个点。

每次改完一个模块,我自己的习惯是强制走一遍完整流程:登录、加一本书、改一本书、借书、还书、查逾期记录、删除读者,全链路跑通再提交。这个项目其实难度不大,但它把 Java 语法、JDBC、GUI 事件模型和基本的数据库事务缝合在了一起,这一个闭环跑通,你对付大多数桌面端课程设计就都有了底气。希望这份拆解对你跑通和改造这个项目有帮助。

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

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

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

立即咨询