Ohhnews

分类导航

$ cd ..
Baeldung原文

MyBatis-Flex 开发指南

#mybatis-flex#java#spring boot#数据持久化#数据库开发

1. 概述

MyBatis-Flex 通过为常见的数据访问任务提供更便捷的编程模型,扩展了 MyBatis 生态系统

在本教程中,我们将构建一个小型 Spring Boot 4 应用程序,以探索 MyBatis-Flex 的核心工作流程,从项目设置到数据访问代码。具体来说,我们将定义一个简单的实体类和映射器(Mapper),执行基本的 CRUD 操作,使用 QueryWrapper 创建过滤查询,并对结果进行分页。

本文中的示例需要 Java 17 或更高版本。

2. 设置最小化 Spring Boot 项目

Spring Boot 简化了 MyBatis-Flex 的设置过程,使我们能够更专注于映射器和查询 API。

2.1. 添加依赖

尽管 MyBatis-Flex 消除了通常与直接 JDBC 访问相关的许多样板代码,并且我们可以将其作为 JPA 或 Hibernate 的替代方案,但它仍然依赖于 Spring JDBC 基础架构来进行数据源配置和管理。

MyBatis-Flex 支持超过 40 种不同的数据库类型,包括 MySQL、PostgreSQL、Oracle、SQL Server 和 SQLite。在本例中,我们使用 H2 作为内存数据库,因为它有助于创建快速、隔离且可重复的测试。

为此,我们将 MyBatis-Flex、Spring JDBC、H2 和 Spring Boot Test Starter 依赖项添加到 Spring Boot 4 的 pom.xml 中:

$ xml
<dependency>
    <groupId>com.mybatis-flex</groupId>
    <artifactId>mybatis-flex-spring-boot4-starter</artifactId>
    <version>1.11.6</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
    <version>4.0.3</version>
</dependency>
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <version>2.4.240</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <version>4.0.3</version>
    <scope>test</scope>
</dependency>

在继续之前,建议查看 Maven Repository 以获取更新的版本。

2.2. 配置数据源 (H2)

接下来,我们在 src/main/resources/application.yml 中配置数据源:

$ config
spring:
    datasource:
        driver-class-name: org.h2.Driver
        url: jdbc:h2:mem:mybatisflex
        username: sa
        password:
    sql:
        init:
            mode: always
            schema-locations: classpath:db/schema-h2.sql
            data-locations: classpath:db/data-h2.sql

URL jdbc:h2:mem:mybatisflex 在应用程序上下文中创建了一个内存数据库。用户名为 sa 且密码为空是 H2 的默认设置。

spring.sql.init 部分指示 Spring Boot 使用 SQL 脚本初始化数据源。在本例中,它从 src/main/resources/db/schema-h2.sql 加载架构,并从 src/main/resources/db/data-h2.sql 加载示例数据。

2.3. 启用 Mapper 扫描

最后,在主 Spring Boot 应用程序类中启用 Mapper 扫描。在 MyBatis-Flex 中,Mapper 是将 Java 代码与实体数据库操作连接起来的组件

$ java
@SpringBootApplication
@MapperScan("com.baeldung.mybatisflex.mapper")
public class MyBatisFlexApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyBatisFlexApplication.class, args);
    }
}

启用 Mapper 扫描后,Spring 可以检测到 AccountMapper,为其创建一个 Bean,并允许我们将其注入到测试中。

3. 创建实体和 Mapper

项目配置完成后,让我们定义领域模型和 MyBatis-Flex 用于持久化操作的 Mapper。

3.1. 使用注解定义简单实体

首先,我们可以从映射到 tb_account 表的简单 Account 实体开始。MyBatis-Flex 使用注解将类与表关联,并自定义字段如何映射到数据库列

$ java
@Table("tb_account")
public class Account {
    @Id(keyType = KeyType.Auto)
    private Long id;
    @Column("user_name")
    private String userName;
    private Integer age;
    private String status;
    @Column("created_at")
    private LocalDateTime createdAt;
    // getters and setters
}

这里,@Table 将类绑定到 tb_account 表,而 @Id 标记主键并将其配置为自动生成的值。值得注意的是,对于 Java 字段名与列名不直接匹配的情况(如 userNamecreatedAt),我们也使用了 @Column。其余字段遵循默认命名约定,因此无需额外注解。

3.2. 创建继承 BaseMapper<T> 的 Mapper 接口

接下来,定义 Account 的 Mapper 接口。在 MyBatis-Flex 中,Mapper 是暴露实体数据访问操作的组件:

$ java
@Mapper
public interface AccountMapper extends BaseMapper<Account> {
}

通过继承 BaseMapper<Account>AccountMapper 继承了一套丰富的内置操作,用于插入、更新、删除和查询。这保持了接口的简洁性,同时提供了我们在本文示例中所需的一切。

3.3. 初始化架构和测试数据

在编写针对数据库的任何 Java 代码之前,先添加一个小型的架构和几行示例数据。我们将两个脚本放在 src/main/resources/db 下,以便 Spring Boot 在启动时自动加载它们。

首先,创建 schema-h2.sql

$ query
CREATE TABLE IF NOT EXISTS tb_account (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    user_name VARCHAR(100) NOT NULL,
    age INT,
    status VARCHAR(20),
    created_at TIMESTAMP
);

接下来是 data-h2.sql

$ query
INSERT INTO tb_account (user_name, age, status, created_at)
VALUES ('sarah', 35, 'ACTIVE', TIMESTAMP '2024-01-15 10:00:00');
INSERT INTO tb_account (user_name, age, status, created_at)
VALUES ('mike', 17, 'INACTIVE', TIMESTAMP '2024-02-01 09:30:00');
INSERT INTO tb_account (user_name, age, status, created_at)
VALUES ('emma', 42, 'ACTIVE', TIMESTAMP '2024-03-10 14:15:00');
INSERT INTO tb_account (user_name, age, status, created_at)
VALUES ('tom', 20, 'ACTIVE', TIMESTAMP '2024-04-05 08:45:00');

示例数据为集成测试提供了可预测的数据集。由于行在年龄、状态和创建时间上有所不同,我们可以在多个示例中重复使用它们,而无需在每个测试中添加额外的设置代码。

4. 基本 CRUD 操作

现在我们有了基础模型,让我们看看在实际应用程序中最先执行的数据操作:插入、读取、更新和删除。

首先,将 Mapper 直接注入测试类:

$ java
@SpringBootTest
@Transactional
public class MyBatisFlexIntegrationTest {
    @Autowired
    private AccountMapper accountMapper;
    // tests
}

此时,我们可以开始添加测试了。

4.1. 插入并根据 ID 检索

尝试一个简单的持久化流程:

$ java
@Test
public void whenInsertAndSelectById_thenAccountIsPersisted() {
    Account account = new Account();
    account.setUserName("olivia");
    account.setAge(28);
    account.setStatus("ACTIVE");
    account.setCreatedAt(LocalDateTime.of(2024, 5, 1, 12, 0));
    accountMapper.insert(account);
    Account persistedAccount = accountMapper.selectOneById(account.getId());
    assertNotNull(account.getId());
    assertNotNull(persistedAccount);
    assertEquals("olivia", persistedAccount.getUserName());
}

这里,我们创建了一个 Account,通过 Mapper 插入它,然后根据生成的 id 检索它,以验证记录是否正确存储。

4.2. 更新现有记录

更新现有记录同样简单。首先加载一个实体,修改其中一个字段,最后将其传回 Mapper 以持久化更改:

$ java
@Test
public void whenUpdatingAnAccount_thenTheNewStatusIsStored() {
    Account account = accountMapper.selectOneById(1L);
    account.setStatus("INACTIVE");
    accountMapper.update(account);
    Account updatedAccount = accountMapper.selectOneById(1L);
    assertEquals("INACTIVE", updatedAccount.getStatus());
}

再次读取同一行数据,确认新值已按预期写入数据库。

4.3. 删除记录

此测试确认 deleteById() 删除了记录,并且后续的查询会返回 null

$ java
@Test
public void whenDeleteById_thenAccountIsRemoved() {
    accountMapper.deleteById(2L);
    Account deletedAccount = accountMapper.selectOneById(2L);
    assertNull(deletedAccount);
}

但在实际操作中,许多应用程序会避免对业务数据进行物理删除,而是倾向于软删除策略,以便记录仍可用于审计或恢复。

5. 使用 QueryWrapper 编写查询

到目前为止,我们已经使用了 Mapper 进行直接的 CRUD 操作。为了实现更具表现力的读取,MyBatis-Flex 提供了 QueryWrapper,这是一个用于在 Java 代码中构建 SQL 条件的流畅 API

5.1. 多条件查询与排序

首先从结合过滤和排序的查询开始。这是应用程序代码中的常见模式,即使涉及多个条件,QueryWrapper 也能保持良好的可读性:

$ java
@Test
public void whenQueryWithFilters_thenMatchingAccountsAreReturned() {
    QueryWrapper queryWrapper = QueryWrapper.create()
      .where(Account::getAge).ge(18)
      .and(Account::getStatus).eq("ACTIVE")
      .orderBy(column("age").desc());
    List<Account> accounts = accountMapper.selectListByQuery(queryWrapper);
    assertEquals(3, accounts.size());
    assertEquals("emma", accounts.get(0).getUserName());
    assertEquals("sarah", accounts.get(1).getUserName());
    assertEquals("tom", accounts.get(2).getUserName());
}

在本例中,我们仅选择了成年且状态为“ACTIVE”的账户,并按年龄降序排列。结果断言明确了排序逻辑,帮助我们验证生成的查询是否符合预期。

5.2. 动态查询

在许多实际搜索场景中,某些过滤器是可选的。与其创建多个查询变体,不如逐步构建 QueryWrapper,仅在存在相应参数时添加每个条件

$ java
@Test
public void whenBuildingADynamicQuery_thenOnlyActiveAdultAccountsAreReturned() {
    Integer minAge = 18;
    String status = "ACTIVE";
    QueryWrapper queryWrapper = QueryWrapper.create();
    if (minAge != null) {
        queryWrapper.where(Account::getAge).ge(minAge);
    }
    if (status != null) {
        queryWrapper.and(Account::getStatus).eq(status);
    }
    queryWrapper.orderBy(column("id").asc());
    List<Account> accounts = accountMapper.selectListByQuery(queryWrapper);
    assertEquals(3, accounts.size());
    assertEquals("sarah", accounts.get(0).getUserName());
    assertEquals("emma", accounts.get(1).getUserName());
    assertEquals("tom", accounts.get(2).getUserName());
}

这种方法在不牺牲清晰度的前提下,保持了查询逻辑的灵活性。

5.3. 执行分页查询

分页是将大型结果集拆分为较小的块(或页面)的常用方法,而不是一次性加载所有匹配行。MyBatis-Flex 通过 Mapper 直接支持分页,因此我们可以请求特定的页面和页面大小,而无需引入单独的分页抽象:

$ java
@Test
public void whenPaginating_thenPageMetadataAndRecordsAreReturned() {
    QueryWrapper queryWrapper = QueryWrapper.create()
      .where(Account::getAge).ge(18)
      .orderBy(column("id").asc());
    Page<Account> page = accountMapper.paginate(1, 2, queryWrapper);
    assertEquals(2, page.getRecords().size());
    assertEquals(3L, page.getTotalRow());
    assertEquals(2L, page.getTotalPage());
}

上面我们对过滤后的结果进行了分页,并验证了返回的记录和页面元数据。这一点非常有用,因为分页不仅仅是限制行数。通常,我们还需要匹配记录的总数和页面总数来构建周围的应用程序逻辑。

6. 结论

在本文中,我们构建了一个使用 MyBatis-Flex 的小型 Spring Boot 4 项目,并以此演示了核心的持久化工作流程。我们配置了 H2 数据源,定义了实体和 Mapper,并使用 JUnit 5 集成测试验证了设置。

此外,我们看到了 MyBatis-Flex 如何支持简单和更具表现力的数据访问模式。具体来说,我们使用了 BaseMapper 的内置 CRUD 操作,通过 QueryWrapper 创建了过滤查询,并运行了分页查询,同时检查了返回的记录和页面元数据。

一如既往,本文的完整代码可在 GitHub 上找到。