1 MyBatis Flex
1.1 简介
MyBatis-Flex 是一个在 MyBatis 基础上深度增强的框架,专为解决 Java 开发中数据库操作的复杂性而设计。它不仅保留了 MyBatis 原生的灵活性,可以自由编写 SQL 语句,同时又引入了一系列强大的特性,大大简化了开发流程。
MyBatis-Flex 官网
教程学习地址:https://mybatis-flex.com/zh/intro/getting-started.html
与其他 ORM 框架相比,MyBatis-Flex 具有显著的优势。它非常轻量,除了 MyBatis 之外,没有任何第三方依赖,这不仅减少了项目的依赖复杂度,还提高了系统的稳定性和自主性。在性能方面,MyBatis-Flex 表现卓越,其独特的架构设计,在 SQL 执行过程中没有任何 SQL 解析操作,从而带来了指数级的性能增长 ,查询单条数据和查询 10 条数据的速度,大概是 MyBatis-Plus 的 5-10 倍。
和MyBatis-Plus 与 Fluent-Mybatis 对比:
| 功能或特点 | MyBatis-Flex | MyBatis-Plus | Fluent-Mybatis |
|---|---|---|---|
| 对 entity 的基本增删改查 | ✅ | ✅ | ✅ |
| 分页查询 | ✅ | ✅ | ✅ |
| 分页查询之总量缓存 | ✅ | ✅ | ❌ |
| 多表查询: from 多张表 | ✅ | ❌ | ❌ |
| 多表查询: left join、inner join 等等 | ✅ | ❌ | ✅ |
| 多表查询: union,union all | ✅ | ❌ | ✅ |
| 单主键配置 | ✅ | ✅ | ✅ |
| 多种 id 生成策略 | ✅ | ✅ | ✅ |
| 支持多主键、复合主键 | ✅ | ❌ | ❌ |
| 字段的 typeHandler 配置 | ✅ | ✅ | ✅ |
| 除了 Mybatis,无其他第三方依赖(更轻量) | ✅ | ❌ | ❌ |
| QueryWrapper 是否支持在微服务项目下进行 RPC 传输 | ✅ | ❌ | 未知 |
| 逻辑删除 | ✅ | ✅ | ✅ |
| 乐观锁 | ✅ | ✅ | ✅ |
| SQL 审计 | ✅ | ❌ | ❌ |
| 数据填充 | ✅ | ✔️ (收费) | ✅ |
| 数据脱敏 | ✅ | ✔️ (收费) | ❌ |
| 字段权限 | ✅ | ✔️ (收费) | ❌ |
| 字段加密 | ✅ | ✔️ (收费) | ❌ |
| 字典回写 | ✅ | ✔️ (收费) | ❌ |
| Db + Row | ✅ | ❌ | ❌ |
| Entity 监听 | ✅ | ❌ | ❌ |
| 多数据源支持 | ✅ | ✅(不支持非Spring项目) | ❌ |
| 多数据源是否支持 Spring 的事务管理,比如 @Transactional 和 TransactionTemplate 等 | ✅ | ❌ | ❌ |
| 多数据源是否支持 "非Spring" 项目 | ✅ | ❌ | ❌ |
| 多租户 | ✅ | ✅ | ❌ |
| 动态表名 | ✅ | ✅ | ❌ |
| 动态 Schema | ✅ | ❌ | ❌ |
1.2 简单操作
1.2.1 配置
1.2.1.1 pom.xml
SpringBoot v2.x 的场景下依赖
<!-- MyBatis-Flex 核心依赖 -->
<dependency>
<groupId>com.mybatis-flex</groupId>
<artifactId>mybatis-flex-spring-boot-starter</artifactId>
<version>1.11.4</version>
</dependency>
<!-- 数据库驱动(以 MySQL 为例) -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.0.33</version>
</dependency>
<!-- 用于生成代码-->
<dependency>
<groupId>com.mybatis-flex</groupId>
<artifactId>mybatis-flex-codegen</artifactId>
<version>1.11.4</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.23</version>
</dependency>
SpringBoot v3.x,需要使用如下依赖:
<dependency>
<groupId>com.mybatis-flex</groupId>
<artifactId>mybatis-flex-spring-boot3-starter</artifactId>
<version>1.11.3</version>
</dependency>
1.2.1.2 yml配置
在application.yml文件中配置数据源
spring:
datasource:
url: jdbc:mysql://localhost:3306/test_db?useSSL=false&serverTimezone=UTC
username: xxxx
password: xxxx
driver-class-name: com.mysql.cj.jdbc.Driver
# MyBatis-Flex 高级配置
mybatis-flex:
mapper-locations: classpath:mapper/**/*.xml
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 开启驼峰命名转换
map-underscore-to-camel-case: true
# 全局配置
global-config:
print-banner: true
# 逻辑删除配置
logic-delete:
logic-delete-value: 1 # 已删除值
logic-not-delete-value: 0 # 未删除值
# 多租户配置
tenant-config:
ignore-tables: sys_config, sys_log # 忽略租户过滤的表
# 字段安全类型(脱敏/加密)
column-security:
# 全局加密密钥(可覆盖)
aes-key: "my-secret-key-123"
1.2.2 生成代码
public static void main(String[] args) {
//配置数据源
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/test?characterEncoding=utf-8");
dataSource.setUsername("root");
dataSource.setPassword("root");
GlobalConfig globalConfig = createGlobalConfigUseStyle();
//通过 datasource 和 globalConfig 创建代码生成器
Generator generator = new Generator(dataSource, globalConfig);
//生成代码
generator.generate();
}
public static GlobalConfig createGlobalConfigUseStyle() {
//创建配置内容
GlobalConfig globalConfig = new GlobalConfig();
String projectPath = AutoGen.class.getResource("/").getPath();
String sourceDir = projectPath.substring(0, projectPath.indexOf("/target/")) ;
globalConfig.setSourceDir(sourceDir);
//设置根包
globalConfig.getPackageConfig()
.setBasePackage("com.test")
.setMapperXmlPath(sourceDir+"/com/test/mapper");
//设置表前缀和只生成哪些表,setGenerateTable 未配置时,生成所有表
globalConfig.getStrategyConfig().setGenerateTable("user");
//设置生成 entity 并启用 Lombok
globalConfig.enableEntity().setWithLombok(true).setJdkVersion(17).setClassSuffix("Entity");
//设置生成相关类及文件
globalConfig.enableMapper();
globalConfig.enableMapperXml();
globalConfig.enableService();
globalConfig.enableServiceImpl();
globalConfig.enableController();
//可以单独配置某个列
/*ColumnConfig columnConfig = new ColumnConfig();
columnConfig.setColumnName("tenant_id");
columnConfig.setLarge(true);
columnConfig.setVersion(true);
globalConfig.getStrategyConfig()
.setColumnConfig("tb_account", columnConfig);*/
return globalConfig;
}
1.2.3 业务类
1.2.3.1 实体
@Table("tb_account")
@Data
public class Account {
@Id(keyType = KeyType.Auto)
private Long id;
@Column("user_name")
private String userName;
private Integer age;
@Column(onInsertValue = "now()")
private LocalDateTime createTime;
@Column(onUpdateValue = "now()")
private LocalDateTime updateTime;
// 逻辑删除字段(0:正常,1:删除)
@Column(logicDelete = true)
private Integer isDeleted;
// 多租户字段
@Column(tenantId = true)
private Long tenantId;
// 加密字段(手机号)
@Column(cryptoType = CryptoType.AES)
private String mobile;
// 脱敏字段(银行卡号)
@Column(maskType = MaskType.BANK_CARD)
private String bankCard;
// 乐观锁字段
@Column(version = true)
private Integer version;
}
@ColumnMask是 MyBatis-Flex 提供的内置脱敏注解,目前已经提供了9种脱敏规则,具体:手机号脱敏、固定电话脱敏、身份证号脱敏、车牌号脱敏、地址脱敏、邮件脱敏、密码脱敏、银行卡号脱敏
1.2.3.2 实体脱敏
当 Mybaits-Flex 内置的9种脱敏规则无法满足要求时,我们还可以自定义脱敏规则,其步骤如下:
通过 MaskManager 注册新的脱敏规则:
MaskManager.registerMaskProcessor("自定义规则名称"
, data -> {
return data;
})
使用自定义的脱敏规则
@Table("tb_account")
public class Account {
@Id(keyType = KeyType.Auto)
private Long id;
@ColumnMask("自定义规则名称")
private String userName;
}
1.2.3.3 Mapper接口
@Mapper
public interface AccountMapper extends BaseMapper<Account> {
// 自定义SQL方法示例
@Select("SELECT * FROM tb_account WHERE age > #{minAge}")
List<Account> selectByMinAge(@Param("minAge") int minAge);
}
1.2.3.4 Service层
@Service
public class AccountService extends ServiceImpl<AccountMapper, Account> {
// 自定义业务方法
public List<Account> findAdults() {
return queryChain()
.select(Account::getId, Account::getUserName)
.where(Account::getAge).ge(18)
.list();
}
}
1.3 MyBatis-Flex 核心 API
1.3.1 条件构造器(QueryWrapper)
// 基础查询
QueryWrapper query = QueryWrapper.create()
.select(ACCOUNT.ID, ACCOUNT.USER_NAME, ACCOUNT.AGE)
.from(ACCOUNT)
.where(ACCOUNT.AGE.between(18, 60))
.and(ACCOUNT.USER_NAME.like("张%"))
.orderBy(ACCOUNT.AGE.desc(), ACCOUNT.ID.asc())
.limit(10);
// 联表查询(带别名)
QueryWrapper query = QueryWrapper.create()
.select(ACCOUNT.ID, ORDER.ORDER_NO, ORDER.AMOUNT)
.from(ACCOUNT.as("a"))
.leftJoin(ORDER).as("o").on(ACCOUNT.ID.eq(ORDER.ACCOUNT_ID))
.where(ORDER.CREATE_TIME.ge(LocalDate.now().minusMonths(1)))
.groupBy(ACCOUNT.ID)
.having(sum(ORDER.AMOUNT).gt(10000));
// 子查询
QueryWrapper subQuery = QueryWrapper.create()
.select(ORDER.ACCOUNT_ID)
.from(ORDER)
.where(ORDER.STATUS.eq(1));
QueryWrapper mainQuery = QueryWrapper.create()
.select()
.from(ACCOUNT)
.where(ACCOUNT.ID.in(subQuery))
.and(ACCOUNT.TENANT_ID.eq(123));
// Lambda 表达式
LambdaQueryWrapper<Account> lambdaQuery = LambdaQueryWrapper.create()
.select(Account::getId, Account::getUserName)
.eq(Account::getAge, 25)
.likeRight(Account::getUserName, "张")
.orderByDesc(Account::getCreateTime);
1.3.2 更新操作(UpdateWrapper)
// 条件更新
UpdateWrapper update = UpdateWrapper.create()
.set(ACCOUNT.AGE, ACCOUNT.AGE.add(1))
.set(ACCOUNT.UPDATE_TIME, LocalDateTime.now())
.where(ACCOUNT.LAST_LOGIN_TIME.lt(LocalDate.now().minusYears(1)));
// 实体更新
Account account = new Account();
account.setStatus(2);
UpdateWrapper update = UpdateWrapper.of(account)
.where(ACCOUNT.STATUS.eq(1).and(ACCOUNT.AGE.lt(18)));
// Lambda 更新
LambdaUpdateWrapper<Account> lambdaUpdate = LambdaUpdateWrapper.create()
.set(Account::getAge, 30)
.set(Account::getUpdateTime, LocalDateTime.now())
.eq(Account::getId, 1001);
1.3.3 分页与聚合
// 分页查询
Page<Account> page = Page.of(1, 20); // 第1页,每页20条
QueryWrapper query = QueryWrapper.create()
.where(ACCOUNT.AGE.ge(18))
.orderBy(ACCOUNT.CREATE_TIME.desc());
Page<Account> result = mapper.paginate(page, query);
// 分页结果处理
List<Account> records = result.getRecords();
long total = result.getTotalRow();
long totalPages = result.getTotalPage();
// 聚合查询
QueryWrapper aggQuery = QueryWrapper.create()
.select(
ACCOUNT.DEPT_ID,
count().as("emp_count"),
avg(ACCOUNT.SALARY).as("avg_salary"),
max(ACCOUNT.SALARY).as("max_salary")
)
.groupBy(ACCOUNT.DEPT_ID)
.having(avg(ACCOUNT.SALARY).gt(10000));
1.3.4 事务管理
// 声明式事务
@Transactional(rollbackFor = Exception.class)
public void transferMoney(Long fromId, Long toId, BigDecimal amount) {
// 扣减转出账户
UpdateWrapper deduct = UpdateWrapper.create()
.setRaw(ACCOUNT.BALANCE, "balance - ?", amount)
.where(ACCOUNT.ID.eq(fromId));
accountMapper.updateByQuery(deduct);
// 增加转入账户
UpdateWrapper add = UpdateWrapper.create()
.setRaw(ACCOUNT.BALANCE, "balance + ?", amount)
.where(ACCOUNT.ID.eq(toId));
accountMapper.updateByQuery(add);
// 记录交易流水
transactionService.logTransfer(fromId, toId, amount);
}
// 编程式事务
public void batchImport(List<Account> accounts) {
Transaction.tx(() -> {
for (Account account : accounts) {
if (account.getAge() < 18) {
throw new RuntimeException("未成年账户禁止导入");
}
accountMapper.insert(account);
}
return true;
});
}
1.3.5 高级特性
// 1. 逻辑删除(自动添加条件)
accountMapper.deleteById(1L); // → UPDATE SET is_deleted=1 WHERE id=1
// 2. 多租户过滤(自动添加租户ID条件)
List<Account> list = accountMapper.selectAll();
// → SELECT * FROM tb_account WHERE tenant_id=当前租户ID
// 3. 字段加密/解密(自动处理)
Account account = accountMapper.selectOneById(1L);
System.out.println(account.getMobile()); // 自动解密 → 13800138000
// 4. 数据脱敏
Account account = accountMapper.selectOneById(1L);
System.out.println(account.getBankCard()); // → 622202******1234
// 5. 乐观锁更新
Account account = accountMapper.selectOneById(1L);
account.setBalance(account.getBalance() + 100);
accountMapper.update(account);
// → UPDATE ... WHERE id=1 AND version=旧版本
6、工具类使用
// Db 工具类快速操作
Db.insert("account", "id,user_name,age", 1001, "张三", 30);
Db.updateById("account", "age", 31, 1001);
List<Account> list = Db.selectAllByCondition(Account.class, "age > ?", 18);
// 批量操作
List<Account> accounts = ...;
Db.executeBatch(accounts, 1000, (mapper, account) -> {
mapper.insert(account);
});
// SQL 工具
String inSql = SqlUtil.buildInCondition("id", Arrays.asList(1,2,3));
// → id IN (1,2,3)
String safeSql = SqlUtil.escapeSql("SELECT * FROM user WHERE name='admin' OR 1=1");
// → 防止 SQL 注入的安全处理