- 后端: 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);
});
}
}
如何运行项目
-
环境准备:
- 安装 JDK 8 或更高版本。
- 安装 MySQL 数据库。
- 配置好 Maven。
-
数据库设置:
- 使用MySQL客户端(如Navicat, DBeaver或命令行)连接到你的MySQL服务器。
- 复制并执行第一步中的SQL脚本,创建数据库和表。
- 修改
DatabaseUtil.java文件中的USER和PASSWORD为你自己的数据库凭据。
-
项目构建与运行:
- 将上述所有Java代码文件放入对应的项目目录结构中。
- 在项目根目录(
library-management-system/)下,打开命令行或终端。 - 执行
mvn clean install来编译项目并下载依赖。 - 编译成功后,运行
Main.java,在IDE(如IntelliJ IDEA或Eclipse)中,可以直接右键Main.java并选择 "Run"。
功能扩展建议
这个系统已经具备了基本的增删改查功能,你可以在此基础上进行扩展:
- 用户管理: 实现用户的注册、登录功能。
Main窗口启动时先显示登录界面。 - 借阅功能: 在图书列表中增加“借阅”按钮,点击后,弹出对话框让用户输入用户ID或选择用户,然后调用
BorrowDAO.borrowBook()。 - 还书功能: 创建一个新的“借阅记录”界面,显示所有未归还的借阅记录,并提供“归还”按钮,调用
BorrowDAO.returnBook()。 - 图书编辑/删除: 在图书列表中增加“编辑”和“删除”按钮。
- 更美观的UI: 使用更高级的Swing库,如 FlatLaf,来美化界面。
- 多线程: 如果数据库操作耗时,可以使用Swing的
SwingWorker在后台线程执行,避免界面卡顿。
这个项目是一个非常好的Java入门和综合实践案例,它涵盖了面向对象编程、数据库操作、异常处理、GUI编程和事务管理等核心概念。
