实战项目终极指南:从零构建企业级图书借阅系统 (BookBorrowSys)

项目愿景与核心功能模块

本项目旨在构建一个功能完备、结构清晰的后台服务系统,用于管理图书、读者及他们之间的借阅关系。通过这个项目,你将实践并融会贯通 Maven、Mybatis (动态SQL、关联查询)、事务管理、分层架构、日志和 JUnit 单元测试等所有核心知识。

核心功能模块详解
  1. 图书管理 (Book Management)

    • 职责: 负责图书信息的生命周期管理。
    • 核心操作:
      • 新增图书: 向数据库中添加一本新书的完整信息。
      • 动态条件查询: 提供一个强大的搜索接口,可以根据书名(模糊)、作者(模糊)、分类ID等任意组合进行查询。这是动态SQL的最佳实践场景。
      • 更新图书信息: 修改图书的标题、作者等信息,以及最重要的库存管理。
      • 删除图书: 从系统中移除一本图书。
  2. 读者管理 (Reader Management)

    • 职责: 负责读者信息的管理。
    • 核心操作:
      • 新增读者: 注册一个新读者。
      • 查询读者信息: 根据ID或唯一的邮箱地址查找读者。
      • 更新读者信息: 修改读者的姓名等。
  3. 借阅管理 (Borrow Management)

    • 职责: 系统的核心业务,处理图书与读者之间的交互,并保证数据的一致性。
    • 核心操作:
      • 借书流程 (带事务): 这是项目的核心业务逻辑。它需要在一个事务中完成两步操作:1. 扣减图书库存;2. 创建一条借阅记录。任何一步失败,整个操作必须回滚。
      • 还书流程 (带事务): 同样是核心业务。在一个事务中完成两步操作:1. 增加图书库存;2. 更新借阅记录的状态和归还日期。
      • 复杂关联查询:
        • 查询某个读者的所有借阅历史(需要同时展示图书的标题等信息)。这是一对多关联查询的典型例子。
        • 查询超期未还的借阅记录,用于催还提醒。

第一部分:环境准备与项目搭建

1. 数据库初始化

【操作】: 在你的MySQL数据库中执行以下SQL脚本,创建数据库和所有需要的表,并插入初始数据。

-- 创建数据库
CREATE DATABASE IF NOT EXISTS book_borrow_sys CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE book_borrow_sys;

-- 创建图书表 (book)
CREATE TABLE `book` (
  `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `title` varchar(255) NOT NULL COMMENT '书名',
  `author` varchar(100) DEFAULT NULL COMMENT '作者',
  `category_id` int DEFAULT NULL COMMENT '分类ID',
  `stock` int NOT NULL DEFAULT '0' COMMENT '库存',
  `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB COMMENT='图书信息表';

-- 创建读者表 (reader)
CREATE TABLE `reader` (
  `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `name` varchar(100) NOT NULL COMMENT '姓名',
  `email` varchar(255) NOT NULL UNIQUE COMMENT '邮箱, 唯一',
  `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '注册时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB COMMENT='读者信息表';

-- 创建借阅记录表 (borrow_record)
CREATE TABLE `borrow_record` (
  `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `book_id` int NOT NULL COMMENT '外键, 关联book.id',
  `reader_id` int NOT NULL COMMENT '外键, 关联reader.id',
  `borrow_date` date NOT NULL COMMENT '借出日期',
  `return_date` date DEFAULT NULL COMMENT '归还日期',
  `status` varchar(20) NOT NULL COMMENT '状态: BORROWED, RETURNED',
  PRIMARY KEY (`id`),
  KEY `fk_book_id` (`book_id`),
  KEY `fk_reader_id` (`reader_id`)
) ENGINE=InnoDB COMMENT='图书借阅记录表';

-- 插入初始测试数据
INSERT INTO `book` (title, author, category_id, stock) VALUES
('Mybatis从入门到精通', '项目导师', 1, 5),
('深入理解Java虚拟机', '周志明', 1, 3),
('三体', '刘慈欣', 2, 10),
('Effective Java', 'Joshua Bloch', 1, 0); -- 库存为0的书,用于测试

INSERT INTO `reader` (name, email) VALUES
('小明', 'xiaoming@example.com'),
('小红', 'xiaohong@example.com');

2. 使用IDEA创建多模块Maven项目

1. 项目结构

请先按照这个结构,在你的IDE中创建好对应的Maven模块和目录。(如何创建请参考此目录往下的创建多模块Maven项目的具体操作流程)

BookBorrowSys/ (父项目)
├── pom.xml
├── bbs-persist/
│   ├── pom.xml
│   └── src/
│       ├── main/
│       │   ├── java/com/example/bbs/persist/
│       │   │   ├── entity/ (Book.java, Reader.java, BorrowRecord.java)
│       │   │   └── mapper/ (BookMapper.java, ReaderMapper.java, BorrowMapper.java)
│       │   └── resources/
│       │       ├── mappers/ (BookMapper.xml, ReaderMapper.xml, BorrowMapper.xml)
│       │       ├── mybatis-config.xml
│       │       └── logback.xml
│       └── test/java/com/example/bbs/persist/mapper/ (*MapperTest.java)
├── bbs-service/
│   ├── pom.xml
│   └── src/
│       ├── main/java/com/example/bbs/service/ (BorrowService.java)
│       └── test/java/com/example/bbs/service/ (BorrowServiceTest.java)
└── bbs-main/
    ├── pom.xml
    └── src/main/java/com/example/bbs/
        ├── util/ (MybatisUtil.java)
        └── main/ (MainApplication.java)

在IntelliJ IDEA专业版中创建多模块Maven项目

我们将分两步完成:

  1. 创建父项目 (Parent Project): BookBorrowSys
  2. 在父项目下创建子模块 (Child Modules): bbs-persist, bbs-service, bbs-main

第一步:创建父项目 BookBorrowSys
  1. 启动创建向导
    打开IntelliG IDEA,点击 File -> New -> Project...

  2. 选择项目类型和配置
    在弹出的“New Project”窗口中:

    • 在左侧选择 Maven
    • 确保你的 Project SDK 已经正确选择了你安装的JDK版本(如11或8)。
    • 不要勾选 Create from archetype (我们从一个空项目开始)。
    • 点击 Next
  3. 定义父项目的坐标
    在这一步,我们为父项目命名并定义其Maven坐标:

    • Name: BookBorrowSys (项目名称,会自动填充到ArtifactId)
    • Location: 选择一个你存放项目的本地路径。
    • 展开 Artifact Coordinates:
      • GroupId: com.example (通常是公司域名的反向)
      • ArtifactId: BookBorrowSys (项目/模块的名称)
      • Version: 1.0-SNAPSHOT (保持默认即可)
    • 点击 Finish
  4. 父项目创建完成
    现在,IDEA会为你创建一个项目。打开pom.xml,你会看到一个基本的POM文件。

  5. 【关键】将父项目打包方式改为pom
    父项目本身不包含任何代码,它只负责管理和聚合子模块。所以,它的打包方式必须是 pom
    打开 BookBorrowSys/pom.xml,在<version>标签下面添加一行:

    <packaging>pom</packaging>
    

第二步:创建子模块

现在,我们将在父项目的基础上,逐一创建bbs-persist, bbs-service, bbs-main这三个子模块。

  1. 启动创建子模块向导

    • 在左侧的项目视图中,右键点击父项目的根目录 (BookBorrowSys)。
    • 选择 New -> Module...
  2. 创建第一个子模块 bbs-persist
    在弹出的“New Module”窗口中:

    • 左侧依然选择 Maven
    • Parent: IDEA会自动识别并选中你的父项目BookBorrowSys这是最关键的一步,确保了继承关系。
    • Name: bbs-persist (填写模块名)
    • Artifact Coordinates下的GroupIdVersion会自动从父项目继承,你只需要确认ArtifactIdbbs-persist即可。
    • 点击 Finish
  3. 观察pom.xml的变化
    创建完成后,你会发现两处重要的自动变化:

    • 子模块的pom.xml (bbs-persist/pom.xml): 自动添加了<parent>标签,指明了它的父项目。
    • 父项目的pom.xml (BookBorrowSys/pom.xml): 自动添加了<modules>标签,并将bbs-persist加入了聚合列表。

    这就是IDEA专业版强大的地方,它自动帮你维护了聚合与继承的关系。

  4. 重复步骤创建其他子模块

    • 重复上述第1、2步,再次右键点击父项目BookBorrowSys -> New -> Module...
    • 创建bbs-service模块。
    • 创建bbs-main模块。

现在,你已经拥有了一个结构清晰、配置规范的企业级多模块项目骨架。可以开始在里面愉快地编写代码了!这个过程看似步骤多,但一旦熟练,你会发现它极大地提升了大型项目的组织效率和可维护性。


第二部分:项目配置 (可直接复制)

这部分是项目的“基础设施”,属于配置范畴,可以直接复制粘贴来快速搭建环境。

1. 父项目BookBorrowSys/pom.xml

【操作】: 将以下完整内容覆盖你创建的父项目的pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<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>BookBorrowSys</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>pom</packaging>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <mybatis.version>3.5.9</mybatis.version>
        <mysql.connector.version>8.0.28</mysql.connector.version>
        <lombok.version>1.18.24</lombok.version>
        <junit.version>5.8.2</junit.version>
        <slf4j.version>1.7.32</slf4j.version>
        <logback.version>1.2.9</logback.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <!-- Database & Persistence -->
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis</artifactId>
                <version>${mybatis.version}</version>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>${mysql.connector.version}</version>
                <scope>runtime</scope>
            </dependency>
            
            <!-- Logging -->
            <dependency>
                <groupId>org.slf4j</groupId>
                <artifactId>slf4j-api</artifactId>
                <version>${slf4j.version}</version>
            </dependency>
            <dependency>
                <groupId>ch.qos.logback</groupId>
                <artifactId>logback-classic</artifactId>
                <version>${logback.version}</version>
            </dependency>

            <!-- Utils & Testing -->
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>${lombok.version}</version>
                <scope>provided</scope>
            </dependency>
            <dependency>
                <groupId>org.junit.jupiter</groupId>
                <artifactId>junit-jupiter-api</artifactId>
                <version>${junit.version}</version>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>org.junit.jupiter</groupId>
                <artifactId>junit-jupiter-engine</artifactId>
                <version>${junit.version}</version>
                <scope>test</scope>
            </dependency>
            
            <!-- Internal Modules -->
            <dependency>
                <groupId>com.example</groupId>
                <artifactId>bbs-persist</artifactId>
                <version>${project.version}</version>
            </dependency>
            <dependency>
                <groupId>com.example</groupId>
                <artifactId>bbs-service</artifactId>
                <version>${project.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <modules>
        <module>bbs-persist</module>
        <module>bbs-service</module>
        <module>bbs-main</module>
    </modules>
</project>
2. 子模块的pom.xml文件

【操作】: 分别将以下内容覆盖到对应子模块的pom.xml文件中。

  • bbs-persist/pom.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <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>
        <parent>
            <groupId>com.example</groupId>
            <artifactId>BookBorrowSys</artifactId>
            <version>1.0-SNAPSHOT</version>
        </parent>
        <artifactId>bbs-persist</artifactId>
    
        <dependencies>
            <dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId></dependency>
            <dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency>
            <dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency>
            <dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-api</artifactId></dependency>
            <dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-engine</artifactId></dependency>
        </dependencies>
    </project>
    
  • bbs-service/pom.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <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>
        <parent>
            <groupId>com.example</groupId>
            <artifactId>BookBorrowSys</artifactId>
            <version>1.0-SNAPSHOT</version>
        </parent>
        <artifactId>bbs-service</artifactId>
    
        <dependencies>
            <dependency><groupId>com.example</groupId><artifactId>bbs-persist</artifactId></dependency>
            <dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId></dependency>
            <dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-api</artifactId></dependency>
            <dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-engine</artifactId></dependency>
        </dependencies>
    </project>
    
  • bbs-main/pom.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <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>
        <parent>
            <groupId>com.example</groupId>
            <artifactId>BookBorrowSys</artifactId>
            <version>1.0-SNAPSHOT</version>
        </parent>
        <artifactId>bbs-main</artifactId>
    
        <dependencies>
            <dependency><groupId>com.example</groupId><artifactId>bbs-service</artifactId></dependency>
            <dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId></dependency>
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-assembly-plugin</artifactId>
                    <version>3.3.0</version>
                    <configuration>
                        <archive>
                            <manifest>
                                <mainClass>com.example.bbs.main.MainApplication</mainClass>
                            </manifest>
                        </archive>
                        <descriptorRefs>
                            <descriptorRef>jar-with-dependencies</descriptorRef>
                        </descriptorRefs>
                    </configuration>
                    <executions>
                        <execution>
                            <id>make-assembly</id>
                            <phase>package</phase>
                            <goals>
                                <goal>single</goal>
                            </goals>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </build>
    </project>
    
3. mybatis-config.xmllogback.xml

【操作】: 在bbs-persist/src/main/resources/目录下创建这两个文件,并复制以下内容。

  • mybatis-config.xml

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
        <settings>
            <!-- 开启下划线到驼峰的自动映射 -->
            <setting name="mapUnderscoreToCamelCase" value="true"/>
            <!-- 指定日志实现为 SLF4J -->
            <setting name="logImpl" value="SLF4J"/>
        </settings>
    
        <!-- 自动为实体类起别名 -->
        <typeAliases>
            <package name="com.example.bbs.persist.entity"/>
        </typeAliases>
    
        <environments default="development">
            <environment id="development">
                <transactionManager type="JDBC"/>
                <dataSource type="POOLED">
                    <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                    <property name="url" value="jdbc:mysql://localhost:3306/book_borrow_sys?useSSL=false&amp;serverTimezone=UTC"/>
                    <property name="username" value="root"/>
                    <!-- 记得修改成你自己的数据库密码 -->
                    <property name="password" value="your_password"/>
                </dataSource>
            </environment>
        </environments>
    
        <!-- 自动扫描Mapper XML文件 -->
        <mappers>
            <package name="com.example.bbs.persist.mapper"/>
        </mappers>
    </configuration>
    
  • logback.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <configuration>
        <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
            <encoder>
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
            </encoder>
        </appender>
    
        <root level="INFO">
            <appender-ref ref="STDOUT" />
        </root>
    
        <!-- 为Mybatis的mapper单独设置日志级别为DEBUG,方便在开发时查看执行的SQL语句 -->
        <logger name="com.example.bbs.persist.mapper" level="DEBUG" additivity="false">
            <appender-ref ref="STDOUT" />
        </logger>
    </configuration>
    

第三部分:持久层开发 (bbs-persist)

这部分是编码的核心,强烈建议你亲手敲一遍,以加深对Mybatis和SQL的理解。

1. 实体类 (Entity)

【操作】: 在bbs-persist/src/main/java/com/example/bbs/persist/entity/目录下创建以下三个Java类。

  • Book.java

    package com.example.bbs.persist.entity;
    
    import lombok.Data;
    import java.util.Date;
    
    @Data
    public class Book {
        private Integer id;
        private String title;
        private String author;
        private Integer categoryId;
        private Integer stock;
        private Date createdAt;
    }
    
  • Reader.java

    package com.example.bbs.persist.entity;
    
    import lombok.Data;
    import java.util.Date;
    
    @Data
    public class Reader {
        private Integer id;
        private String name;
        private String email;
        private Date createdAt;
    }
    
  • BorrowRecord.java

    package com.example.bbs.persist.entity;
    
    import lombok.Data;
    import java.util.Date;
    
    @Data
    public class BorrowRecord {
        private Integer id;
        private Integer bookId;
        private Integer readerId;
        private Date borrowDate;
        private Date returnDate;
        private String status;
    
        // 关联对象,用于复杂查询的结果映射
        private Book book;
        private Reader reader;
    }
    
2. Mapper接口与XML文件

【操作】: 在bbs-persist模块中,分别创建对应的Java接口和XML文件。

  • BookMapper.java (.../mapper/)

    package com.example.bbs.persist.mapper;
    
    import com.example.bbs.persist.entity.Book;
    import org.apache.ibatis.annotations.Param;
    import java.util.List;
    
    public interface BookMapper {
        int insert(Book book);
        Book findById(Integer id);
        List<Book> findByCondition(Book book);
        int update(Book book);
        int deleteById(Integer id);
        int updateStock(@Param("id") Integer id, @Param("amount") int amount);
    }
    
  • BookMapper.xml (.../resources/mappers/)

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.example.bbs.persist.mapper.BookMapper">
    
        <insert id="insert" useGeneratedKeys="true" keyProperty="id">
            INSERT INTO book (title, author, category_id, stock)
            VALUES (#{title}, #{author}, #{categoryId}, #{stock})
        </insert>
    
        <select id="findById" resultType="Book">
            SELECT * FROM book WHERE id = #{id}
        </select>
    
        <select id="findByCondition" parameterType="Book" resultType="Book">
            SELECT * FROM book
            <where>
                <if test="title != null and title != ''">
                    AND title LIKE CONCAT('%', #{title}, '%')
                </if>
                <if test="author != null and author != ''">
                    AND author LIKE CONCAT('%', #{author}, '%')
                </if>
                <if test="categoryId != null">
                    AND category_id = #{categoryId}
                </if>
            </where>
        </select>
    
        <update id="update" parameterType="Book">
            UPDATE book
            <set>
                <if test="title != null and title != ''">title = #{title},</if>
                <if test="author != null and author != ''">author = #{author},</if>
                <if test="categoryId != null">category_id = #{categoryId},</if>
                <if test="stock != null">stock = #{stock},</if>
            </set>
            WHERE id = #{id}
        </update>
    
        <delete id="deleteById">
            DELETE FROM book WHERE id = #{id}
        </delete>
    
        <update id="updateStock">
            UPDATE book SET stock = stock + #{amount} WHERE id = #{id} AND stock + #{amount} >= 0
        </update>
    
    </mapper>
    
  • ReaderMapper.java & ReaderMapper.xml:

    // ReaderMapper.java
    package com.example.bbs.persist.mapper;
    import com.example.bbs.persist.entity.Reader;
    public interface ReaderMapper {
        int insert(Reader reader);
        Reader findById(Integer id);
        Reader findByEmail(String email);
    }
    
    <!-- ReaderMapper.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.example.bbs.persist.mapper.ReaderMapper">
        <insert id="insert" useGeneratedKeys="true" keyProperty="id">
            INSERT INTO reader (name, email) VALUES (#{name}, #{email})
        </insert>
        <select id="findById" resultType="Reader">
            SELECT * FROM reader WHERE id = #{id}
        </select>
        <select id="findByEmail" resultType="Reader">
            SELECT * FROM reader WHERE email = #{email}
        </select>
    </mapper>
    
  • BorrowMapper.java & BorrowMapper.xml:

    // BorrowMapper.java
    package com.example.bbs.persist.mapper;
    import com.example.bbs.persist.entity.BorrowRecord;
    import org.apache.ibatis.annotations.Param;
    import java.util.Date;
    import java.util.List;
    public interface BorrowMapper {
        int insert(BorrowRecord record);
        BorrowRecord findById(Integer id);
        int update(BorrowRecord record);
        List<BorrowRecord> findRecordsByReaderId(Integer readerId);
        List<BorrowRecord> findOverdueRecords(@Param("currentDate") Date currentDate);
    }
    
    <!-- BorrowMapper.xml -->
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.example.bbs.persist.mapper.BorrowMapper">
        
        <insert id="insert" useGeneratedKeys="true" keyProperty="id">
            INSERT INTO borrow_record (book_id, reader_id, borrow_date, status)
            VALUES (#{bookId}, #{readerId}, #{borrowDate}, #{status})
        </insert>
    
        <select id="findById" resultType="BorrowRecord">
            SELECT * FROM borrow_record WHERE id = #{id}
        </select>
    
        <update id="update" parameterType="BorrowRecord">
            UPDATE borrow_record
            <set>
                <if test="returnDate != null">return_date = #{returnDate},</if>
                <if test="status != null and status != ''">status = #{status},</if>
            </set>
            WHERE id = #{id}
        </update>
        
        <resultMap id="BorrowRecordWithDetailsResultMap" type="BorrowRecord">
            <id property="id" column="borrow_id"/>
            <result property="borrowDate" column="borrow_date"/>
            <result property="returnDate" column="return_date"/>
            <result property="status" column="status"/>
            <association property="book" javaType="Book">
                <id property="id" column="book_id"/>
                <result property="title" column="book_title"/>
                <result property="author" column="book_author"/>
            </association>
            <association property="reader" javaType="Reader">
                <id property="id" column="reader_id"/>
                <result property="name" column="reader_name"/>
            </association>
        </resultMap>
    
        <select id="findRecordsByReaderId" resultMap="BorrowRecordWithDetailsResultMap">
            SELECT
                br.id as borrow_id, br.borrow_date, br.return_date, br.status,
                b.id as book_id, b.title as book_title, b.author as book_author,
                r.id as reader_id, r.name as reader_name
            FROM borrow_record br
            JOIN book b ON br.book_id = b.id
            JOIN reader r ON br.reader_id = r.id
            WHERE br.reader_id = #{readerId}
            ORDER BY br.borrow_date DESC
        </select>
    
        <select id="findOverdueRecords" resultMap="BorrowRecordWithDetailsResultMap">
            SELECT
                br.id as borrow_id, br.borrow_date, br.return_date, br.status,
                b.id as book_id, b.title as book_title, b.author as book_author,
                r.id as reader_id, r.name as reader_name
            FROM borrow_record br
            JOIN book b ON br.book_id = b.id
            JOIN reader r ON br.reader_id = r.id
            WHERE br.status = 'BORROWED' AND DATEDIFF(#{currentDate}, br.borrow_date) > 30
        </select>
    
    </mapper>
    

第四部分:业务层与主程序开发

1. MybatisUtil.java (工具类)

【操作】: 在bbs-main/src/main/java/com/example/bbs/util/目录下创建。

package com.example.bbs.util;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;

public class MybatisUtil {
    private static SqlSessionFactory sqlSessionFactory;

    static {
        try {
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
            // 在实际应用中,这里应该有更健壮的异常处理
            throw new RuntimeException("Could not initialize SqlSessionFactory", e);
        }
    }

    public static SqlSessionFactory getSqlSessionFactory() {
        return sqlSessionFactory;
    }
}
2. BorrowService.java (核心业务)

【操作】: 在bbs-service/src/main/java/com/example/bbs/service/目录下创建。

package com.example.bbs.service;

import com.example.bbs.persist.entity.Book;
import com.example.bbs.persist.entity.BorrowRecord;
import com.example.bbs.persist.mapper.BookMapper;
import com.example.bbs.persist.mapper.BorrowMapper;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Date;
import java.util.List;

public class BorrowService {
    private static final Logger log = LoggerFactory.getLogger(BorrowService.class);
    private final SqlSessionFactory sqlSessionFactory;

    public BorrowService(SqlSessionFactory sqlSessionFactory) {
        this.sqlSessionFactory = sqlSessionFactory;
    }

    public boolean borrowBook(int readerId, int bookId) {
        log.info("开始处理借书业务:读者ID={}, 图书ID={}", readerId, bookId);
        // 使用try-with-resources确保SqlSession被关闭,手动管理事务
        try (SqlSession session = sqlSessionFactory.openSession(false)) {
            try {
                BookMapper bookMapper = session.getMapper(BookMapper.class);
                BorrowMapper borrowMapper = session.getMapper(BorrowMapper.class);

                // 1. 检查库存
                Book book = bookMapper.findById(bookId);
                if (book == null) {
                    log.error("借书失败:图书不存在。图书ID: {}", bookId);
                    return false;
                }
                if (book.getStock() <= 0) {
                    log.warn("借书失败:图书 [{}] 库存不足。", book.getTitle());
                    return false;
                }

                // 2. 扣减库存 (amount为-1)
                int updatedStockRows = bookMapper.updateStock(bookId, -1);
                if (updatedStockRows == 0) {
                    throw new RuntimeException("更新库存失败,可能在并发情况下库存已不足。");
                }
                log.debug("图书库存更新成功,图书ID: {},现有库存: {}", bookId, book.getStock() - 1);

                // 3. 创建借阅记录
                BorrowRecord record = new BorrowRecord();
                record.setBookId(bookId);
                record.setReaderId(readerId);
                record.setBorrowDate(new Date());
                record.setStatus("BORROWED");
                borrowMapper.insert(record);
                log.debug("借阅记录创建成功,新记录ID: {}", record.getId());

                // 4. 所有操作成功,提交事务
                session.commit();
                log.info("借书业务成功完成。读者ID: {}, 图书: {}", readerId, book.getTitle());
                return true;
            } catch (Exception e) {
                // 任何一步失败,回滚所有数据库操作
                session.rollback();
                log.error("借书业务发生异常,事务已回滚。", e);
                return false;
            }
        }
    }

    public boolean returnBook(int borrowRecordId) {
        log.info("开始处理还书业务:借阅记录ID={}", borrowRecordId);
        try (SqlSession session = sqlSessionFactory.openSession(false)) {
            try {
                BorrowMapper borrowMapper = session.getMapper(BorrowMapper.class);
                BookMapper bookMapper = session.getMapper(BookMapper.class);

                // 1. 查找借阅记录
                BorrowRecord record = borrowMapper.findById(borrowRecordId);
                if (record == null) {
                    log.error("还书失败:借阅记录不存在。记录ID: {}", borrowRecordId);
                    return false;
                }
                if ("RETURNED".equals(record.getStatus())) {
                    log.warn("还书失败:该书已归还。记录ID: {}", borrowRecordId);
                    return false;
                }

                // 2. 增加图书库存 (amount为+1)
                bookMapper.updateStock(record.getBookId(), 1);
                log.debug("图书库存已归还,图书ID: {}", record.getBookId());

                // 3. 更新借阅记录状态
                BorrowRecord recordToUpdate = new BorrowRecord();
                recordToUpdate.setId(borrowRecordId);
                recordToUpdate.setStatus("RETURNED");
                recordToUpdate.setReturnDate(new Date());
                borrowMapper.update(recordToUpdate);
                log.debug("借阅记录状态已更新为RETURNED,记录ID: {}", borrowRecordId);

                // 4. 提交事务
                session.commit();
                log.info("还书业务成功完成。记录ID: {}", borrowRecordId);
                return true;
            } catch (Exception e) {
                session.rollback();
                log.error("还书业务发生异常,事务已回滚。", e);
                return false;
            }
        }
    }
}
3. MainApplication.java (主程序)

【操作】: 在bbs-main/src/main/java/com/example/bbs/main/目录下创建。

package com.example.bbs.main;

import com.example.bbs.persist.entity.Book;
import com.example.bbs.persist.entity.BorrowRecord;
import com.example.bbs.persist.mapper.BookMapper;
import com.example.bbs.persist.mapper.BorrowMapper;
import com.example.bbs.service.BorrowService;
import com.example.bbs.util.MybatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;

import java.util.List;

public class MainApplication {
    public static void main(String[] args) {
        SqlSessionFactory sqlSessionFactory = MybatisUtil.getSqlSessionFactory();
        BorrowService borrowService = new BorrowService(sqlSessionFactory);

        System.out.println("\n========= 业务场景模拟开始 =========\n");

        // --- 场景一: 小明借阅 '深入理解Java虚拟机' ---
        System.out.println("--- 场景一: 小明(ID=1)借阅 '深入理解Java虚拟机'(ID=2) ---");
        borrowService.borrowBook(1, 2);

        // --- 场景二: 小红尝试借阅库存为0的 'Effective Java' ---
        System.out.println("\n--- 场景二: 小红(ID=2)尝试借阅 'Effective Java'(ID=4), 此书库存为0 ---");
        borrowService.borrowBook(2, 4);

        // --- 场景三: 小明归还他借的第一本书 ---
        // 我们需要先查到小明借的第一本书的记录ID
        try (SqlSession session = sqlSessionFactory.openSession()) {
            BorrowMapper borrowMapper = session.getMapper(BorrowMapper.class);
            List<BorrowRecord> xiaomingRecords = borrowMapper.findRecordsByReaderId(1);
            if (!xiaomingRecords.isEmpty() && "BORROWED".equals(xiaomingRecords.get(0).getStatus())) {
                int recordIdToReturn = xiaomingRecords.get(0).getId();
                System.out.printf("\n--- 场景三: 小明归还图书, 借阅记录ID: %d ---\n", recordIdToReturn);
                borrowService.returnBook(recordIdToReturn);
            }
        }
        
        // --- 场景四: 动态查询图书 ---
        System.out.println("\n--- 场景四: 动态查询书名包含'入门', 作者是'项目导师'的图书 ---");
        try (SqlSession session = sqlSessionFactory.openSession()) {
            BookMapper bookMapper = session.getMapper(BookMapper.class);
            Book queryCondition = new Book();
            queryCondition.setTitle("入门");
            queryCondition.setAuthor("项目导师");
            List<Book> books = bookMapper.findByCondition(queryCondition);
            System.out.println("查询结果:");
            books.forEach(System.out::println);
        }

        System.out.println("\n========= 业务场景模拟结束 =========\n");
    }
}

第五部分:项目构建与运行

【操作】:

  1. 构建: 在IDEA的Maven面板中,找到父项目BookBorrowSys,展开Lifecycle,双击clean,然后再双击install。或者在项目根目录打开终端,执行 mvn clean install
  2. 运行: 找到bbs-main模块下的MainApplication.java,右键点击,选择 Run 'MainApplication.main()'
  3. 观察控制台: 查看控制台打印的日志和输出,验证业务流程是否按预期执行。你将能清晰地看到Mybatis打印的SQL语句、SLF4J输出的业务日志以及最终的业务结果。

总结

至此,你已拥有了一份从数据库到最终应用程序的完整、可运行的项目代码和操作指南。这个项目不仅仅是代码的堆砌,它是一个有机的整体,展现了:

  • 分层架构的思想。
  • Maven如何组织和管理复杂项目。
  • Mybatis如何处理各种数据库操作,特别是强大的动态SQL和关联查询。
  • 事务在核心业务中的重要性。
  • 日志如何帮助我们追踪和调试程序。

请务必亲手完成这个项目,它将是你技术成长道路上一个坚实的里程碑。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值