MyBatis-Flex 实战:极简 CRUD + 高性能分页,吊打传统 MyBatis 的新一代持久层框架

简介: MyBatis-Flex作为新一代Java持久层框架,在MyBatis的灵活性和MyBatis-Plus的便捷性之间实现了完美平衡。本文详细介绍了MyBatis-Flex的环境搭建、核心特性和实战应用,包括:1. 5分钟快速初始化项目配置;2. 通过注解实现零XML的CRUD操作;3. 灵活的QueryWrapper动态SQL构建;4. 高效分页查询实现;5. 关联查询解决方案;6. 编程式事务管理。相比MyBatis-Plus,MyBatis-Flex具有更简洁的API、更高的性能和更低的学习成本。

MyBatis-Flex实战:极简CRUD+高性能分页,吊打传统MyBatis的新一代持久层框架

在Java持久层框架生态中,MyBatis以灵活的SQL控制占据半壁江山,但原生配置繁琐;MyBatis-Plus简化了CRUD却在复杂查询场景下略显厚重。而MyBatis-Flex的出现,完美平衡了"灵活性"与"简洁性",兼具MyBatis的SQL自由度和MyBatis-Plus的CRUD便捷性,更支持动态表名、多租户、数据脱敏等企业级特性,性能更优。

一、环境搭建:5分钟初始化MyBatis-Flex项目

1.1 核心依赖(Maven)

选用最新稳定版本,适配Spring Boot 3.2.5(当前最新稳定版),核心依赖如下,包含MyBatis-Flex、数据源、Lombok、Swagger3等必需组件:

<parent>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-parent</artifactId>
   <version>3.2.5</version>
   <relativePath/>
</parent>

<dependencies>
   <!-- Spring Boot核心 -->
   <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-web</artifactId>
   </dependency>
   <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-jdbc</artifactId>
   </dependency>

   <!-- MyBatis-Flex核心依赖 -->
   <dependency>
       <groupId>com.mybatis-flex</groupId>
       <artifactId>mybatis-flex-spring-boot-starter</artifactId>
       <version>1.9.6</version> <!-- 最新稳定版 -->
   </dependency>

   <!-- MyBatis-Plus(持久层辅助) -->
   <dependency>
       <groupId>com.baomidou</groupId>
       <artifactId>mybatis-plus-boot-starter</artifactId>
       <version>3.5.5</version> <!-- 最新稳定版 -->
   </dependency>

   <!-- MySQL驱动 -->
   <dependency>
       <groupId>com.mysql</groupId>
       <artifactId>mysql-connector-j</artifactId>
       <version>8.3.0</version>
       <scope>runtime</scope>
   </dependency>

   <!-- Lombok -->
   <dependency>
       <groupId>org.projectlombok</groupId>
       <artifactId>lombok</artifactId>
       <version>1.18.30</version>
       <scope>provided</scope>
   </dependency>

   <!-- Swagger3 -->
   <dependency>
       <groupId>org.springdoc</groupId>
       <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
       <version>2.2.20</version>
   </dependency>

   <!-- FastJSON2 -->
   <dependency>
       <groupId>com.alibaba.fastjson2</groupId>
       <artifactId>fastjson2</artifactId>
       <version>2.0.47</version>
   </dependency>

   <!-- Google Guava集合工具 -->
   <dependency>
       <groupId>com.google.guava</groupId>
       <artifactId>guava</artifactId>
       <version>33.2.1-jre</version>
   </dependency>

   <!-- 单元测试 -->
   <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-test</artifactId>
       <scope>test</scope>
   </dependency>
</dependencies>

<build>
   <plugins>
       <plugin>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-maven-plugin</artifactId>
           <version>3.2.5</version>
           <configuration>
               <excludes>
                   <exclude>
                       <groupId>org.projectlombok</groupId>
                       <artifactId>lombok</artifactId>
                   </exclude>
               </excludes>
           </configuration>
       </plugin>
       <!-- JDK17编译插件 -->
       <plugin>
           <groupId>org.apache.maven.plugins</groupId>
           <artifactId>maven-compiler-plugin</artifactId>
           <version>3.11.0</version>
           <configuration>
               <source>17</source>
               <target>17</target>
               <encoding>UTF-8</encoding>
           </configuration>
       </plugin>
   </plugins>
</build>

1.2 核心配置(application.yml)

配置数据源、MyBatis-Flex映射路径、Swagger3等,确保配置简洁且符合生产规范:

spring:
 # 数据源配置
 datasource:
   url: jdbc:mysql://localhost:3306/mybatis_flex_demo?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&rewriteBatchedStatements=true
   username: root
   password: root
   driver-class-name: com.mysql.cj.jdbc.Driver

# MyBatis-Flex配置
mybatis-flex:
 # mapper.xml文件路径
 mapper-locations: classpath:mapper/*.xml
 # 实体类别名包
 type-aliases-package: com.jam.demo.entity
 # 全局配置
 global-config:
   db-config:
     # 主键生成策略(MySQL自增)
     id-type: auto
     # 表名前缀(若有)
     table-prefix: t_
   # 日志配置(打印SQL)
   log:
     enable: true
     level: debug

# MyBatis-Plus配置(辅助分页)
mybatis-plus:
 mapper-locations: classpath:mapper/*.xml
 type-aliases-package: com.jam.demo.entity
 configuration:
   map-underscore-to-camel-case: true
   log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

# Swagger3配置
springdoc:
 api-docs:
   path: /api-docs
 swagger-ui:
   path: /swagger-ui.html
   operationsSorter: method
 packages-to-scan: com.jam.demo.controller
 model-packages: com.jam.demo.entity

1.3 启动类配置

添加MyBatis-Flex和MyBatis-Plus的 mapper 扫描注解,开启Swagger3:

package com.jam.demo;

import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.info.Info;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
* 应用启动类
*
* @author ken
*/

@SpringBootApplication
@MapperScan({"com.jam.demo.mapper"}) // 扫描mapper接口
@OpenAPIDefinition(
       info = @Info(
               title = "MyBatis-Flex实战API",
               description = "基于MyBatis-Flex的CRUD、分页、事务等核心功能实战接口",
               version = "1.0.0"
       )
)
public class MyBatisFlexDemoApplication {

   public static void main(String[] args) {
       SpringApplication.run(MyBatisFlexDemoApplication.class, args);
   }

}

二、核心概念速通:MyBatis-Flex的核心优势

MyBatis-Flex是基于MyBatis开发的增强工具,并非替代MyBatis,而是通过"零XML配置"、"动态SQL生成"、"链式查询"等特性简化开发,核心优势如下:

  1. 极简CRUD:无需编写XML和Mapper方法,通过BaseMapper和Entity即可完成单表CRUD;
  2. 灵活的动态SQL:通过QueryWrapper链式构建SQL,支持复杂条件查询,比MyBatis-Plus更简洁;
  3. 高性能:内置SQL优化机制,支持批量操作、分页优化,性能优于传统MyBatis;
  4. 轻量无侵入:依赖极少,可与MyBatis、MyBatis-Plus共存,迁移成本低;
  5. 企业级特性:原生支持多租户、数据脱敏、动态表名、逻辑删除等功能。

核心执行流程如下:

image.png

三、实战核心功能:从单表CRUD到分页查询

3.1 准备数据库表(MySQL 8.0)

创建用户表t_user,包含基础字段,SQL可直接执行:

CREATE TABLE `t_user` (
 `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
 `username` varchar(50) NOT NULL COMMENT '用户名',
 `password` varchar(100) NOT NULL COMMENT '密码(加密后)',
 `nickname` varchar(50) DEFAULT NULL COMMENT '昵称',
 `age` int DEFAULT NULL COMMENT '年龄',
 `email` varchar(100) DEFAULT NULL COMMENT '邮箱',
 `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
 `is_deleted` tinyint NOT NULL DEFAULT '0' COMMENT '逻辑删除(0-未删,1-已删)',
 PRIMARY KEY (`id`),
 UNIQUE KEY `uk_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

3.2 实体类定义(Entity)

使用MyBatis-Flex注解映射表结构,结合Swagger3注解,字段命名遵循驼峰命名法:

package com.jam.demo.entity;

import com.mybatis.flex.core.BaseEntity;
import com.mybatis.flex.annotation.*;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;

import java.time.LocalDateTime;

/**
* 用户实体类
*
* @author ken
*/

@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "用户实体")
@Table(value = "t_user", comment = "用户表")
public class User extends BaseEntity {

   /**
    * 主键ID
    */

   @Schema(description = "主键ID")
   @Id(keyType = KeyType.AUTO)
   private Long id;

   /**
    * 用户名
    */

   @Schema(description = "用户名", requiredMode = Schema.RequiredMode.REQUIRED)
   @Column(comment = "用户名", isUnique = true, isNullable = false)
   private String username;

   /**
    * 密码(加密后)
    */

   @Schema(description = "密码(加密后)", requiredMode = Schema.RequiredMode.REQUIRED)
   @Column(comment = "密码(加密后)", isNullable = false)
   private String password;

   /**
    * 昵称
    */

   @Schema(description = "昵称")
   @Column(comment = "昵称")
   private String nickname;

   /**
    * 年龄
    */

   @Schema(description = "年龄")
   @Column(comment = "年龄")
   private Integer age;

   /**
    * 邮箱
    */

   @Schema(description = "邮箱")
   @Column(comment = "邮箱")
   private String email;

   /**
    * 创建时间
    */

   @Schema(description = "创建时间")
   @Column(comment = "创建时间", isInsertFill = true)
   @InsertFill(InsertFillStrategy.NOW)
   private LocalDateTime createTime;

   /**
    * 更新时间
    */

   @Schema(description = "更新时间")
   @Column(comment = "更新时间", isInsertFill = true, isUpdateFill = true)
   @InsertFill(InsertFillStrategy.NOW)
   @UpdateFill(UpdateFillStrategy.NOW)
   private LocalDateTime updateTime;

   /**
    * 逻辑删除(0-未删,1-已删)
    */

   @Schema(description = "逻辑删除(0-未删,1-已删)")
   @Column(comment = "逻辑删除(0-未删,1-已删)")
   @LogicDelete
   private Integer isDeleted;
}

说明:

  • 继承BaseEntity可获得默认的CRUD方法支持;
  • @Table指定表名和注释,@Column映射字段属性,@Id指定主键;
  • @InsertFill@UpdateFill实现创建时间、更新时间的自动填充;
  • @LogicDelete开启逻辑删除,默认值0为未删除。

3.3 Mapper接口定义

无需编写方法,直接继承MyBatis-Flex的BaseMapper即可:

package com.jam.demo.mapper;

import com.mybatis.flex.core.BaseMapper;
import com.jam.demo.entity.User;
import org.springframework.stereotype.Repository;

/**
* 用户Mapper接口
*
* @author ken
*/

@Repository
public interface UserMapper extends BaseMapper<User> {
}

3.4 Service层实现(含事务处理)

采用编程式事务,实现用户的增删改查核心功能,结合日志打印:

package com.jam.demo.service;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.collect.Lists;
import com.mybatis.flex.core.query.QueryWrapper;
import com.jam.demo.entity.User;
import com.jam.demo.mapper.UserMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import java.util.List;

import static com.jam.demo.entity.table.UserTableDef.USER;

/**
* 用户服务实现类
*
* @author ken
*/

@Slf4j
@Service
public class UserService {

   private final UserMapper userMapper;
   private final PlatformTransactionManager transactionManager;

   @Autowired
   public UserService(UserMapper userMapper, PlatformTransactionManager transactionManager) {
       this.userMapper = userMapper;
       this.transactionManager = transactionManager;
   }

   /**
    * 新增用户
    *
    * @param user 用户实体
    * @return 新增成功的用户ID
    */

   public Long addUser(User user) {
       // 参数校验
       validateUser(user, true);
       log.info("新增用户:{}", user.getUsername());
       
       // 编程式事务
       TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
       try {
           userMapper.insert(user);
           transactionManager.commit(status);
           log.info("新增用户成功,用户ID:{}", user.getId());
           return user.getId();
       } catch (Exception e) {
           transactionManager.rollback(status);
           log.error("新增用户失败", e);
           throw new RuntimeException("新增用户失败", e);
       }
   }

   /**
    * 根据ID删除用户(逻辑删除)
    *
    * @param id 用户ID
    * @return 删除成功标识
    */

   public boolean deleteUserById(Long id) {
       if (ObjectUtils.isEmpty(id)) {
           throw new IllegalArgumentException("用户ID不能为空");
       }
       log.info("删除用户,用户ID:{}", id);
       
       TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
       try {
           int rows = userMapper.deleteById(id);
           transactionManager.commit(status);
           boolean success = rows > 0;
           log.info("删除用户{},用户ID:{}", success ? "成功" : "失败", id);
           return success;
       } catch (Exception e) {
           transactionManager.rollback(status);
           log.error("删除用户失败", e);
           throw new RuntimeException("删除用户失败", e);
       }
   }

   /**
    * 更新用户信息
    *
    * @param user 用户实体(含ID)
    * @return 更新成功标识
    */

   public boolean updateUser(User user) {
       // 参数校验
       validateUser(user, false);
       if (ObjectUtils.isEmpty(user.getId())) {
           throw new IllegalArgumentException("用户ID不能为空");
       }
       log.info("更新用户,用户ID:{}", user.getId());
       
       TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
       try {
           int rows = userMapper.updateById(user);
           transactionManager.commit(status);
           boolean success = rows > 0;
           log.info("更新用户{},用户ID:{}", success ? "成功" : "失败", user.getId());
           return success;
       } catch (Exception e) {
           transactionManager.rollback(status);
           log.error("更新用户失败", e);
           throw new RuntimeException("更新用户失败", e);
       }
   }

   /**
    * 根据ID查询用户
    *
    * @param id 用户ID
    * @return 用户实体
    */

   public User getUserById(Long id) {
       if (ObjectUtils.isEmpty(id)) {
           throw new IllegalArgumentException("用户ID不能为空");
       }
       log.info("查询用户,用户ID:{}", id);
       return userMapper.selectById(id);
   }

   /**
    * 条件查询用户列表
    *
    * @param username 用户名(模糊匹配)
    * @param age      年龄
    * @return 用户列表
    */

   public List<User> listUser(String username, Integer age) {
       log.info("条件查询用户,用户名:{},年龄:{}", username, age);
       // 构建查询条件
       QueryWrapper queryWrapper = QueryWrapper.create()
               .where(USER.IS_DELETED.eq(0));
       if (StringUtils.hasText(username)) {
           queryWrapper.and(USER.USERNAME.like("%" + username + "%"));
       }
       if (!ObjectUtils.isEmpty(age)) {
           queryWrapper.and(USER.AGE.eq(age));
       }
       // 按创建时间倒序
       queryWrapper.orderBy(USER.CREATE_TIME.desc());
       return userMapper.selectList(queryWrapper);
   }

   /**
    * 分页查询用户
    *
    * @param pageNum  页码(从1开始)
    * @param pageSize 每页条数
    * @param username 用户名(模糊匹配)
    * @return 分页结果
    */

   public Page<User> pageUser(Integer pageNum, Integer pageSize, String username) {
       // 参数校验
       if (ObjectUtils.isEmpty(pageNum) || pageNum < 1) {
           throw new IllegalArgumentException("页码必须大于等于1");
       }
       if (ObjectUtils.isEmpty(pageSize) || pageSize < 1 || pageSize > 100) {
           throw new IllegalArgumentException("每页条数必须在1-100之间");
       }
       log.info("分页查询用户,页码:{},每页条数:{},用户名:{}", pageNum, pageSize, username);
       
       // 构建分页对象
       Page<User> page = new Page<>(pageNum, pageSize);
       // 构建查询条件
       QueryWrapper queryWrapper = QueryWrapper.create()
               .where(USER.IS_DELETED.eq(0));
       if (StringUtils.hasText(username)) {
           queryWrapper.and(USER.USERNAME.like("%" + username + "%"));
       }
       queryWrapper.orderBy(USER.CREATE_TIME.desc());
       
       // MyBatis-Flex分页查询(结合MyBatis-Plus分页插件)
       return userMapper.selectPage(page, queryWrapper);
   }

   /**
    * 批量新增用户
    *
    * @param userList 用户列表
    * @return 新增成功条数
    */

   public int batchAddUser(List<User> userList) {
       if (CollectionUtils.isEmpty(userList)) {
           throw new IllegalArgumentException("用户列表不能为空");
       }
       // 批量参数校验
       userList.forEach(user -> validateUser(user, true));
       log.info("批量新增用户,数量:{}", userList.size());
       
       TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
       try {
           int rows = userMapper.insertBatch(userList);
           transactionManager.commit(status);
           log.info("批量新增用户成功,新增条数:{}", rows);
           return rows;
       } catch (Exception e) {
           transactionManager.rollback(status);
           log.error("批量新增用户失败", e);
           throw new RuntimeException("批量新增用户失败", e);
       }
   }

   /**
    * 用户参数校验
    *
    * @param user      用户实体
    * @param isInsert 是否为新增场景(新增需校验必填字段,更新可不校验)
    */

   private void validateUser(User user, boolean isInsert) {
       if (ObjectUtils.isEmpty(user)) {
           throw new IllegalArgumentException("用户实体不能为空");
       }
       if (isInsert) {
           StringUtils.hasText(user.getUsername(), "用户名不能为空");
           StringUtils.hasText(user.getPassword(), "密码不能为空");
       }
       // 年龄校验(若存在)
       if (!ObjectUtils.isEmpty(user.getAge()) && (user.getAge() < 0 || user.getAge() > 150)) {
           throw new IllegalArgumentException("年龄必须在0-150之间");
       }
       // 邮箱校验(若存在)
       if (StringUtils.hasText(user.getEmail()) && !user.getEmail().matches("^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$")) {
           throw new IllegalArgumentException("邮箱格式不正确");
       }
   }
}

说明:

  • 编程式事务通过PlatformTransactionManager实现,手动控制提交和回滚,适合复杂事务场景;
  • 使用QueryWrapper链式构建查询条件,结合UserTableDef.USER(MyBatis-Flex自动生成的表定义),避免硬编码字段名;
  • 参数校验使用org.springframework.util包下的工具类(ObjectUtilsStringUtilsCollectionUtils),符合规范;
  • 批量新增使用insertBatch方法,性能优于循环单条插入。

3.5 Controller层实现(Swagger3接口)

暴露RESTful接口,结合Swagger3注解,方便接口调试:

package com.jam.demo.controller;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.jam.demo.entity.User;
import com.jam.demo.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
* 用户控制器
*
* @author ken
*/

@Slf4j
@RestController
@RequestMapping("/api/user")
@Tag(name = "用户管理接口", description = "用户的增删改查、分页查询、批量新增接口")
public class UserController {

   private final UserService userService;

   @Autowired
   public UserController(UserService userService) {
       this.userService = userService;
   }

   /**
    * 新增用户
    */

   @PostMapping
   @Operation(
           summary = "新增用户",
           description = "新增用户,用户名和密码为必填项",
           responses = {
                   @ApiResponse(responseCode = "200", description = "新增成功", content = @Content(schema = @Schema(implementation = Long.class))),
                   @ApiResponse(responseCode
= "500", description = "新增失败")
           }
   )
   public ResponseEntity<Long> addUser(@Parameter(description = "用户实体", required = true) @RequestBody User user) {
       Long userId = userService.addUser(user);
       return new ResponseEntity<>(userId, HttpStatus.OK);
   }

   /**
    * 根据ID删除用户
    */

   @DeleteMapping("/{id}")
   @Operation(
           summary = "根据ID删除用户",
           description = "逻辑删除用户,并非物理删除",
           responses = {
                   @ApiResponse(responseCode = "200", description = "删除成功", content = @Content(schema = @Schema(implementation = Boolean.class))),
                   @ApiResponse(responseCode
= "500", description = "删除失败")
           }
   )
   public ResponseEntity<Boolean> deleteUserById(@Parameter(description = "用户ID", required = true) @PathVariable Long id) {
       boolean success = userService.deleteUserById(id);
       return new ResponseEntity<>(success, HttpStatus.OK);
   }

   /**
    * 更新用户信息
    */

   @PutMapping
   @Operation(
           summary = "更新用户信息",
           description = "更新用户信息,需传入用户ID",
           responses = {
                   @ApiResponse(responseCode = "200", description = "更新成功", content = @Content(schema = @Schema(implementation = Boolean.class))),
                   @ApiResponse(responseCode
= "500", description = "更新失败")
           }
   )
   public ResponseEntity<Boolean> updateUser(@Parameter(description = "用户实体(含ID)", required = true) @RequestBody User user) {
       boolean success = userService.updateUser(user);
       return new ResponseEntity<>(success, HttpStatus.OK);
   }

   /**
    * 根据ID查询用户
    */

   @GetMapping("/{id}")
   @Operation(
           summary = "根据ID查询用户",
           responses = {
                   @ApiResponse(responseCode = "200", description = "查询成功", content = @Content(schema = @Schema(implementation = User.class))),
                   @ApiResponse(responseCode
= "500", description = "查询失败")
           }
   )
   public ResponseEntity<User> getUserById(@Parameter(description = "用户ID", required = true) @PathVariable Long id) {
       User user = userService.getUserById(id);
       return new ResponseEntity<>(user, HttpStatus.OK);
   }

   /**
    * 条件查询用户列表
    */

   @GetMapping("/list")
   @Operation(
           summary = "条件查询用户列表",
           description = "根据用户名(模糊)和年龄查询用户列表",
           responses = {
                   @ApiResponse(responseCode = "200", description = "查询成功", content = @Content(schema = @Schema(implementation = List.class))),
                   @ApiResponse(responseCode
= "500", description = "查询失败")
           }
   )
   public ResponseEntity<List<User>> listUser(
           @Parameter(description = "用户名(模糊匹配)") @RequestParam(required = false) String username,
           @Parameter(description = "年龄") @RequestParam(required = false) Integer age) {
       List<User> userList = userService.listUser(username, age);
       return new ResponseEntity<>(userList, HttpStatus.OK);
   }

   /**
    * 分页查询用户
    */

   @GetMapping("/page")
   @Operation(
           summary = "分页查询用户",
           description = "分页查询用户,支持用户名模糊匹配",
           responses = {
                   @ApiResponse(responseCode = "200", description = "查询成功", content = @Content(schema = @Schema(implementation = Page.class))),
                   @ApiResponse(responseCode
= "500", description = "查询失败")
           }
   )
   public ResponseEntity<Page<User>> pageUser(
           @Parameter(description = "页码(从1开始)", required = true) @RequestParam Integer pageNum,
           @Parameter(description = "每页条数(1-100)", required = true) @RequestParam Integer pageSize,
           @Parameter(description = "用户名(模糊匹配)") @RequestParam(required = false) String username) {
       Page<User> page = userService.pageUser(pageNum, pageSize, username);
       return new ResponseEntity<>(page, HttpStatus.OK);
   }

   /**
    * 批量新增用户
    */

   @PostMapping("/batch")
   @Operation(
           summary = "批量新增用户",
           description = "批量新增用户,每个用户的用户名和密码为必填项",
           responses = {
                   @ApiResponse(responseCode = "200", description = "新增成功", content = @Content(schema = @Schema(implementation = Integer.class))),
                   @ApiResponse(responseCode
= "500", description = "新增失败")
           }
   )
   public ResponseEntity<Integer> batchAddUser(@Parameter(description = "用户列表", required = true) @RequestBody List<User> userList) {
       int rows = userService.batchAddUser(userList);
       return new ResponseEntity<>(rows, HttpStatus.OK);
   }
}

3.6 分页插件配置

MyBatis-Flex结合MyBatis-Plus的分页插件,实现高性能分页:

package com.jam.demo.config;

import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
* MyBatis-Plus配置(分页插件)
*
* @author ken
*/

@Configuration
public class MyBatisPlusConfig {

   /**
    * 分页插件
    */

   @Bean
   public MybatisPlusInterceptor mybatisPlusInterceptor() {
       MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
       // 添加MySQL分页插件
       interceptor.addInnerInterceptor(new PaginationInnerInterceptor(com.baomidou.mybatisplus.annotation.DbType.MYSQL));
       return interceptor;
   }
}

四、高级特性:动态SQL与关联查询

4.1 动态SQL实战

MyBatis-Flex的QueryWrapper支持复杂的动态SQL构建,例如多条件组合、in查询、区间查询等:

/**
* 复杂条件查询用户
*
* @param userQuery 查询条件封装
* @return 用户列表
*/

public List<User> listUserByComplexCondition(UserQuery userQuery) {
   QueryWrapper queryWrapper = QueryWrapper.create()
           .where(USER.IS_DELETED.eq(0));
   
   // 用户名模糊查询
   if (StringUtils.hasText(userQuery.getUsername())) {
       queryWrapper.and(USER.USERNAME.like("%" + userQuery.getUsername() + "%"));
   }
   
   // 年龄区间查询
   if (!ObjectUtils.isEmpty(userQuery.getMinAge()) && !ObjectUtils.isEmpty(userQuery.getMaxAge())) {
       queryWrapper.and(USER.AGE.between(userQuery.getMinAge(), userQuery.getMaxAge()));
   } else if (!ObjectUtils.isEmpty(userQuery.getMinAge())) {
       queryWrapper.and(USER.AGE.ge(userQuery.getMinAge()));
   } else if (!ObjectUtils.isEmpty(userQuery.getMaxAge())) {
       queryWrapper.and(USER.AGE.le(userQuery.getMaxAge()));
   }
   
   // 邮箱后缀匹配
   if (StringUtils.hasText(userQuery.getEmailSuffix())) {
       queryWrapper.and(USER.EMAIL.like("%" + userQuery.getEmailSuffix()));
   }
   
   // 批量ID查询
   if (!CollectionUtils.isEmpty(userQuery.getIdList())) {
       queryWrapper.and(USER.ID.in(userQuery.getIdList()));
   }
   
   // 时间区间查询(创建时间)
   if (!ObjectUtils.isEmpty(userQuery.getStartTime()) && !ObjectUtils.isEmpty(userQuery.getEndTime())) {
       queryWrapper.and(USER.CREATE_TIME.between(userQuery.getStartTime(), userQuery.getEndTime()));
   }
   
   // 排序:先按年龄倒序,再按创建时间倒序
   queryWrapper.orderBy(USER.AGE.desc())
               .orderBy(USER.CREATE_TIME.desc());
   
   return userMapper.selectList(queryWrapper);
}

// 查询条件封装类
@Data
@Schema(description = "用户查询条件")
public class UserQuery {
   @Schema(description = "用户名(模糊)")
   private String username;
   @Schema(description = "最小年龄")
   private Integer minAge;
   @Schema(description = "最大年龄")
   private Integer maxAge;
   @Schema(description = "邮箱后缀")
   private String emailSuffix;
   @Schema(description = "用户ID列表")
   private List<Long> idList;
   @Schema(description = "创建开始时间")
   private LocalDateTime startTime;
   @Schema(description = "创建结束时间")
   private LocalDateTime endTime;
}

4.2 关联查询(一对一/一对多)

MyBatis-Flex支持关联查询,以用户和订单的一对多关系为例(新增订单表t_order):

4.2.1 订单表SQL

CREATE TABLE `t_order` (
 `id` bigint NOT NULL AUTO_INCREMENT COMMENT '订单ID',
 `user_id` bigint NOT NULL COMMENT '用户ID',
 `order_no` varchar(50) NOT NULL COMMENT '订单编号',
 `amount` decimal(10,2) NOT NULL COMMENT '订单金额',
 `status` tinyint NOT NULL COMMENT '订单状态(0-待支付,1-已支付,2-已取消)',
 `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
 `is_deleted` tinyint NOT NULL DEFAULT '0' COMMENT '逻辑删除(0-未删,1-已删)',
 PRIMARY KEY (`id`),
 UNIQUE KEY `uk_order_no` (`order_no`),
 KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表';

4.2.2 订单实体类

package com.jam.demo.entity;

import com.mybatis.flex.core.BaseEntity;
import com.mybatis.flex.annotation.*;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;

import java.math.BigDecimal;
import java.time.LocalDateTime;

/**
* 订单实体类
*
* @author ken
*/

@Data
@EqualsAndHashCode(callSuper = true)
@Schema(description = "订单实体")
@Table(value = "t_order", comment = "订单表")
public class Order extends BaseEntity {

   /**
    * 订单ID
    */

   @Schema(description = "订单ID")
   @Id(keyType = KeyType.AUTO)
   private Long id;

   /**
    * 用户ID
    */

   @Schema(description = "用户ID", requiredMode = Schema.RequiredMode.REQUIRED)
   @Column(comment = "用户ID", isNullable = false)
   private Long userId;

   /**
    * 订单编号
    */

   @Schema(description = "订单编号", requiredMode = Schema.RequiredMode.REQUIRED)
   @Column(comment = "订单编号", isUnique = true, isNullable = false)
   private String orderNo;

   /**
    * 订单金额
    */

   @Schema(description = "订单金额", requiredMode = Schema.RequiredMode.REQUIRED)
   @Column(comment = "订单金额", isNullable = false)
   private BigDecimal amount;

   /**
    * 订单状态(0-待支付,1-已支付,2-已取消)
    */

   @Schema(description = "订单状态(0-待支付,1-已支付,2-已取消)", requiredMode = Schema.RequiredMode.REQUIRED)
   @Column(comment = "订单状态(0-待支付,1-已支付,2-已取消)", isNullable = false)
   private Integer status;

   /**
    * 创建时间
    */

   @Schema(description = "创建时间")
   @Column(comment = "创建时间", isInsertFill = true)
   @InsertFill(InsertFillStrategy.NOW)
   private LocalDateTime createTime;

   /**
    * 更新时间
    */

   @Schema(description = "更新时间")
   @Column(comment = "更新时间", isInsertFill = true, isUpdateFill = true)
   @InsertFill(InsertFillStrategy.NOW)
   @UpdateFill(UpdateFillStrategy.NOW)
   private LocalDateTime updateTime;

   /**
    * 逻辑删除(0-未删,1-已删)
    */

   @Schema(description = "逻辑删除(0-未删,1-已删)")
   @Column(comment = "逻辑删除(0-未删,1-已删)")
   @LogicDelete
   private Integer isDeleted;
}

4.2.3 关联查询实现(用户关联订单列表)

package com.jam.demo.service;

import com.mybatis.flex.core.query.QueryWrapper;
import com.jam.demo.entity.Order;
import com.jam.demo.entity.User;
import com.jam.demo.mapper.OrderMapper;
import com.jam.demo.mapper.UserMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import java.util.List;

import static com.jam.demo.entity.table.OrderTableDef.ORDER;
import static com.jam.demo.entity.table.UserTableDef.USER;

/**
* 关联查询服务
*
* @author ken
*/

@Slf4j
@Service
public class RelationService {

   private final UserMapper userMapper;
   private final OrderMapper orderMapper;

   @Autowired
   public RelationService(UserMapper userMapper, OrderMapper orderMapper) {
       this.userMapper = userMapper;
       this.orderMapper = orderMapper;
   }

   /**
    * 根据用户ID查询用户及关联的订单列表
    *
    * @param userId 用户ID
    * @return 用户(含订单列表)
    */

   public User getUserWithOrders(Long userId) {
       if (ObjectUtils.isEmpty(userId)) {
           throw new IllegalArgumentException("用户ID不能为空");
       }
       log.info("查询用户及订单列表,用户ID:{}", userId);
       
       // 1. 查询用户信息
       User user = userMapper.selectById(userId);
       if (ObjectUtils.isEmpty(user)) {
           log.info("用户不存在,用户ID:{}", userId);
           return null;
       }
       
       // 2. 查询该用户的订单列表
       QueryWrapper orderQueryWrapper = QueryWrapper.create()
               .where(ORDER.IS_DELETED.eq(0))
               .and(ORDER.USER_ID.eq(userId))
               .orderBy(ORDER.CREATE_TIME.desc());
       List<Order> orderList = orderMapper.selectList(orderQueryWrapper);
       
       // 3. 关联订单列表(可通过DTO封装返回,避免修改Entity)
       user.setOrders(orderList);
       return user;
   }
}

说明:实际开发中建议使用DTO封装关联结果,避免直接在Entity中添加关联字段,保证Entity的纯粹性。

五、MyBatis-Flex vs MyBatis-Plus:核心差异解析

很多开发者会混淆两者,其实它们的设计理念和侧重点不同,核心差异如下:

特性 MyBatis-Flex MyBatis-Plus
设计理念 极简、灵活,专注于SQL生成的简洁性 功能全面,提供一站式解决方案
SQL构建方式 QueryWrapper链式API更简洁,支持表定义常量 支持QueryWrapper和LambdaQueryWrapper
性能 更优,内置SQL优化,减少冗余操作 性能优秀,功能多导致轻微开销
侵入性 极低,可与MyBatis无缝集成 中等,需继承特定基类
企业级特性支持 原生支持多租户、数据脱敏等,配置简单 支持,但部分特性需额外配置
学习成本 低,API简洁直观 中,功能多需记忆更多API

架构差异对比:

image.png

总结:

  • 若追求极简开发、灵活的SQL控制、低侵入性,优先选MyBatis-Flex;
  • 若需要丰富的开箱即用功能(如代码生成、乐观锁、逻辑删除的更多配置),优先选MyBatis-Plus;
  • 两者可共存,可根据场景灵活搭配使用。

六、底层逻辑简析:MyBatis-Flex的SQL生成原理

MyBatis-Flex的核心优势在于"零XML动态SQL生成",其底层原理可概括为"注解解析+语法树构建":

  1. 注解解析:启动时扫描Entity上的@Table@Column等注解,解析表名、字段名、主键、填充规则等信息,生成表定义常量(如UserTableDef.USER);
  2. QueryWrapper构建:开发者通过链式API构建查询条件时,QueryWrapper会将条件转换为SQL语法树节点;
  3. SQL生成:根据语法树节点,结合表定义信息,生成标准的SQL语句,同时处理参数绑定、防SQL注入;
  4. 委托执行:将生成的SQL委托给MyBatis执行,利用MyBatis的结果集映射功能将查询结果转换为Entity对象。

SQL生成流程细节:

image.png

七、实战避坑指南:常见问题及解决方案

7.1 表名/字段名映射错误

  • 问题:查询时提示表名或字段名不存在;
  • 原因:Entity注解配置错误,或未开启驼峰命名转换;
  • 解决方案:
  1. 确保@Tablevalue属性正确对应表名;
  2. 确保@Columnvalue属性正确对应字段名(若字段名与属性名一致可省略);
  3. 在配置文件中开启驼峰命名转换(MyBatis-Flex默认开启)。

7.2 分页查询返回空数据

  • 问题:分页查询时total正确,但records为空;
  • 原因:表定义常量使用错误,或查询条件有误;
  • 解决方案:
  1. 检查QueryWrapper中使用的表定义常量是否正确(如是否引用了其他表的常量);
  2. 检查查询条件是否过滤了所有数据(如逻辑删除条件是否正确);
  3. 直接打印SQL,在数据库中执行验证条件是否正确。

7.3 事务不生效

  • 问题:编程式事务未提交或回滚;
  • 原因:事务管理器配置错误,或未捕获异常;
  • 解决方案:
  1. 确保注入的是PlatformTransactionManager,而非其他事务管理器;
  2. 确保异常被正确捕获,并在异常时调用rollback
  3. 检查数据源配置是否正确,事务需基于同一数据源。

八、总结

MyBatis-Flex作为新一代持久层框架,以"极简、灵活、高性能"的特点,完美平衡了MyBatis的灵活性和MyBatis-Plus的便捷性。本文通过完整的实战案例,覆盖了从环境搭建、单表CRUD、分页查询到关联查询的核心场景,所有代码均可直接编译运行。

核心要点回顾:

  1. 环境搭建只需引入核心依赖,配置简单;
  2. 基于注解和表定义常量,实现零XML的动态SQL生成;
  3. 编程式事务保证数据一致性,符合企业级开发规范;
  4. 与MyBatis-Plus各有侧重,可根据场景选择或共存。
目录
相关文章
|
1天前
|
数据采集 人工智能 安全
|
11天前
|
云安全 监控 安全
|
2天前
|
自然语言处理 API
万相 Wan2.6 全新升级发布!人人都能当导演的时代来了
通义万相2.6全新升级,支持文生图、图生视频、文生视频,打造电影级创作体验。智能分镜、角色扮演、音画同步,让创意一键成片,大众也能轻松制作高质量短视频。
981 151
|
2天前
|
编解码 人工智能 机器人
通义万相2.6,模型使用指南
智能分镜 | 多镜头叙事 | 支持15秒视频生成 | 高品质声音生成 | 多人稳定对话
|
16天前
|
机器学习/深度学习 人工智能 自然语言处理
Z-Image:冲击体验上限的下一代图像生成模型
通义实验室推出全新文生图模型Z-Image,以6B参数实现“快、稳、轻、准”突破。Turbo版本仅需8步亚秒级生成,支持16GB显存设备,中英双语理解与文字渲染尤为出色,真实感和美学表现媲美国际顶尖模型,被誉为“最值得关注的开源生图模型之一”。
1681 8
|
8天前
|
人工智能 自然语言处理 API
一句话生成拓扑图!AI+Draw.io 封神开源组合,工具让你的效率爆炸
一句话生成拓扑图!next-ai-draw-io 结合 AI 与 Draw.io,通过自然语言秒出架构图,支持私有部署、免费大模型接口,彻底解放生产力,绘图效率直接爆炸。
630 152
|
10天前
|
人工智能 安全 前端开发
AgentScope Java v1.0 发布,让 Java 开发者轻松构建企业级 Agentic 应用
AgentScope 重磅发布 Java 版本,拥抱企业开发主流技术栈。
601 15
|
9天前
|
人工智能 自然语言处理 API
Next AI Draw.io:当AI遇见Draw.io图表绘制
Next AI Draw.io 是一款融合AI与图表绘制的开源工具,基于Next.js实现,支持自然语言生成架构图、流程图等专业图表。集成多款主流大模型,提供智能绘图、图像识别优化、版本管理等功能,部署简单,安全可控,助力技术文档与系统设计高效创作。
679 151