当前位置: 首页 > news >正文

项目实战:从零构建Java+MySQL图书借阅系统(Swing版)

1. 项目概述与需求分析

图书借阅系统是图书馆日常运营的核心工具,也是Java初学者练手的经典项目。这个Swing版系统主要面向高校课程设计或毕业设计场景,需要实现以下核心功能:

  • 用户角色分离:管理员负责图书/用户管理,读者完成借阅/归还操作
  • 基础CRUD:图书信息增删改查、用户信息维护
  • 借阅逻辑:包含借书、还书、续借、逾期计算等完整流程
  • 数据统计:图书库存、借阅排行榜等可视化报表

我去年指导学生做这个项目时,发现新手常卡在三个地方:数据库设计不合理导致后期逻辑混乱、Swing事件绑定不熟练、借还书的事务处理出错。下面我们就从最关键的数据库设计开始,一步步避开这些坑。

2. 数据库设计与实现

2.1 表结构设计

用Navicat新建名为library_db的数据库,字符集选utf8mb4。核心四张表的设计要点:

-- 管理员表 CREATE TABLE `admin` ( `id` INT PRIMARY KEY AUTO_INCREMENT, `username` VARCHAR(20) NOT NULL UNIQUE, `password` CHAR(32) NOT NULL -- 存储MD5加密结果 ); -- 图书表(注意索引设计) CREATE TABLE `book` ( `id` INT PRIMARY KEY AUTO_INCREMENT, `isbn` VARCHAR(17) NOT NULL UNIQUE, `name` VARCHAR(100) NOT NULL, `author` VARCHAR(50) NOT NULL, `press` VARCHAR(50) COMMENT '出版社', `stock` INT DEFAULT 1 CHECK(stock >= 0) -- 库存不能为负 ); CREATE INDEX idx_book_name ON book(name); -- 读者表 CREATE TABLE `reader` ( `id` INT PRIMARY KEY AUTO_INCREMENT, `card_id` VARCHAR(20) NOT NULL UNIQUE COMMENT '借书证号', `name` VARCHAR(20) NOT NULL, `password` CHAR(32) NOT NULL ); -- 借阅记录表(核心业务表) CREATE TABLE `borrow` ( `id` INT PRIMARY KEY AUTO_INCREMENT, `book_id` INT NOT NULL, `reader_id` INT NOT NULL, `borrow_date` DATETIME DEFAULT CURRENT_TIMESTAMP, `return_date` DATETIME COMMENT '应还日期', `actual_return` DATETIME COMMENT '实际归还', FOREIGN KEY (book_id) REFERENCES book(id), FOREIGN KEY (reader_id) REFERENCES reader(id) );

避坑指南

  1. 密码字段一定要用CHAR定长存储加密结果
  2. ISBN字段建议遵循978-7-04-048911-2这种带分隔符格式
  3. 借阅表的日期字段要区分"应还"和"实还"

2.2 初始化测试数据

插入示例数据时注意业务规则:

-- 管理员账号 INSERT INTO admin VALUES(null, 'admin', MD5('123456')); -- 图书数据(真实ISBN) INSERT INTO book VALUES (null, '978-7-121-35412-6', 'Java核心技术', 'Cay S. Horstmann', '电子工业出版社', 5), (null, '978-7-302-48527-9', 'MySQL必知必会', 'Ben Forta', '清华大学出版社', 3); -- 读者账号 INSERT INTO reader VALUES (null, '2023001', '张三', MD5('111111')), (null, '2023002', '李四', MD5('222222'));

3. Java项目搭建

3.1 Maven项目初始化

在IDEA中新建Maven项目,pom.xml关键依赖:

<dependencies> <!-- MySQL驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.28</version> </dependency> <!-- 日期处理 --> <dependency> <groupId>joda-time</groupId> <artifactId>joda-time</artifactId> <version>2.10.10</version> </dependency> <!-- 单元测试 --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.2</version> <scope>test</scope> </dependency> </dependencies>

3.2 数据库工具类

封装JDBC操作时要注意连接池和异常处理:

public class DBUtil { private static final String URL = "jdbc:mysql://localhost:3306/library_db?useSSL=false&serverTimezone=Asia/Shanghai"; private static final String USER = "root"; private static final String PASSWORD = "yourpassword"; // 获取连接(实际项目建议用HikariCP) public static Connection getConnection() { try { return DriverManager.getConnection(URL, USER, PASSWORD); } catch (SQLException e) { throw new RuntimeException("数据库连接失败", e); } } // 关闭资源 public static void close(Connection conn, Statement stmt, ResultSet rs) { try { if(rs != null) rs.close(); if(stmt != null) stmt.close(); if(conn != null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }

4. 登录模块开发

4.1 登录界面设计

使用Swing构建登录窗口时,建议用JPanel+GridBagLayout实现响应式布局:

public class LoginFrame extends JFrame { private JTextField tfUsername; private JPasswordField pfPassword; public LoginFrame() { setTitle("图书管理系统登录"); setSize(400, 300); setDefaultCloseOperation(EXIT_ON_CLOSE); setLocationRelativeTo(null); JPanel panel = new JPanel(new GridBagLayout()); GridBagConstraints gbc = new GridBagConstraints(); gbc.insets = new Insets(5, 5, 5, 5); // 用户名标签 gbc.gridx = 0; gbc.gridy = 0; panel.add(new JLabel("用户名:"), gbc); // 用户名输入框 gbc.gridx = 1; gbc.fill = GridBagConstraints.HORIZONTAL; tfUsername = new JTextField(15); panel.add(tfUsername, gbc); // 密码标签(gridy+1) gbc.gridx = 0; gbc.gridy = 1; panel.add(new JLabel("密码:"), gbc); // 密码输入框 gbc.gridx = 1; pfPassword = new JPasswordField(15); panel.add(pfPassword, gbc); // 登录按钮(事件绑定在4.2节) gbc.gridx = 0; gbc.gridy = 2; gbc.gridwidth = 2; JButton btnLogin = new JButton("登录"); panel.add(btnLogin, gbc); add(panel); } }

4.2 登录验证逻辑

密码验证要使用MD5加密比对,防止SQL注入:

// 在LoginFrame类中添加事件监听 btnLogin.addActionListener(e -> { String username = tfUsername.getText().trim(); String password = new String(pfPassword.getPassword()); if(username.isEmpty() || password.isEmpty()) { JOptionPane.showMessageDialog(this, "用户名密码不能为空"); return; } // 密码MD5加密 String encryptedPwd = DigestUtils.md5Hex(password); Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { conn = DBUtil.getConnection(); String sql = "SELECT * FROM admin WHERE username=? AND password=?"; ps = conn.prepareStatement(sql); ps.setString(1, username); ps.setString(2, encryptedPwd); rs = ps.executeQuery(); if(rs.next()) { new MainFrame().setVisible(true); // 跳转主界面 dispose(); // 关闭登录窗口 } else { JOptionPane.showMessageDialog(this, "用户名或密码错误"); } } catch (SQLException ex) { ex.printStackTrace(); JOptionPane.showMessageDialog(this, "数据库查询失败"); } finally { DBUtil.close(conn, ps, rs); } });

5. 图书管理模块

5.1 图书查询功能

实现多条件模糊查询时,注意SQL拼接的安全性问题:

public List<Book> searchBooks(String name, String author) { List<Book> books = new ArrayList<>(); StringBuilder sql = new StringBuilder("SELECT * FROM book WHERE 1=1 "); List<Object> params = new ArrayList<>(); if(!name.isEmpty()) { sql.append("AND name LIKE ? "); params.add("%" + name + "%"); } if(!author.isEmpty()) { sql.append("AND author LIKE ? "); params.add("%" + author + "%"); } Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { conn = DBUtil.getConnection(); ps = conn.prepareStatement(sql.toString()); for(int i=0; i<params.size(); i++) { ps.setObject(i+1, params.get(i)); } rs = ps.executeQuery(); while(rs.next()) { Book book = new Book(); book.setId(rs.getInt("id")); book.setIsbn(rs.getString("isbn")); book.setName(rs.getString("name")); book.setAuthor(rs.getString("author")); book.setPress(rs.getString("press")); book.setStock(rs.getInt("stock")); books.add(book); } } catch (SQLException e) { e.printStackTrace(); } finally { DBUtil.close(conn, ps, rs); } return books; }

5.2 借书业务实现

借书操作需要处理事务,确保库存和借阅记录同步更新:

public boolean borrowBook(int bookId, int readerId) { Connection conn = null; try { conn = DBUtil.getConnection(); conn.setAutoCommit(false); // 开启事务 // 1. 检查库存 PreparedStatement ps1 = conn.prepareStatement( "SELECT stock FROM book WHERE id=? FOR UPDATE"); ps1.setInt(1, bookId); ResultSet rs = ps1.executeQuery(); if(!rs.next() || rs.getInt("stock") <= 0) { return false; } // 2. 减少库存 PreparedStatement ps2 = conn.prepareStatement( "UPDATE book SET stock=stock-1 WHERE id=?"); ps2.setInt(1, bookId); ps2.executeUpdate(); // 3. 添加借阅记录(应还日期=当前时间+30天) PreparedStatement ps3 = conn.prepareStatement( "INSERT INTO borrow(book_id, reader_id, return_date) " + "VALUES(?, ?, DATE_ADD(NOW(), INTERVAL 30 DAY))"); ps3.setInt(1, bookId); ps3.setInt(2, readerId); ps3.executeUpdate(); conn.commit(); return true; } catch (SQLException e) { if(conn != null) { try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } e.printStackTrace(); return false; } finally { if(conn != null) { try { conn.setAutoCommit(true); } catch (SQLException e) { e.printStackTrace(); } } DBUtil.close(conn, null, null); } }

6. 报表统计功能

6.1 使用JFreeChart生成图表

在pom.xml添加依赖后,实现借阅排行榜:

<dependency> <groupId>org.jfree</groupId> <artifactId>jfreechart</artifactId> <version>1.5.3</version> </dependency>

生成饼图的代码示例:

public JPanel createBookRankChart() { // 获取借阅量前5的图书 Map<String, Integer> data = new LinkedHashMap<>(); String sql = "SELECT b.name, COUNT(*) as cnt FROM borrow br " + "JOIN book b ON br.book_id=b.id " + "GROUP BY b.name ORDER BY cnt DESC LIMIT 5"; try (Connection conn = DBUtil.getConnection(); Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(sql)) { while(rs.next()) { data.put(rs.getString("name"), rs.getInt("cnt")); } } catch (SQLException e) { e.printStackTrace(); } // 创建数据集 DefaultPieDataset dataset = new DefaultPieDataset(); data.forEach(dataset::setValue); // 创建图表 JFreeChart chart = ChartFactory.createPieChart( "图书借阅排行榜", dataset, true, true, false); // 返回图表面板 return new ChartPanel(chart); }

7. 系统打包与部署

7.1 使用Maven打包可执行JAR

在pom.xml中添加maven-assembly-plugin:

<build> <plugins> <plugin> <artifactId>maven-assembly-plugin</artifactId> <version>3.3.0</version> <configuration> <archive> <manifest> <mainClass>com.yourpackage.MainClass</mainClass> </manifest> </archive> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> </configuration> <executions> <execution> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions> </plugin> </plugins> </build>

运行mvn package后,在target目录会生成包含所有依赖的jar文件,直接通过java -jar命令即可运行。

7.2 常见问题解决

  1. 中文乱码问题

    • MySQL连接字符串添加useUnicode=true&characterEncoding=UTF-8
    • Swing界面设置默认字体:
      UIManager.put("Button.font", new Font("微软雅黑", Font.PLAIN, 12)); UIManager.put("Label.font", new Font("微软雅黑", Font.PLAIN, 12));
  2. 界面卡顿优化

    • 耗时操作放在SwingWorker中执行
    • 列表数据超过1000条时启用分页查询
  3. 数据库连接泄漏

    • 使用try-with-resources确保资源关闭
    • 定期检查SHOW PROCESSLIST
http://www.cnnetsun.cn/news/3359007.html

相关文章:

  • 智能代理技能(Agent Skills)开发指南与技术解析
  • 企业级AI Agent落地:从模型能力到工程体系的跨越
  • 单片机DAY_2
  • L9958与TM4C1294NCZAD电机控制方案解析
  • Chapter 2 The Well: Navigating Parasitics and Performance in CMOS Fabrication
  • Lua C接口封装设计:构建安全高效的C/C++与Lua双向通信桥梁
  • RoPE旋转位置编码原理与工程实践:从数学本质到CUDA优化
  • 本地AI音乐生成实战:为游戏开发打造史诗级战斗配乐
  • Mythos:AI原生安全时代的数字安全分析师
  • 通义灵码(阿里云)配置和streamlit实现小交互聊天界面
  • Unity插件缺失报错全解析:从编译错误到运行时依赖的完整解决方案
  • 压电蜂鸣器驱动方案设计与PIC18LF47K40实现
  • 【Java实习面试算法冲刺】回溯
  • 鸿蒙ArkTS实战:实现任务数据的保存与启动恢复
  • 虚幻引擎资源热更新实战:HotPatcher避坑指南与解决方案
  • ComfyUI中Z-Image-Turbo+ControlNet+Krea2高清修复方案详解
  • Anthropic TAI #200:Mythos能力驱动的价值对齐与分阶段开放实践
  • SHP文件纯前端处理:从上传到下载的完整实战指南
  • AI应用做出来怎么收费?很多人按次或月收费,亏了?这个技能帮你10分钟生成按词元计费方案
  • 残差扩散桥模型RDBM:一种面向通用图像恢复的统一扩散框架详解 (去雨、去雾、去雪等)
  • UE5 Lumen硬件光追与Nanite协同渲染的五大核心问题与实战解决方案
  • 【JAVA毕设源码分享】基于springboot宾馆客房管理系统的设计与实现(程序+文档+代码讲解+一条龙定制)
  • 低代码平台横评:Dify、RAGFlow、MaxKB、FastGPT 谁更适合你
  • AI助手在企业政策执行中的偏科现象与优化方案
  • SIEMENS 7396257 D514 印刷电路板
  • 三线模型:AI可解释性的创新框架与实践
  • SL6Pro频谱仪与SDR6软件无线电联动实战:从频谱分析到信号解调
  • Linux系统下MySQL客户端安装与多发行版适配指南
  • 从贝叶斯定理到垃圾邮件过滤:朴素贝叶斯分类器的实战解析
  • 在浙江收集的出轨证据怎样才能在法庭有效?对比完5种取证方式,终于有答案了。