MyBatis-Flex:让 MyBatis 真正“柔”起来,告别 XML 的“铁镣铐”!🚀💪
兄弟们,用过 MyBatis 吗?肯定用过!那你一定经历过这样的痛苦:写个简单查询,却要在 XML 和 Java 代码间反复横跳;改个字段名,要改实体类、Mapper、XML 三处;想用点高级特性,XML 配置能写到你怀疑人生。😫
MyBatis-Flex 来了!它不是一个新 ORM,而是 MyBatis 的“超级增强版”。如果说 MyBatis 是个稳重的中年人,那 MyBatis-Flex 就是个会街舞的年轻人——更灵活、更轻量、性能更高,关键是:彻底解放生产力!
一、MyBatis-Flex 是啥?—— MyBatis 的“Pro Max Ultra”版 📱➡️🚀
先看个对比:
传统 MyBatis:
// 1. UserMapper.java
User selectById(@Param("id") Long id);
// 2. UserMapper.xml(写了100行)
<select id="selectById" resultMap="userMap">
SELECT * FROM user WHERE id = #{id}
</select>
<resultMap id="userMap" type="User">
<id property="id" column="id"/>
<result property="name" column="name"/>
<!-- 还有20个字段要配... -->
</resultMap>
// 3. 调用
User user = userMapper.selectById(1L);
MyBatis-Flex:
// 1. 就这一行!😎
User user = userMapper.selectOneById(1L);
// 2. 或者用QueryWrapper
User user = userMapper.selectOneByQuery(
QueryWrapper.create()
.where(User::getId).eq(1L)
.and(User::getStatus).eq(1)
);
所以,MyBatis-Flex 是什么?
它是一个构建在 MyBatis 之上的、高度灵活的 ORM 框架。核心特点:
- 无 XML:所有 SQL 通过 Java API 动态构建
- APT 自动生成:编译时生成实体类、Mapper、TableDef
- 极致性能:比 MyBatis-Plus 快 5~10 倍(官方数据)
- 轻量:只有 500KB,无第三方依赖
二、为什么需要 MyBatis-Flex?—— 当 MyBatis 遇上“中年危机” 🧓➡️🕺
MyBatis 的痛点:
- XML 地狱:简单项目还好,复杂项目 XML 文件比代码还多
- 维护困难:字段改了要改三处,容易遗漏
- 动态 SQL 难写:
<if>``<choose>标签嵌套到眼花 - 代码生成器弱:生成的代码“傻大粗”,不好定制
- 性能有损耗:反射多,XML 解析有开销
MyBatis-Plus 的不足:
- 虽然解决了 XML 问题,但设计复杂,学习曲线陡
- 性能有损耗:反射+缓存,大型项目明显
- 不够灵活:复杂查询支持有限
MyBatis-Flex 的解决方案:
- APT 代码生成:编译时生成,零运行时反射
- QueryWrapper 极致灵活:链式调用,支持任意复杂查询
- 多租户、数据权限:原生支持,配置简单
- 字段加密/脱敏:注解搞定,业务无感知
- SQL 审计:自动记录,方便排查
核心思想: “约定大于配置,但绝不限制灵活性”
三、核心特性“炫技”时刻 ✨
特性1:APT 自动生成(编译时魔法)🧙♂️
传统代码生成器:运行时生成,用反射,性能差
MyBatis-Flex 的 APT:编译时生成,像 Lombok 一样
// 1. 实体类(你写的)
@Table("user")
public class User {
@Id(keyType = KeyType.Auto)
private Long id;
@Column("user_name")
private String name;
private Integer age;
// getter/setter
}
// 2. 编译时自动生成(你看不到但存在)
public class Tables {
public static final UserTable USER = new UserTable();
public static class UserTable extends TableDef {
public final Column ID = new Column(this, "id");
public final Column NAME = new Column(this, "user_name");
public final Column AGE = new Column(this, "age");
public UserTable() {
super("user");
}
}
}
优势:
- 零反射:
UserTable.NAME是编译时常量,不是字符串! - 类型安全:编译时检查字段名是否正确
- IDE 提示:自动补全,不会写错字段名
特性2:QueryWrapper 的“七十二变” 🎭
基础查询:
// 1. 等值查询
QueryWrapper query = QueryWrapper.create()
.where(User::getId).eq(1L)
.and(User::getAge).ge(18)
.and(User::getName).like("张%");
// 2. 动态条件(比MyBatis的<if>优雅多了!)
QueryWrapper query = QueryWrapper.create()
.where(Account::getId).ge(1)
.when(age != null, qw -> qw.and(Account::getAge).eq(age))
.when(name != null, qw -> qw.and(Account::getName).like(name));
// 3. 复杂嵌套
QueryWrapper query = QueryWrapper.create()
.where(User::getId).in(
select("user_id").from("order").where("amount > 1000")
)
.and(qw -> qw
.where(User::getStatus).eq(1)
.or(User::getVip).eq(1)
);
联表查询(不用写XML!):
// 用户和部门联表
QueryWrapper query = QueryWrapper.create()
.select(USER.ALL_COLUMNS, DEPT.NAME.as("deptName"))
.from(USER)
.leftJoin(DEPT).on(USER.DEPT_ID.eq(DEPT.ID))
.where(USER.STATUS.eq(1))
.orderBy(USER.CREATE_TIME.desc());
List<UserDTO> list = userMapper.selectListByQueryAs(query, UserDTO.class);
分组聚合:
QueryWrapper query = QueryWrapper.create()
.select(
USER.GENDER,
count().as("total"),
avg(USER.AGE).as("avgAge")
)
.from(USER)
.groupBy(USER.GENDER)
.having(count().gt(10));
特性3:多租户支持(一行配置)🏢
@Configuration
public class MyBatisFlexConfig {
@Bean
public TenantFactory tenantFactory() {
return new TenantFactory() {
@Override
public Expression getTenantId() {
// 从ThreadLocal或SecurityContext获取租户ID
return TenantManager.getTenantId();
}
@Override
public String getTenantIdColumn() {
return "tenant_id"; // 租户字段名
}
};
}
}
// 使用时自动过滤
List<User> users = userMapper.selectAll();
// 实际SQL:SELECT * FROM user WHERE tenant_id = 'current_tenant'
特性4:数据权限(注解驱动)🔐
@Table("order")
public class Order {
// 字段省略
}
// Service层
@Service
public class OrderService {
// 自动添加数据权限条件
// 如:AND (creator_id = ? OR dept_id IN (?))
@DataPermission(
type = DataPermissionType.USER_AND_DEPT,
userField = "creator_id",
deptField = "dept_id"
)
public List<Order> getUserOrders() {
return orderMapper.selectAll();
}
}
特性5:字段加密/脱敏 🎭
@Table("user")
public class User {
@Column("phone")
@ColumnEncrypt("myEncryptor") // 存储时自动加密
private String phone;
@Column("id_card")
@ColumnMask(MaskType.ID_CARD) // 查询时自动脱敏:510**********1234
private String idCard;
}
// 插入时自动加密
user.setPhone("13800138000");
userMapper.insert(user);
// 数据库存的是:U2FsdGVkX1+2pGz3V4vI7R5g...
// 查询时自动解密+脱敏
User user = userMapper.selectOneById(1L);
System.out.println(user.getPhone()); // 13800138000(自动解密)
System.out.println(user.getIdCard()); // 510**********1234(自动脱敏)
四、性能对比:真的能“起飞”吗?✈️
官方基准测试(查询 10000 次):
MyBatis: 基础线
MyBatis-Plus: 慢 20%~30%
MyBatis-Flex: 快 5~10 倍!
为什么快?
- 零反射:APT 生成,直接方法调用
- 无 XML 解析:SQL 动态构建,无解析开销
- 智能缓存:TableDef 编译时常量,永久缓存
- 最小化包装:QueryWrapper 最终生成最简 SQL
五、快速入门:5分钟上手 ⏰
步骤1:加依赖
<dependency>
<groupId>com.mybatis-flex</groupId>
<artifactId>mybatis-flex-spring-boot-starter</artifactId>
<version>1.6.5</version>
</dependency>
<!-- APT处理器 -->
<dependency>
<groupId>com.mybatis-flex</groupId>
<artifactId>mybatis-flex-processor</artifactId>
<version>1.6.5</version>
<scope>provided</scope>
</dependency>
步骤2:配置
# application.yml
mybatis-flex:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
# 启用APT
processor:
enable: true
步骤3:写实体类
@Table("user")
public class User {
@Id(keyType = KeyType.Auto)
private Long id;
@Column("user_name")
private String name;
private Integer age;
@Column(onInsertValue = "now()")
private Date createTime;
// getter/setter
}
步骤4:写 Mapper(可选,有通用 Mapper)
// 1. 继承 BaseMapper(已有CRUD)
public interface UserMapper extends BaseMapper<User> {
// 自定义方法
List<User> selectByName(@Param("name") String name);
}
// 2. 或者用 @Mapper
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
步骤5:开箱即用的 Service
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
// 1. 增删改查
public void addUser(User user) {
userMapper.insert(user); // 自动回填ID
}
public User getUser(Long id) {
return userMapper.selectOneById(id);
}
// 2. 复杂查询
public List<User> findUsers(String name, Integer minAge) {
return userMapper.selectListByQuery(
QueryWrapper.create()
.where(User::getName).like(name)
.and(User::getAge).ge(minAge)
.orderBy(User::getCreateTime.desc())
);
}
// 3. 更新(只更新非null字段)
public void updateUser(User user) {
userMapper.update(user); // 智能更新
}
}
六、高级玩法:解放生产力的“骚操作” 🎩
玩法1:逻辑删除(一行注解)
@Table("user")
public class User {
// 其他字段...
@Column(isLogicDelete = true)
private Boolean deleted; // 1删除 0正常
// 或时间戳
// @Column(logicDelete = LogicDeleteType.DELETE_TIMESTAMP)
// private Date deleteTime;
}
// 自动过滤已删除数据
List<User> users = userMapper.selectAll();
// SQL: SELECT * FROM user WHERE deleted = 0
玩法2:乐观锁(防并发更新)
@Table("product")
public class Product {
@Version // 乐观锁字段
private Integer version;
private Integer stock;
}
// 更新时自动检查版本
product.setStock(product.getStock() - 1);
productMapper.update(product);
// SQL: UPDATE product SET stock=?, version=?
// WHERE id=? AND version=?
// 返回影响行数=0表示被其他线程修改了
玩法3:多主键支持
@Table("user_role")
public class UserRole {
@Id
private Long userId;
@Id // 多个@Id表示联合主键
private Long roleId;
// 使用复合主键查询
UserRole userRole = userRoleMapper.selectOneById(
Id.ofKeys(1L, 2L) // userId=1, roleId=2
);
}
玩法4:SQL审计(记录所有操作)
@Configuration
public class AuditConfig {
@Bean
public SqlAudit sqlAudit() {
return new SqlAudit() {
@Override
public void audit(SqlAuditInfo auditInfo) {
// 记录到日志或数据库
log.info("SQL: {}, 参数: {}, 耗时: {}ms",
auditInfo.getFullSql(),
auditInfo.getParameters(),
auditInfo.getElapsedTime()
);
// 慢SQL告警
if (auditInfo.getElapsedTime() > 1000) {
alertSlowSql(auditInfo);
}
}
};
}
}
七、工作中的“防坑”指南 🕳️
坑1:APT 不生成代码
检查:
- IDEA 中启用注解处理:
Settings → Build → Annotation Processors - 确保依赖中加了
mybatis-flex-processor - 清理重新编译:
mvn clean compile
坑2:字段名映射问题
// 错误:数据库是下划线,Java是驼峰
@Column("userName") // 数据库字段是 user_name
private String userName;
// 正确1:指定column
@Column("user_name")
private String userName;
// 正确2:全局配置驼峰转换
mybatis-flex:
global-config:
column-to-property-style: camel
坑3:批量插入性能
// 错误:循环insert
for (User user : userList) {
userMapper.insert(user); // 每次都要获取连接
}
// 正确:批量插入
userMapper.insertBatch(userList); // 一次插入,性能提升N倍
坑4:分页查询内存溢出
// 错误:一次性查全部
List<User> users = userMapper.selectAll(); // 100万数据?
// 正确:分页查询
Page<User> page = userMapper.paginate(
Page.of(1, 20), // 第1页,20条
QueryWrapper.create().orderBy(User::getId.desc())
);
// 流式查询(大数据量)
try (Cursor<User> cursor = userMapper.selectCursorByQuery(
QueryWrapper.create().orderBy(User::getId)
)) {
cursor.forEach(user -> {
// 处理
});
}
坑5:事务管理
// 错误:多个操作没在事务中
public void updateUser(User user) {
userMapper.update(user);
logMapper.insert(log); // 万一失败,user更新无法回滚
}
// 正确:添加@Transactional
@Transactional(rollbackFor = Exception.class)
public void updateUserWithLog(User user, Log log) {
userMapper.update(user);
logMapper.insert(log);
}
八、对比 MyBatis-Plus:我该选谁?🤔
| 特性 | MyBatis-Flex | MyBatis-Plus |
|---|---|---|
| 性能 | ⭐⭐⭐⭐⭐(零反射,极致快) | ⭐⭐⭐(反射+缓存) |
| 灵活性 | ⭐⭐⭐⭐⭐(任意复杂查询) | ⭐⭐⭐(复杂查询受限) |
| 学习成本 | ⭐⭐⭐(简单直观) | ⭐⭐⭐⭐(概念多) |
| 社区生态 | ⭐⭐(较新,在增长) | ⭐⭐⭐⭐⭐(成熟) |
| 代码生成 | ⭐⭐⭐⭐⭐(APT,零反射) | ⭐⭐⭐(运行时生成) |
| 多租户 | ⭐⭐⭐⭐⭐(原生支持) | ⭐⭐⭐(需插件) |
| 数据权限 | ⭐⭐⭐⭐⭐(注解驱动) | ⭐⭐(需自定义) |
选择建议:
- 新项目、追求性能:选 MyBatis-Flex
- 老项目、求稳定:继续用 MyBatis-Plus
- 复杂查询多:选 MyBatis-Flex(QueryWrapper 真香)
- 需要丰富插件:选 MyBatis-Plus(生态成熟)
九、什么时候不用 MyBatis-Flex?🙅♂️
- 超级简单项目:JDBC Template 就够了
- 强依赖 MyBatis 插件生态:很多插件还没适配
- 团队不熟悉:学习新框架需要成本
- 需要 JPA 特性:用 Spring Data JPA
- 超大规模团队:MyBatis-Plus 的社区支持更稳
十、总结:让数据库操作真正“柔”起来 🌈
MyBatis-Flex 的核心价值就一句话: “用最少的代码,做最多的事,还跑得飞快!”
它解决了 MyBatis 系列框架的三大痛点:
- XML 配置繁琐 → 全 Java API
- 性能不够极致 → 零反射 + APT
- 功能不够灵活 → 强大的 QueryWrapper
适合人群:
- 被 MyBatis XML 折磨过的开发者
- 追求极致性能的架构师
- 需要灵活数据权限的中台系统
- 厌倦了反射的性能损耗
最后,下次当你又要写 MyBatis XML 时,不妨试试 MyBatis-Flex。你可能会发现,原来数据库操作可以这么“柔”、这么“顺滑”!🚀
记住:技术选型没有银弹,但 MyBatis-Flex 绝对是你 ORM 武器库中值得拥有的一把“瑞士军刀”!🔪✨