杰瑞科技汇

Java+SQL如何高效实现图书管理系统?

  • 后端: Java (核心逻辑)
  • 数据库: MySQL (数据存储)
  • 连接技术: JDBC (Java连接数据库的标准API)
  • 前端: 简单的Java Swing (用于桌面GUI界面,易于理解)

第一步:数据库设计

我们需要设计数据库的结构,一个典型的图书管理系统至少需要两个核心表:books (图书) 和 users (用户)。

创建数据库

CREATE DATABASE IF NOT EXISTS library_db;
USE library_db;

创建图书表 (books)

这张表存储所有图书的信息。

CREATE TABLE books (
    book_id INT PRIMARY KEY AUTO_INCREMENT,VARCHAR(255) NOT NULL,
    author VARCHAR(100) NOT NULL,
    isbn VARCHAR(20) UNIQUE NOT NULL,
    total_copies INT NOT NULL DEFAULT 1,
    available_copies INT NOT NULL DEFAULT 1
);
  • book_id: 图书ID,主键,自增。: 书名。
  • author: 作者。
  • isbn: 国际标准书号,唯一。
  • total_copies: 总副本数。
  • available_copies: 可借阅副本数,当available_copies为0时,表示图书已被借完。

创建用户表 (users)

这张表存储用户信息。

CREATE TABLE users (
    user_id INT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(50) UNIQUE NOT NULL,
    password VARCHAR(50) NOT NULL,
    full_name VARCHAR(100) NOT NULL
);
  • user_id: 用户ID,主键,自增。
  • username: 用户名,唯一。
  • password: 密码 (实际项目中应加密存储,这里为简化)。
  • full_name: 真实姓名。

创建借阅记录表 (borrow_records)

这张表记录谁借了哪本书,以及借阅和归还的时间。

CREATE TABLE borrow_records (
    record_id INT PRIMARY KEY AUTO_INCREMENT,
    user_id INT NOT NULL,
    book_id INT NOT NULL,
    borrow_date DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    return_date DATETIME,
    FOREIGN KEY (user_id) REFERENCES users(user_id),
    FOREIGN KEY (book_id) REFERENCES books(book_id)
);
  • record_id: 记录ID,主键。
  • user_id: 借阅者ID,外键关联users表。
  • book_id: 被借图书ID,外键关联books表。
  • borrow_date: 借阅时间,默认为当前时间。
  • return_date: 归还时间,如果为NULL,表示尚未归还。

第二步:Java项目结构与后端逻辑

我们将使用Maven来管理项目依赖,这样处理JDBC驱动会非常方便。

Maven项目结构

library-management-system/
├── pom.xml
└── src/
    └── main/
        ├── java/
        │   └── com/
        │       └── example/
        │           ├── model/
        │           │   ├── Book.java
        │           │   ├── User.java
        │           │   └── BorrowRecord.java
        │           ├── dao/
        │           │   ├── BookDAO.java
        │           │   ├── UserDAO.java
        │           │   └── BorrowDAO.java
        │           ├── util/
        │           │   └── DatabaseUtil.java
        │           └── Main.java
        └── resources/

pom.xml 配置文件

添加MySQL JDBC驱动的依赖。

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>library-management-system</artifactId>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>
    <dependencies>
        <!-- MySQL Connector/J -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.33</version> <!-- 使用你安装的MySQL版本对应的驱动 -->
        </dependency>
    </dependencies>
</project>

数据库连接工具类 (DatabaseUtil.java)

这个类负责获取和管理数据库连接。

package com.example.util;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class DatabaseUtil {
    private static final String URL = "jdbc:mysql://localhost:3306/library_db?useSSL=false&serverTimezone=UTC";
    private static final String USER = "root"; // 你的数据库用户名
    private static final String PASSWORD = "your_password"; // 你的数据库密码
    public static Connection getConnection() {
        try {
            return DriverManager.getConnection(URL, USER, PASSWORD);
        } catch (SQLException e) {
            e.printStackTrace();
            throw new RuntimeException("Failed to connect to the database.", e);
        }
    }
}

Model (实体类)

这些类对应数据库表的结构。

Book.java

package com.example.model;
public class Book {
    private int id;
    private String title;
    private String author;
    private String isbn;
    private int totalCopies;
    private int availableCopies;
    // 构造方法、Getters和Setters
    public Book() {}
    public Book(String title, String author, String isbn, int totalCopies) {
        this.title = title;
        this.author = author;
        this.isbn = isbn;
        this.totalCopies = totalCopies;
        this.availableCopies = totalCopies;
    }
    // ... 省略所有getter和setter
    @Override
    public String toString() {
        return "Book{" +
                "id=" + id +
                ", title='" + title + '\'' +
                ", author='" + author + '\'' +
                ", isbn='" + isbn + '\'' +
                ", totalCopies=" + totalCopies +
                ", availableCopies=" + availableCopies +
                '}';
    }
}

User.java

package com.example.model;
public class User {
    private int id;
    private String username;
    private String password;
    private String fullName;
    // ... 构造方法、Getters和Setters
}

DAO (Data Access Object) 层

DAO层负责所有与数据库交互的逻辑。

BookDAO.java

package com.example.dao;
import com.example.model.Book;
import com.example.util.DatabaseUtil;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
public class BookDAO {
    // 添加新书
    public void addBook(Book book) throws SQLException {
        String sql = "INSERT INTO books (title, author, isbn, total_copies, available_copies) VALUES (?, ?, ?, ?, ?)";
        try (Connection conn = DatabaseUtil.getConnection();
             PreparedStatement pstmt = conn.prepareStatement(sql)) {
            pstmt.setString(1, book.getTitle());
            pstmt.setString(2, book.getAuthor());
            pstmt.setString(3, book.getIsbn());
            pstmt.setInt(4, book.getTotalCopies());
            pstmt.setInt(5, book.getAvailableCopies());
            pstmt.executeUpdate();
        }
    }
    // 根据ID查找图书
    public Book getBookById(int id) throws SQLException {
        String sql = "SELECT * FROM books WHERE book_id = ?";
        try (Connection conn = DatabaseUtil.getConnection();
             PreparedStatement pstmt = conn.prepareStatement(sql)) {
            pstmt.setInt(1, id);
            ResultSet rs = pstmt.executeQuery();
            if (rs.next()) {
                Book book = new Book();
                book.setId(rs.getInt("book_id"));
                book.setTitle(rs.getString("title"));
                book.setAuthor(rs.getString("author"));
                book.setIsbn(rs.getString("isbn"));
                book.setTotalCopies(rs.getInt("total_copies"));
                book.setAvailableCopies(rs.getInt("available_copies"));
                return book;
            }
        }
        return null;
    }
    // 搜索图书 (按书名或作者)
    public List<Book> searchBooks(String keyword) throws SQLException {
        List<Book> books = new ArrayList<>();
        String sql = "SELECT * FROM books WHERE title LIKE ? OR author LIKE ?";
        try (Connection conn = DatabaseUtil.getConnection();
             PreparedStatement pstmt = conn.prepareStatement(sql)) {
            String searchPattern = "%" + keyword + "%";
            pstmt.setString(1, searchPattern);
            pstmt.setString(2, searchPattern);
            ResultSet rs = pstmt.executeQuery();
            while (rs.next()) {
                Book book = new Book();
                book.setId(rs.getInt("book_id"));
                book.setTitle(rs.getString("title"));
                book.setAuthor(rs.getString("author"));
                book.setIsbn(rs.getString("isbn"));
                book.setTotalCopies(rs.getInt("total_copies"));
                book.setAvailableCopies(rs.getInt("available_copies"));
                books.add(book);
            }
        }
        return books;
    }
    // 获取所有图书
    public List<Book> getAllBooks() throws SQLException {
        List<Book> books = new ArrayList<>();
        String sql = "SELECT * FROM books";
        try (Connection conn = DatabaseUtil.getConnection();
             Statement stmt = conn.createStatement();
             ResultSet rs = stmt.executeQuery(sql)) {
            while (rs.next()) {
                Book book = new Book();
                book.setId(rs.getInt("book_id"));
                book.setTitle(rs.getString("title"));
                book.setAuthor(rs.getString("author"));
                book.setIsbn(rs.getString("isbn"));
                book.setTotalCopies(rs.getInt("total_copies"));
                book.setAvailableCopies(rs.getInt("available_copies"));
                books.add(book);
            }
        }
        return books;
    }
}

BorrowDAO.java

package com.example.dao;
import com.example.util.DatabaseUtil;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class BorrowDAO {
    // 借书
    public boolean borrowBook(int userId, int bookId) throws SQLException {
        Connection conn = null;
        try {
            conn = DatabaseUtil.getConnection();
            conn.setAutoCommit(false); // 开启事务
            // 1. 检查图书是否可借
            String checkSql = "SELECT available_copies FROM books WHERE book_id = ? FOR UPDATE"; // FOR UPDATE 锁定行
            PreparedStatement checkPstmt = conn.prepareStatement(checkSql);
            checkPstmt.setInt(1, bookId);
            ResultSet rs = checkPstmt.executeQuery();
            if (!rs.next() || rs.getInt("available_copies") <= 0) {
                conn.rollback();
                return false; // 图书不可借
            }
            // 2. 更新图书可借数量
            String updateBookSql = "UPDATE books SET available_copies = available_copies - 1 WHERE book_id = ?";
            PreparedStatement updateBookPstmt = conn.prepareStatement(updateBookSql);
            updateBookPstmt.setInt(1, bookId);
            updateBookPstmt.executeUpdate();
            // 3. 添加借阅记录
            String borrowSql = "INSERT INTO borrow_records (user_id, book_id) VALUES (?, ?)";
            PreparedStatement borrowPstmt = conn.prepareStatement(borrowSql);
            borrowPstmt.setInt(1, userId);
            borrowPstmt.setInt(2, bookId);
            borrowPstmt.executeUpdate();
            conn.commit(); // 提交事务
            return true;
        } catch (SQLException e) {
            if (conn != null) conn.rollback(); // 出错则回滚
            e.printStackTrace();
            throw e;
        } finally {
            if (conn != null) {
                conn.setAutoCommit(true); // 恢复自动提交模式
                conn.close();
            }
        }
    }
    // 还书
    public boolean returnBook(int recordId) throws SQLException {
        Connection conn = null;
        try {
            conn = DatabaseUtil.getConnection();
            conn.setAutoCommit(false);
            // 1. 获取被借图书的ID
            String getBookIdSql = "SELECT book_id FROM borrow_records WHERE record_id = ?";
            PreparedStatement getBookIdPstmt = conn.prepareStatement(getBookIdSql);
            getBookIdPstmt.setInt(1, recordId);
            ResultSet rs = getBookIdPstmt.executeQuery();
            if (!rs.next()) {
                conn.rollback();
                return false; // 借阅记录不存在
            }
            int bookId = rs.getInt("book_id");
            // 2. 更新图书可借数量
            String updateBookSql = "UPDATE books SET available_copies = available_copies + 1 WHERE book_id = ?";
            PreparedStatement updateBookPstmt = conn.prepareStatement(updateBookSql);
            updateBookPstmt.setInt(1, bookId);
            updateBookPstmt.executeUpdate();
            // 3. 更新借阅记录的归还日期
            String updateRecordSql = "UPDATE borrow_records SET return_date = CURRENT_TIMESTAMP WHERE record_id = ?";
            PreparedStatement updateRecordPstmt = conn.prepareStatement(updateRecordSql);
            updateRecordPstmt.setInt(1, recordId);
            updateRecordPstmt.executeUpdate();
            conn.commit();
            return true;
        } catch (SQLException e) {
            if (conn != null) conn.rollback();
            e.printStackTrace();
            throw e;
        } finally {
            if (conn != null) {
                conn.setAutoCommit(true);
                conn.close();
            }
        }
    }
}
  • 事务处理: 在借书和还书操作中,我们使用了数据库事务,这确保了多个SQL操作要么全部成功,要么全部失败,保证了数据的一致性,借书时,必须同时减少available_copies和增加borrow_records记录,不能只做其中一个。
  • 悲观锁: SELECT ... FOR UPDATE 会在查询时锁定选中的行,直到事务结束,这可以防止在检查图书可借性和实际借出之间,有其他用户同时借走这本书。

第三步:前端界面 (Java Swing)

我们将创建一个简单的桌面应用程序来与我们的后端交互。

Main.java (主窗口)

package com.example;
import com.example.dao.BookDAO;
import com.example.dao.BorrowDAO;
import com.example.model.Book;
import javax.swing.*;
import javax.swing.table.DefaultTableModel;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.sql.SQLException;
import java.util.List;
public class Main extends JFrame {
    private JTextField titleField, authorField, isbnField, copiesField;
    private JTable bookTable;
    private DefaultTableModel tableModel;
    private BookDAO bookDAO;
    private BorrowDAO borrowDAO;
    public Main() {
        bookDAO = new BookDAO();
        borrowDAO = new BorrowDAO();
        initUI();
    }
    private void initUI() {
        setTitle("图书管理系统");
        setSize(800, 600);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
        // 使用卡片布局来切换不同面板
        CardLayout cardLayout = new CardLayout();
        JPanel mainPanel = new JPanel(cardLayout);
        // 1. 图书管理面板
        JPanel bookPanel = createBookPanel();
        mainPanel.add(bookPanel, "BOOKS");
        // 2. 借阅管理面板 (可以扩展)
        // JPanel borrowPanel = createBorrowPanel();
        // mainPanel.add(borrowPanel, "BORROW");
        // 添加一个简单的菜单栏来切换视图
        JMenuBar menuBar = new JMenuBar();
        JMenu menu = new JMenu("菜单");
        JMenuItem booksMenuItem = new JMenuItem("图书管理");
        // JMenuItem borrowMenuItem = new JMenuItem("借阅管理");
        menu.add(booksMenuItem);
        // menu.add(borrowMenuItem);
        menuBar.add(menu);
        setJMenuBar(menuBar);
        booksMenuItem.addActionListener(e -> cardLayout.show(mainPanel, "BOOKS"));
        // borrowMenuItem.addActionListener(e -> cardLayout.show(mainPanel, "BORROW"));
        add(mainPanel);
    }
    private JPanel createBookPanel() {
        JPanel panel = new JPanel(new BorderLayout());
        // --- 顶部: 添加图书的表单 ---
        JPanel formPanel = new JPanel(new GridLayout(5, 2, 5, 5));
        formPanel.setBorder(BorderFactory.createTitledBorder("添加新书"));
        formPanel.add(new JLabel("书名:"));
        titleField = new JTextField();
        formPanel.add(titleField);
        formPanel.add(new JLabel("作者:"));
        authorField = new JTextField();
        formPanel.add(authorField);
        formPanel.add(new JLabel("ISBN:"));
        isbnField = new JTextField();
        formPanel.add(isbnField);
        formPanel.add(new JLabel("副本数:"));
        copiesField = new JTextField("1");
        formPanel.add(copiesField);
        JButton addButton = new JButton("添加图书");
        addButton.addActionListener(this::addBook);
        formPanel.add(addButton);
        // --- 中间: 搜索框 ---
        JPanel searchPanel = new JPanel(new BorderLayout());
        JTextField searchField = new JTextField();
        JButton searchButton = new JButton("搜索");
        searchButton.addActionListener(e -> {
            try {
                List<Book> books = bookDAO.searchBooks(searchField.getText());
                displayBooks(books);
            } catch (SQLException ex) {
                JOptionPane.showMessageDialog(this, "搜索失败: " + ex.getMessage(), "错误", JOptionPane.ERROR_MESSAGE);
            }
        });
        searchPanel.add(new JLabel("搜索书名/作者:"), BorderLayout.WEST);
        searchPanel.add(searchField, BorderLayout.CENTER);
        searchPanel.add(searchButton, BorderLayout.EAST);
        // --- 底部: 图书列表表格 ---
        String[] columnNames = {"ID", "书名", "作者", "ISBN", "总副本", "可借"};
        tableModel = new DefaultTableModel(columnNames, 0) {
            @Override
            public boolean isCellEditable(int row, int column) {
                return false; // 所有单元格不可编辑
            }
        };
        bookTable = new JTable(tableModel);
        JScrollPane scrollPane = new JScrollPane(bookTable);
        // 刷新按钮
        JButton refreshButton = new JButton("刷新列表");
        refreshButton.addActionListener(e -> {
            try {
                loadAllBooks();
            } catch (SQLException ex) {
                JOptionPane.showMessageDialog(this, "刷新失败: " + ex.getMessage(), "错误", JOptionPane.ERROR_MESSAGE);
            }
        });
        // 按钮面板
        JPanel buttonPanel = new JPanel();
        buttonPanel.add(refreshButton);
        // 组装面板
        JPanel topPanel = new JPanel(new BorderLayout());
        topPanel.add(formPanel, BorderLayout.NORTH);
        topPanel.add(searchPanel, BorderLayout.SOUTH);
        panel.add(topPanel, BorderLayout.NORTH);
        panel.add(scrollPane, BorderLayout.CENTER);
        panel.add(buttonPanel, BorderLayout.SOUTH);
        // 初始加载所有图书
        try {
            loadAllBooks();
        } catch (SQLException e) {
            JOptionPane.showMessageDialog(this, "初始加载失败: " + e.getMessage(), "错误", JOptionPane.ERROR_MESSAGE);
        }
        return panel;
    }
    private void addBook(ActionEvent e) {
        try {
            String title = titleField.getText();
            String author = authorField.getText();
            String isbn = isbnField.getText();
            int copies = Integer.parseInt(copiesField.getText());
            if (title.isEmpty() || author.isEmpty() || isbn.isEmpty()) {
                JOptionPane.showMessageDialog(this, "请填写所有字段!", "警告", JOptionPane.WARNING_MESSAGE);
                return;
            }
            Book book = new Book(title, author, isbn, copies);
            bookDAO.addBook(book);
            JOptionPane.showMessageDialog(this, "图书添加成功!", "成功", JOptionPane.INFORMATION_MESSAGE);
            titleField.setText("");
            authorField.setText("");
            isbnField.setText("");
            copiesField.setText("1");
            loadAllBooks(); // 刷新列表
        } catch (NumberFormatException ex) {
            JOptionPane.showMessageDialog(this, "副本数必须是数字!", "错误", JOptionPane.ERROR_MESSAGE);
        } catch (SQLException ex) {
            JOptionPane.showMessageDialog(this, "添加失败: " + ex.getMessage(), "错误", JOptionPane.ERROR_MESSAGE);
        }
    }
    private void loadAllBooks() throws SQLException {
        List<Book> books = bookDAO.getAllBooks();
        displayBooks(books);
    }
    private void displayBooks(List<Book> books) {
        tableModel.setRowCount(0); // 清空表格
        for (Book book : books) {
            Object[] row = {
                    book.getId(),
                    book.getTitle(),
                    book.getAuthor(),
                    book.getIsbn(),
                    book.getTotalCopies(),
                    book.getAvailableCopies()
            };
            tableModel.addRow(row);
        }
    }
    public static void main(String[] args) {
        // 确保GUI在事件调度线程中创建和显示
        SwingUtilities.invokeLater(() -> {
            try {
                // 检查MySQL驱动是否加载
                Class.forName("com.mysql.cj.jdbc.Driver");
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
                JOptionPane.showMessageDialog(null, "MySQL JDBC Driver not found. Please add it to your project.", "Error", JOptionPane.ERROR_MESSAGE);
                System.exit(1);
            }
            new Main().setVisible(true);
        });
    }
}

如何运行项目

  1. 环境准备:

    • 安装 JDK 8 或更高版本。
    • 安装 MySQL 数据库。
    • 配置好 Maven。
  2. 数据库设置:

    • 使用MySQL客户端(如Navicat, DBeaver或命令行)连接到你的MySQL服务器。
    • 复制并执行第一步中的SQL脚本,创建数据库和表。
    • 修改 DatabaseUtil.java 文件中的 USERPASSWORD 为你自己的数据库凭据。
  3. 项目构建与运行:

    • 将上述所有Java代码文件放入对应的项目目录结构中。
    • 在项目根目录(library-management-system/)下,打开命令行或终端。
    • 执行 mvn clean install 来编译项目并下载依赖。
    • 编译成功后,运行 Main.java,在IDE(如IntelliJ IDEA或Eclipse)中,可以直接右键 Main.java 并选择 "Run"。

功能扩展建议

这个系统已经具备了基本的增删改查功能,你可以在此基础上进行扩展:

  1. 用户管理: 实现用户的注册、登录功能。Main窗口启动时先显示登录界面。
  2. 借阅功能: 在图书列表中增加“借阅”按钮,点击后,弹出对话框让用户输入用户ID或选择用户,然后调用BorrowDAO.borrowBook()
  3. 还书功能: 创建一个新的“借阅记录”界面,显示所有未归还的借阅记录,并提供“归还”按钮,调用BorrowDAO.returnBook()
  4. 图书编辑/删除: 在图书列表中增加“编辑”和“删除”按钮。
  5. 更美观的UI: 使用更高级的Swing库,如 FlatLaf,来美化界面。
  6. 多线程: 如果数据库操作耗时,可以使用Swing的 SwingWorker 在后台线程执行,避免界面卡顿。

这个项目是一个非常好的Java入门和综合实践案例,它涵盖了面向对象编程、数据库操作、异常处理、GUI编程和事务管理等核心概念。

分享:
扫描分享到社交APP
上一篇
下一篇