---
url: /diary/spring/springboot4-mybatis-flex/index.md
description: >-
  MyBatis-Flex 在 Spring Boot 4 中的集成配置与核心特性实战，包括 Spring Boot 4 专用
  Starter、QueryWrapper、代码生成器、多数据源等功能。
---
## MyBatis-Flex 简介

MyBatis-Flex 是一个 MyBatis 增强框架，类比 MyBatis-Plus，但更轻量、性能更高。核心特点：

* **零第三方依赖**：除 MyBatis 本身外无任何第三方依赖
* **QueryWrapper**：类型安全的查询构建器，支持 Lambda 表达式
* **高性能**：查询构建无反射开销，基础 CRUD 性能接近手写 JDBC
* **代码生成器**：支持生成 Entity、Mapper、Service、Controller
* **功能丰富**：数据脱敏、逻辑删除、乐观锁、数据审计、数据填充、多数据源、ActiveRecord、关联查询

### MyBatis-Flex vs MyBatis-Plus

| 对比项 | MyBatis-Plus | MyBatis-Flex |
|---|---|---|
| 第三方依赖 | 较多 | **零依赖** |
| QueryWrapper | 字符串列名，无编译检查 | **Lambda 类型安全** |
| 性能 | 有反射开销 | **接近原生 JDBC** |
| 多主键支持 | 有限 | **灵活（自增、雪花、UUID等）** |
| Spring Boot 4 支持 | 暂无专用 starter | **`mybatis-flex-spring-boot4-starter`** |

## Spring Boot 4 专用 Starter

MyBatis-Flex 从 **v1.11.5**（2025-12-24）开始提供 Spring Boot 4 专用 starter，当前最新版为 **v1.11.6**。

### 依赖配置

```xml
<!-- Spring Boot 4 专用 starter（注意：不是 mybatis-flex-spring-boot-starter） -->
<dependency>
    <groupId>com.mybatis-flex</groupId>
    <artifactId>mybatis-flex-spring-boot4-starter</artifactId>
    <version>1.11.6</version>
</dependency>

<!-- 数据库驱动（以 MySQL 为例） -->
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <scope>runtime</scope>
</dependency>

<!-- 连接池（Spring Boot 默认 HikariCP） -->
<dependency>
    <groupId>com.zaxxer</groupId>
    <artifactId>HikariCP</artifactId>
</dependency>

<!-- 代码生成器（可选） -->
<dependency>
    <groupId>com.mybatis-flex</groupId>
    <artifactId>mybatis-flex-codegen</artifactId>
    <version>1.11.6</version>
</dependency>
```

> **重要**：Spring Boot 4 项目必须使用 `mybatis-flex-spring-boot4-starter`，而不是 `mybatis-flex-spring-boot-starter`。后者只支持 Spring Boot 3.x。

### application.yml 配置

```yaml
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
    username: root
    password: password
    driver-class-name: com.mysql.cj.jdbc.Driver

mybatis-flex:
  # Mapper XML 文件位置
  mapper-locations: classpath*:/mapper/**/*.xml
  # 实体类扫描路径
  type-packages: com.example.entity
  # 配置
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  # 全局配置
  global-config:
    # 逻辑删除配置
    logic-delete-column: is_deleted
    logic-delete-value: 1
    logic-not-delete-value: 0
    # 关键字列
    key-config:
      key-type: auto
```

## 核心功能实战

### Entity 定义

```java
@Table("tb_account")
public class Account {

    @Id(keyType = KeyType.Auto)  // 自增主键
    private Long id;

    private String userName;

    private Integer age;

    private Date birthday;

    @Column(isLogicDelete = true)  // 逻辑删除字段
    private Boolean isDeleted;

    @Column(onUpdateValue = "now()")  // 自动填充更新时间
    private Date updateTime;

    @Column(onInsertValue = "now()")  // 自动填充创建时间
    private Date createTime;

    // getter/setter ...
}
```

### Mapper 接口

```java
public interface AccountMapper extends BaseMapper<Account> {
    // BaseMapper 已提供常用 CRUD 方法
    // 如需自定义 SQL，可在此添加并在 XML 中实现
}
```

### 查询操作（QueryWrapper）

MyBatis-Flex 的 `QueryWrapper` 是类型安全的，使用 Lambda 表达式引用列名：

```java
// 基础查询
QueryWrapper query = QueryWrapper.create()
    .select()
    .from(ACCOUNT)
    .where(ACCOUNT.AGE.ge(18))
    .and(ACCOUNT.USER_NAME.like("张"))
    .orderBy(ACCOUNT.ID.desc());

List<Account> accounts = accountMapper.selectListByQuery(query);

// 等价 SQL：
// SELECT * FROM tb_account
// WHERE age >= 18 AND user_name LIKE '%张%'
// ORDER BY id DESC
```

```java
// 条件构造（动态条件）
QueryWrapper query = QueryWrapper.create()
    .select(ACCOUNT.ID, ACCOUNT.USER_NAME)
    .from(ACCOUNT)
    .where(ACCOUNT.AGE.ge(18))
    .and(age != null ? ACCOUNT.AGE.le(age) : noCondition())
    .and(userName != null ? ACCOUNT.USER_NAME.like(userName) : noCondition());
```

### CRUD 操作

```java
@Service
public class AccountService {

    @Autowired
    private AccountMapper accountMapper;

    // 新增
    public void add(Account account) {
        accountMapper.insert(account);
    }

    // 批量新增
    public void batchAdd(List<Account> accounts) {
        accountMapper.insertBatch(accounts);
    }

    // 根据ID查询
    public Account getById(Long id) {
        return accountMapper.selectOneById(id);
    }

    // 更新
    public void update(Account account) {
        accountMapper.update(account);
    }

    // 根据条件更新
    public void updateByCondition() {
        Account account = new Account();
        account.setAge(20);
        accountMapper.updateByCondition(account,
            QueryWrapper.create().where(ACCOUNT.USER_NAME.like("test")));
    }

    // 删除
    public void deleteById(Long id) {
        accountMapper.deleteById(id);  // 逻辑删除会自动处理
    }

    // 分页查询
    public Page<Account> page(int pageNum, int pageSize) {
        QueryWrapper query = QueryWrapper.create()
            .from(ACCOUNT)
            .orderBy(ACCOUNT.ID.desc());
        return accountMapper.paginate(query, pageNum, pageSize);
    }
}
```

### 关联查询

```java
@Table("tb_account")
public class Account {

    @Id(keyType = KeyType.Auto)
    private Long id;
    private String userName;

    // 一对一关联
    @RelationOneToOne(selfField = "id", targetField = "accountId")
    private AccountDetail detail;

    // 一对多关联
    @RelationOneToMany(selfField = "id", targetField = "accountId")
    private List<Order> orders;

    // 多对多关联
    @RelationManyToMany(
        selfField = "id",
        targetField = "id",
        joinTable = "tb_account_role",
        joinSelfColumn = "account_id",
        joinTargetColumn = "role_id"
    )
    private List<Role> roles;
}
```

使用关联查询：

```java
// 查询时自动加载关联数据
List<Account> accounts = accountMapper.selectListWithRelationsByQuery(
    QueryWrapper.create().where(ACCOUNT.AGE.ge(18))
);
```

### ActiveRecord 模式

让 Entity 继承 `Model` 即可使用：

```java
@Table("tb_account")
public class Account extends Model<Account> {

    @Id(keyType = KeyType.Auto)
    private Long id;
    private String userName;
    private Integer age;

    // getter/setter ...
}

// 直接使用 Entity 操作
Account account = new Account();
account.setUserName("test");
account.setAge(20);
account.save();          // 新增

account = Account.getById(1L);  // 查询
account.setAge(21);
account.updateById();    // 更新
account.deleteById();    // 删除
```

## 多数据源配置

```java
@Configuration
public class DataSourceConfig {

    @Bean
    @Primary
    public DataSource masterDataSource() {
        HikariDataSource ds = new HikariDataSource();
        ds.setJdbcUrl("jdbc:mysql://localhost:3306/master_db");
        ds.setUsername("root");
        ds.setPassword("password");
        return ds;
    }

    @Bean
    public DataSource slaveDataSource() {
        HikariDataSource ds = new HikariDataSource();
        ds.setJdbcUrl("jdbc:mysql://localhost:3306/slave_db");
        ds.setUsername("root");
        ds.setPassword("password");
        return ds;
    }
}
```

使用 `@DS` 注解切换数据源：

```java
@DS("slave")
public List<Account> getFromSlave() {
    return accountMapper.selectAll();
}
```

## 数据脱敏

```java
@Table("tb_account")
public class Account {

    @Id(keyType = KeyType.Auto)
    private Long id;

    // 手机号脱敏：138****1234
    @Column(typeHandler = DesensitizeTypeHandler.class)
    @Desensitize(type = DesensitizeType.MOBILE)
    private String phone;

    // 邮箱脱敏：t***@example.com
    @Column(typeHandler = DesensitizeTypeHandler.class)
    @Desensitize(type = DesensitizeType.EMAIL)
    private String email;

    // 身份证脱敏
    @Column(typeHandler = DesensitizeTypeHandler.class)
    @Desensitize(type = DesensitizeType.ID_CARD)
    private String idCard;
}
```

## 代码生成器

```java
public class CodeGenerator {

    public static void main(String[] args) {
        Generator generator = Generator.create(
            // 数据源配置
            new JdbcMySQLDataSource(
                "jdbc:mysql://localhost:3306/mydb",
                "root", "password"
            )
        );

        // 生成配置
        GlobalConfig config = generator.getGlobalConfig();
        config.setBasePackage("com.example");
        config.setSourceDir(System.getProperty("user.dir") + "/src/main/java");
        config.setMapperXmlPath(System.getProperty("user.dir") + "/src/main/resources/mapper");
        config.setEntityWithLombok(true);
        config.setEntityJdk8Time(true);

        // 执行生成
        generator.generate();
    }
}
```

## 迁移检查清单

* \[ ] `mybatis-flex-spring-boot-starter` → `mybatis-flex-spring-boot4-starter`
* \[ ] 版本 >= 1.11.5（推荐 1.11.6+）
* \[ ] MyBatis-Flex 本身的 API 无变化，无需改动业务代码
* \[ ] `QueryWrapper` 用法完全一致
* \[ ] Entity、Mapper、Service 层代码无需改动
* \[ ] 如果项目同时使用了 MyBatis-Plus，需要评估是否迁移到 MyBatis-Flex（两者不兼容）

***

> 相关文章：
>
> * [Spring Boot 4 概览与迁移指南](./01.SpringBoot4概览与迁移指南.md)
> * [Jackson 配置与迁移](./02.Jackson配置与迁移.md)
> * [Redis 集成使用](./03.Redis集成使用.md)
> * [其他核心变更](./05.其他核心变更.md)
