实战项目:基于控制台与数据库的图书管理系统开发指南

一、项目概述与设计思路

1.1 为什么选择图书管理系统

图书管理系统是学习编程的经典项目,它涵盖了:

  • 控制台交互:学习用户输入输出处理

  • 数据库操作:掌握CRUD核心功能

  • 业务逻辑:理解实际应用场景

  • 系统架构:实践分层设计思想

1.2 系统功能设计

核心功能模块

1. 图书管理
   - 添加新书
   - 删除图书
   - 修改图书信息
   - 查询图书(按ID/书名/作者)
   
2. 借阅管理
   - 图书借出
   - 图书归还
   - 借阅记录查询
   
3. 用户管理
   - 读者注册
   - 读者信息修改
   - 读者注销
   
4. 统计报表
   - 图书库存统计
   - 借阅排行榜
   - 逾期未还清单

1.3 技术选型

技术组件选择方案备注
开发语言Java 8+兼顾教学与实用
数据库MySQL 8.0免费开源,应用广泛
数据库连接JDBC学习原生数据库操作
控制台框架-纯Java实现
单元测试JUnit 5保证代码质量
日志系统SLF4J + Logback记录系统运行状态

二、数据库设计与实现

2.1 数据库表结构设计

ER图关键实体

图书(Book) ---< 借阅记录(BorrowRecord) >--- 读者(Reader)

建表SQL

-- 图书表
CREATE TABLE books (
    book_id INT AUTO_INCREMENT PRIMARY KEY,
    isbn VARCHAR(20) NOT NULL UNIQUE,
    title VARCHAR(100) NOT NULL,
    author VARCHAR(50) NOT NULL,
    publisher VARCHAR(50),
    publish_date DATE,
    price DECIMAL(10,2),
    stock INT DEFAULT 1 COMMENT '库存数量',
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 读者表
CREATE TABLE readers (
    reader_id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(50) NOT NULL,
    gender CHAR(1) CHECK (gender IN ('M', 'F')),
    phone VARCHAR(20),
    email VARCHAR(100),
    register_date DATE DEFAULT (CURRENT_DATE),
    membership_level INT DEFAULT 1
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 借阅记录表
CREATE TABLE borrow_records (
    record_id INT AUTO_INCREMENT PRIMARY KEY,
    book_id INT NOT NULL,
    reader_id INT NOT NULL,
    borrow_date DATETIME DEFAULT CURRENT_TIMESTAMP,
    due_date DATETIME GENERATED ALWAYS AS (borrow_date + INTERVAL 30 DAY) STORED,
    return_date DATETIME,
    status TINYINT DEFAULT 1 COMMENT '1-借出 2-已还 3-逾期',
    FOREIGN KEY (book_id) REFERENCES books(book_id),
    FOREIGN KEY (reader_id) REFERENCES readers(reader_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

2.2 索引优化设计

-- 提高查询性能的索引
CREATE INDEX idx_books_title ON books(title);
CREATE INDEX idx_books_author ON books(author);
CREATE INDEX idx_borrow_records_status ON borrow_records(status);
CREATE INDEX idx_borrow_records_due_date ON borrow_records(due_date);

2.3 初始化测试数据

-- 插入示例图书
INSERT INTO books (isbn, title, author, publisher, price, stock)
VALUES 
('9787111636667', 'Java核心技术 卷I', 'Cay S. Horstmann', '机械工业出版社', 119.00, 5),
('9787115523660', 'Effective Java', 'Joshua Bloch', '机械工业出版社', 129.00, 3),
('9787302515421', 'Python编程:从入门到实践', 'Eric Matthes', '人民邮电出版社', 89.00, 7);

-- 插入示例读者
INSERT INTO readers (name, gender, phone, email)
VALUES 
('张三', 'M', '13800138001', 'zhangsan@example.com'),
('李四', 'F', '13900139001', 'lisi@example.com');

三、Java核心代码实现

3.1 数据库连接层

DBUtil.java - 数据库工具类:

public class DBUtil {
    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 = "123456";
    
    static {
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
    
    public static Connection getConnection() throws SQLException {
        return DriverManager.getConnection(URL, USER, PASSWORD);
    }
    
    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();
        }
    }
}

3.2 实体类设计

Book.java - 图书实体:

public class Book {
    private Integer bookId;
    private String isbn;
    private String title;
    private String author;
    private String publisher;
    private Date publishDate;
    private BigDecimal price;
    private Integer stock;
    
    // 构造方法、getter和setter省略
    // 建议使用Lombok @Data注解简化代码
}

3.3 数据访问层(DAO)

BookDAO.java - 图书数据访问:

public class BookDAO {
    // 添加新书
    public boolean addBook(Book book) {
        String sql = "INSERT INTO books (isbn, title, author, publisher, publish_date, price, stock) " +
                     "VALUES (?, ?, ?, ?, ?, ?, ?)";
        try (Connection conn = DBUtil.getConnection();
             PreparedStatement pstmt = conn.prepareStatement(sql)) {
            
            pstmt.setString(1, book.getIsbn());
            pstmt.setString(2, book.getTitle());
            pstmt.setString(3, book.getAuthor());
            pstmt.setString(4, book.getPublisher());
            pstmt.setDate(5, new java.sql.Date(book.getPublishDate().getTime()));
            pstmt.setBigDecimal(6, book.getPrice());
            pstmt.setInt(7, book.getStock());
            
            return pstmt.executeUpdate() > 0;
        } catch (SQLException e) {
            e.printStackTrace();
            return false;
        }
    }
    
    // 按ID查询图书
    public Book getBookById(int bookId) {
        String sql = "SELECT * FROM books WHERE book_id = ?";
        Book book = null;
        
        try (Connection conn = DBUtil.getConnection();
             PreparedStatement pstmt = conn.prepareStatement(sql)) {
            
            pstmt.setInt(1, bookId);
            try (ResultSet rs = pstmt.executeQuery()) {
                if (rs.next()) {
                    book = new Book();
                    book.setBookId(rs.getInt("book_id"));
                    book.setIsbn(rs.getString("isbn"));
                    // 设置其他属性...
                }
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        
        return book;
    }
    
    // 其他CRUD方法...
}

3.4 业务逻辑层(Service)

LibraryService.java - 核心业务逻辑:

public class LibraryService {
    private BookDAO bookDAO = new BookDAO();
    private ReaderDAO readerDAO = new ReaderDAO();
    private BorrowRecordDAO recordDAO = new BorrowRecordDAO();
    
    // 借书业务方法
    public boolean borrowBook(int bookId, int readerId) {
        // 检查图书库存
        Book book = bookDAO.getBookById(bookId);
        if (book == null || book.getStock() <= 0) {
            System.out.println("图书不存在或库存不足");
            return false;
        }
        
        // 检查读者是否存在
        Reader reader = readerDAO.getReaderById(readerId);
        if (reader == null) {
            System.out.println("读者不存在");
            return false;
        }
        
        // 检查是否已借过同一本书未还
        if (recordDAO.hasUnreturnedRecord(bookId, readerId)) {
            System.out.println("您已借阅该书且未归还");
            return false;
        }
        
        // 开启事务
        Connection conn = null;
        try {
            conn = DBUtil.getConnection();
            conn.setAutoCommit(false);
            
            // 1. 减少库存
            bookDAO.updateStock(conn, bookId, -1);
            
            // 2. 创建借阅记录
            BorrowRecord record = new BorrowRecord();
            record.setBookId(bookId);
            record.setReaderId(readerId);
            recordDAO.addRecord(conn, record);
            
            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);
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    // 其他业务方法...
}

3.5 控制台界面实现

ConsoleUI.java - 用户交互界面:

public class ConsoleUI {
    private Scanner scanner = new Scanner(System.in);
    private LibraryService libraryService = new LibraryService();
    
    public void start() {
        while (true) {
            showMainMenu();
            int choice = getIntInput("请选择操作:");
            
            switch (choice) {
                case 1:
                    manageBooks();
                    break;
                case 2:
                    manageReaders();
                    break;
                case 3:
                    manageBorrowing();
                    break;
                case 4:
                    generateReports();
                    break;
                case 0:
                    System.out.println("感谢使用图书管理系统,再见!");
                    return;
                default:
                    System.out.println("无效选择,请重新输入");
            }
        }
    }
    
    private void showMainMenu() {
        System.out.println("\n===== 图书管理系统 =====");
        System.out.println("1. 图书管理");
        System.out.println("2. 读者管理");
        System.out.println("3. 借阅管理");
        System.out.println("4. 统计报表");
        System.out.println("0. 退出系统");
    }
    
    private void manageBooks() {
        while (true) {
            System.out.println("\n===== 图书管理 =====");
            System.out.println("1. 添加新书");
            System.out.println("2. 查询图书");
            System.out.println("3. 修改图书信息");
            System.out.println("4. 删除图书");
            System.out.println("0. 返回上级菜单");
            
            int choice = getIntInput("请选择操作:");
            switch (choice) {
                case 1:
                    addNewBook();
                    break;
                case 2:
                    searchBooks();
                    break;
                // 其他case...
                case 0:
                    return;
                default:
                    System.out.println("无效选择");
            }
        }
    }
    
    private void addNewBook() {
        System.out.println("\n--- 添加新书 ---");
        String isbn = getStringInput("ISBN:");
        String title = getStringInput("书名:");
        String author = getStringInput("作者:");
        
        Book book = new Book();
        book.setIsbn(isbn);
        book.setTitle(title);
        book.setAuthor(author);
        // 设置其他属性...
        
        if (libraryService.addBook(book)) {
            System.out.println("添加图书成功!");
        } else {
            System.out.println("添加图书失败");
        }
    }
    
    // 其他方法...
    
    private int getIntInput(String prompt) {
        while (true) {
            try {
                System.out.print(prompt);
                return Integer.parseInt(scanner.nextLine());
            } catch (NumberFormatException e) {
                System.out.println("请输入有效数字!");
            }
        }
    }
    
    private String getStringInput(String prompt) {
        System.out.print(prompt);
        return scanner.nextLine();
    }
}

四、项目扩展与优化

4.1 功能扩展建议

  1. 预约功能

    • 允许读者预约已被借出的图书

    • 图书归还时通知预约读者

  2. 逾期罚款

    • 计算逾期天数

    • 按规则自动计算罚款金额

  3. 图书推荐

    • 基于借阅历史的简单推荐

    • 热门图书推荐

4.2 代码优化方向

引入连接池

// 使用HikariCP替代原生JDBC连接
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/library_db");
config.setUsername("root");
config.setPassword("123456");
HikariDataSource dataSource = new HikariDataSource(config);

使用DAO接口

public interface BookDAO {
    boolean addBook(Book book);
    Book getBookById(int bookId);
    // 其他方法...
}

public class BookDAOImpl implements BookDAO {
    // 实现方法...
}

日志记录

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class BookDAOImpl implements BookDAO {
    private static final Logger logger = LoggerFactory.getLogger(BookDAOImpl.class);
    
    public boolean addBook(Book book) {
        logger.debug("尝试添加图书:{}", book.getTitle());
        // 实现代码...
    }
}

4.3 异常处理改进

自定义异常类

public class LibraryException extends Exception {
    public LibraryException(String message) {
        super(message);
    }
    
    public LibraryException(String message, Throwable cause) {
        super(message, cause);
    }
}

// 使用示例
public void borrowBook(int bookId, int readerId) throws LibraryException {
    try {
        // 业务逻辑...
    } catch (SQLException e) {
        throw new LibraryException("借书操作失败", e);
    }
}

五、项目部署与测试

5.1 单元测试示例

BookDAOTest.java

public class BookDAOTest {
    private BookDAO bookDAO = new BookDAOImpl();
    
    @Test
    public void testAddAndGetBook() {
        Book book = new Book();
        book.setIsbn("978-3-16-148410-0");
        book.setTitle("测试图书");
        book.setAuthor("测试作者");
        
        boolean added = bookDAO.addBook(book);
        assertTrue(added);
        
        Book retrieved = bookDAO.getBookByIsbn("978-3-16-148410-0");
        assertNotNull(retrieved);
        assertEquals("测试图书", retrieved.getTitle());
    }
    
    // 其他测试方法...
}

5.2 系统测试流程

  1. 图书管理测试

    • 添加不同种类的图书

    • 测试各种查询条件组合

    • 验证库存更新逻辑

  2. 借还书测试

    • 正常借书/还书流程

    • 测试库存不足情况

    • 验证逾期计算正确性

  3. 并发测试

    • 模拟多个用户同时借阅同一本书

    • 验证库存扣减的原子性

结语

通过这个图书管理系统项目,我们完整实践了:

  1. 控制台程序的交互设计

  2. 数据库表结构设计与优化

  3. JDBC的实战应用

  4. 分层架构的实现

  5. 基础业务逻辑开发

进一步学习建议

  1. 尝试使用MyBatis重构数据访问层

  2. 添加Web界面转型为B/S架构

  3. 学习使用Spring框架改造项目

  4. 研究数据库事务隔离级别的实际影响

如果您在实现过程中遇到任何问题,欢迎在评论区留言讨论。觉得本文有帮助的话,请点赞收藏支持!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值