多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

MyBatis-Flex 零侵入设计原理与工程实践

MyBatis-Flex 零侵入设计原理与工程实践 简介这是一份面向Java后端开发者与数据库应用工程师的MyBatis增强框架实战资源聚焦轻量级、高性能ORM开发需求解决传统MyBatis在多表关联、分页、批量操作及企业级特性如多租户、逻辑删除、SQL审计等方面的扩展痛点。资源包共1058个文件含839个核心Java源码涵盖Mapper、Entity、插件及启动器模块、65个Markdown文档含快速入门、API说明与最佳实践、41个SQL脚本建表与测试数据、22个XML配置Spring整合与MyBatis配置以及YML、JSON、Properties等配置文件和少量前端资源CSS/JS/Vue整体压缩后仅5.53MB结构清晰、开箱即用。目前已有87人学习下载。读者可直接获取完整可运行的MyBatis-Flex工程骨架、全量注解与API示例、多数据源与分库分表配置模板以及包含数据脱敏、字段加密、乐观锁等企业级功能的实现实例大幅降低二次集成成本。1. 为什么我放弃 MyBatis-Plus转而用 MyBatis-Flex 做新项目核心数据层去年底接手一个中型供应链系统重构原技术栈是 Spring Boot 2.7 MyBatis-Plus 3.5.x。上线前压测阶段暴露出三个硬伤一是分页插件在多表 JOIN 场景下生成的 COUNT SQL 性能极差单次查询耗时从 80ms 拉到 1.2s二是字段级加密需求落地时BaseMapper 的 update 方法无法跳过 null 值字段导致加密字段被误置为 NULL三是动态 SQL 复杂嵌套后 XML 难以维护团队新人改个条件要花半天看懂choose嵌套逻辑。当时我就在想有没有一种框架既保留 MyBatis 原生 SQL 的可控性又不像 MP 那样用大量注解和泛型把业务逻辑捆死直到看到 MyBatis-Flex 的 GitHub README 第一行写着“不侵入、不封装、不魔改 MyBatis 核心机制”我立刻 clone 下来跑了个 demo——它不是在 MyBatis 上盖房子而是在 MyBatis 底层铺轨道。MyBatis-Flex 的本质是把 MyBatis 的“SQL 编写自由度”和“ORM 易用性”做了正交解耦。它不替换 SqlSessionFactory不重写 Executor所有增强能力都通过 MyBatis 原生的 Plugin 机制、TypeHandler 扩展点、Configuration 自定义注入实现。这意味着你用 Flex 写的代码换回原生 MyBatis 只需删掉几个注解、改两行配置SQL 本身完全不用动。这种设计哲学直接解决了我最头疼的问题当业务复杂度上升时框架不该成为理解 SQL 的障碍而应成为组织 SQL 的脚手架。比如热词里常被问到的“mybatis 中 # 和 的区别”Flex 并不改变这个底层语义但它用QueryWrapper的链式调用把#{}参数化逻辑封装进方法链让你不再需要手动拼写WHERE name #{name}这种易错字符串而{}的原始 SQL 注入能力在 Flex 的select(id, name, ${customSql})中依然保留且 IDE 能对${}内容做语法高亮——这是 MP 做不到的细节。它真正优雅的地方在于把开发者从“框架语法翻译器”的角色解放出来。你看热词里高频出现的“mybatis 分页 怎么设置能全查出来”“mybatis updatebyid 更新值为 null 不更新问题”这些问题在 MP 里要么靠改源码要么靠写一堆 if-else 判断字段非空。而 Flex 的UpdateWrapper直接提供.setIfPresent(phone, user.getPhone())方法.setIfNotBlank(email, user.getEmail())甚至支持.set(status, StatusEnum.ACTIVE.getCode(), user.isForceActive())这种带业务逻辑的条件赋值。这不是炫技是把真实业务场景里的字段更新规则直接映射成可读性强、IDE 可跳转、单元测试可覆盖的 Java 代码。我上个项目里有个“用户资料补全”功能涉及 12 个字段的差异化更新用 MP 写了 47 行 if-else换成 Flex 后压缩到 9 行链式调用且每个.setIfXXX()的参数类型都是编译期检查的再也不用担心user.getPhone()返回空字符串却误更新数据库字段。2. 核心设计思路不碰 MyBatis 内核只在扩展点上做文章2.1 为什么选择“零侵入”而非“重写引擎”很多增强框架包括早期的 MyBatis-Plus走的是“封装替代”路线自己实现一套 Mapper 接口、自定义 SqlSessionFactoryBean、重写 BaseMapper 的通用方法。这条路短期见效快但长期埋雷。我在上家公司维护一个用了 5 年的 MP 项目升级到 MP 4.0 时发现LambdaQueryWrapper的泛型推导机制变了导致所有eq(User::getAge, 18)调用全部编译报错光是修复就花了 3 天——因为 MP 把 Lambda 表达式解析逻辑耦合进了自己的LambdaUtils而 MyBatis 官方从未承诺过 Lambda 解析的 API 稳定性。MyBatis-Flex 的破局点很清醒MyBatis 的核心价值是 SQL 的绝对控制权任何增强都不能以牺牲这个控制权为代价。所以它严格遵循 MyBatis 官方扩展规范所有能力都构建在以下四个标准扩展点上Interceptor 插件机制分页、乐观锁、数据权限拦截都在这里实现。比如分页插件Flex 不像 MP 那样重写PageInterceptor而是基于 MyBatis 原生Executor的query方法拦截在 SQL 解析后、执行前动态注入LIMIT ? OFFSET ?并用BoundSql的sql字段二次解析生成 COUNT 查询。这样做的好处是即使你用Select(SELECT * FROM user WHERE ${dynamicWhere})这种高危写法Flex 的分页依然能工作因为 COUNT 生成逻辑作用于最终的 SQL 字符串而非 XML 的 AST 结构。TypeHandler 扩展字段级加密、枚举自动转换、JSON 字段序列化都靠这个。比如热词里提到的“spring boot mybatis 实现数据库字段级加密”Flex 提供ColumnEncrypt注解背后注册的是AesTypeHandler它在setParameter时调用 AES 加密在getResult时自动解密。关键在于这个 TypeHandler 直接复用 MyBatis 的PreparedStatement.setObject()和ResultSet.getObject()流程不碰ResultMap解析逻辑所以和原生Results映射完全兼容。Configuration 自定义注入mybatis-flex.config文件的加载逻辑就在这里。Flex 会扫描 classpath 下的mybatis-flex-config.yml解析出db-type: mysql、enable-logic-delete: true等配置然后调用configuration.addInterceptor(...)或configuration.getTypeHandlerRegistry().register(...)注册对应组件。这种设计让配置变更无需重启应用——你改完 yml 文件Flex 的ConfigWatcher会监听文件变化触发Configuration的动态刷新比 MP 的MapperScan注解式配置灵活得多。Mapper 接口代理增强QueryWrapper和UpdateWrapper的魔法就发生在这里。Flex 不要求你继承 BaseMapper而是通过Mapper接口的动态代理在invoke方法里拦截selectList(wrapper)这类调用将QueryWrapper解析成MappedStatement的boundSql.sql。整个过程不修改 MyBatis 的MapperProxy只是在其invoke链条上加了一层 Wrapper 解析器。这意味着你混用原生Select和 Flex 的selectList完全无冲突SQL 日志里能看到两种风格的查询并存。提示这种设计带来的最大红利是调试友好性。当你遇到 SQL 异常直接看 MyBatis 的debug日志就能看到 Flex 生成的最终 SQL而不是 MP 那种“先生成 AST再转 SQL中间还夹着 Lambda 解析”的黑盒流程。我在排查“mybatis 小于号转义”问题时MP 的日志显示WHERE age lt; #{minAge}根本看不出是 XML 转义还是框架 Bug而 Flex 的日志直接打印WHERE age ?一眼定位是前端传参没过滤 HTML 实体。2.2 “优雅”的底层实现Wrapper 体系如何避免反射地狱热词里反复出现的“mybatis 动态 sql”痛点在 Flex 中被QueryWrapper和UpdateWrapper彻底重构。传统方案要么写 XML 的if testname ! nullAND name #{name}/if要么用 MP 的QueryWrapper.eq(name, name)。前者维护成本高后者存在严重反射隐患eq(name, name)的字符串 name 是运行时检查的拼错字段名只有到运行时才报org.apache.ibatis.reflection.ReflectionException且 IDE 无法跳转到实体类字段。Flex 的解法是双轨制编译期安全 运行时灵活。它提供两套 APILambda 版本推荐QueryWrapper.of(User.class).eq(User::getName, 张三)。这里User::getName是 Java 方法引用编译期就能校验字段是否存在、类型是否匹配。Flex 内部用SerializedLambda解析方法引用提取出getName对应的字段名 name再生成 SQL。整个过程不依赖反射 APIMethod对象在编译期就固化连 ProGuard 混淆都不影响。字符串版本兼容旧代码QueryWrapper.of(User.class).eq(name, 张三)。这个走的是传统反射路径但 Flex 做了关键优化它缓存了Class.forName(com.xxx.User).getDeclaredField(name)的结果且首次反射失败时会抛出带详细上下文的异常比如Field namme not found in class com.xxx.User, did you mean name?—— 这个提示是 Flex 自己做的模糊匹配不是 JVM 原生异常。更绝的是UpdateWrapper的setIfPresent系列方法。它不是简单判断value ! null而是根据字段类型做精细化处理对String类型调用StringUtils.isNotBlank(value)避免空字符串更新对Integer类型判断value ! null value ! 0可配置是否忽略 0对LocalDateTime类型判断value ! null对自定义枚举调用enumValue ! null enumValue.isValid()需实现Validatable接口。这种设计让“mybatis updatebyid 更新值为 null 不更新问题”变成一行代码解决.setIfPresent(phone, user.getPhone())而不是在 Service 层写一堆if (user.getPhone() ! null !user.getPhone().trim().isEmpty())。3. 实操详解从零搭建一个支持字段加密、分页、逻辑删除的 Flex 项目3.1 环境准备与依赖配置Spring Boot 3.x 兼容版我们以 Spring Boot 3.2 JDK 17 为基准环境。注意Flex 4.x 开始全面支持 Jakarta EE 9所以javax.*包名已全部替换为jakarta.*这点和 MP 4.x 的迁移路径一致。以下是pom.xml关键依赖dependencies !-- Spring Boot Web -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- MyBatis-Spring-Boot-Starter必须用 3.0 -- dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version3.0.3/version /dependency !-- MyBatis-Flex 核心注意 groupId 是 com.mybatisflex -- dependency groupIdcom.mybatisflex/groupId artifactIdmybatisflex-spring-boot-starter/artifactId version4.9.2/version /dependency !-- 数据库驱动以 MySQL 8.0 为例 -- dependency groupIdmysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency !-- Lombok简化实体类 -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies关键点解析mybatis-spring-boot-starter版本必须 ≥3.0.0这是 Spring Boot 3.x 的强制要求低版本会因 Jakarta EE 包名冲突启动失败。我试过用 2.3.x 强行降级结果MapperScan注解失效因为 Spring Boot 3.x 的MapperScannerConfigurer已重构。mybatisflex-spring-boot-starter的 starter 机制它会在spring.factories中声明org.springframework.boot.autoconfigure.EnableAutoConfiguration...MybatisFlexAutoConfiguration这个自动配置类会扫描mybatis-flex-config.yml并初始化FlexDataSource、FlexConfiguration等 Bean。如果你不想用 starter也可以手动配置但会失去Table注解自动建表等便利功能。为什么不用mybatis-flex-core单独引入因为 starter 包含了 Spring 集成所需的FlexTransactionManager、FlexSqlSessionFactoryBean等适配器单独引 core 会导致事务不生效——这是踩过的坑Transactional注解在 Flex 项目里必须配合 starter 的FlexTransactionManager才能正常回滚。3.2 配置文件mybatis-flex-config.yml深度解析Flex 的配置中心是mybatis-flex-config.yml它比 MP 的application.yml中mybatis-plus:配置块更结构化。以下是生产环境推荐配置# mybatis-flex-config.yml global-config: # 主键策略AUTO数据库自增、ASSIGN_ID雪花ID、NONE手动赋值 id-generator: ASSIGN_ID # 逻辑删除字段名默认 deleted可全局统一 logic-delete-field: deleted # 逻辑删除值未删除0已删除1支持布尔值或数字 logic-delete-value: 1 # 未删除值 logic-not-delete-value: 0 db-config: # 数据库类型影响分页方言和函数生成 db-type: mysql # 是否启用主键自动填充INSERT 时自动设 ID enable-id-auto-fill: true # 是否启用字段自动填充如 create_time, update_time enable-field-auto-fill: true plugins: # 分页插件配置 pagination: enabled: true # 默认每页大小 default-page-size: 20 # 最大允许页码防恶意请求 max-page-size: 1000 # 是否统计总记录数大数据量可设为 false count: true # 乐观锁插件 optimistic-lock: enabled: true # 版本字段名 version-field: version # 数据审计插件自动填充 create_time/update_time audit: enabled: true create-time-field: create_time update-time-field: update_time type-handlers: # 自定义类型处理器用于字段级加密 - class: com.example.handler.AesTypeHandler java-type: java.lang.String jdbc-type: VARCHAR # 加密密钥实际项目应从配置中心获取 properties: secret-key: your-32-byte-secret-key-here配置要点说明logic-delete-field必须与实体类TableField注解一致比如你的 User 实体有TableField(deleted) private Integer deleted;这里就必须填deleted。Flex 不会自动推断字段名填错会导致逻辑删除失效。db-type: mysql决定分页 SQL 生成方式Flex 内置了 MySQL、PostgreSQL、Oracle、SQL Server 的分页方言。比如 MySQL 用LIMIT ? OFFSET ?PostgreSQL 用LIMIT ? OFFSET ?Oracle 用ROWNUM嵌套子查询。如果你用的是阿里云 PolarDB兼容 MySQL这里填mysql即可无需额外适配。type-handlers的properties是关键AesTypeHandler需要密钥但密钥不能硬编码在 yml 里。正确做法是创建AesTypeHandlerFactory在create()方法里从Environment获取aes.secret-key配置项再实例化AesTypeHandler。Flex 的type-handlers配置会调用这个工厂类确保密钥安全。3.3 实体类与 Mapper 接口告别 BaseMapper 继承Flex 不要求实体类继承任何父类也不强制 Mapper 接口继承BaseMapper。这是它“零侵入”理念的直接体现。以下是User实体的标准写法import com.mybatisflex.annotation.*; import lombok.Data; import lombok.EqualsAndHashCode; import java.time.LocalDateTime; Data EqualsAndHashCode(callSuper false) Table(sys_user) // 指定表名不加此注解则默认驼峰转下划线 public class User { Key(type KeyType.GENERATE) // 主键策略GENERATE 对应 ASSIGN_ID private Long id; Column(username) private String username; Column(password) ColumnEncrypt // 启用字段级加密由 AesTypeHandler 处理 private String password; Column(email) private String email; Column(deleted) LogicDelete // 标记逻辑删除字段 private Integer deleted; Column(version) Version // 乐观锁字段 private Integer version; Column(create_time) private LocalDateTime createTime; Column(update_time) private LocalDateTime updateTime; }Mapper 接口只需Mapper注解无需继承import com.mybatisflex.core.mapper.Mapper; import org.apache.ibatis.annotations.Mapper; Mapper public interface UserMapper extends MapperUser { // 这里可以写原生 Select、UpdateFlex 不干涉 Select(SELECT * FROM sys_user WHERE status #{status}) ListUser selectByStatus(Param(status) Integer status); // 也可以用 Flex 的 Wrapper 方法 ListUser selectList(QueryWrapper wrapper); }注意MapperUser是 Flex 提供的泛型接口它只包含insert,updateById,deleteById等基础方法不包含selectList。selectList是 Flex 通过动态代理注入的所以你必须在UserMapper接口中声明这个方法否则代理找不到入口。这是和 MP 的关键区别——MP 的BaseMapper是抽象类所有方法都已定义Flex 的MapperT是接口你需要显式声明要使用的方法。3.4 核心功能实操分页、字段加密、逻辑删除一气呵成我们写一个完整的 Service 方法演示如何用 Flex 实现“查询未删除用户列表支持分页密码字段自动加密存储”import com.mybatisflex.core.query.QueryWrapper; import com.mybatisflex.core.update.UpdateWrapper; import com.mybatisflex.core.row.Row; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.List; Service public class UserService { Resource private UserMapper userMapper; /** * 分页查询未删除用户 * param page 当前页码从1开始 * param size 每页数量 * return 分页结果 */ public PageUser listUsers(int page, int size) { // 构建 QueryWrapper自动添加逻辑删除条件 QueryWrapper wrapper QueryWrapper.of(User.class) .select(id, username, email, create_time) // 指定查询字段避免查 password .eq(deleted, 0); // 显式条件也可省略因 LogicDelete 已自动注入 // Flex 的分页是 Page 对象不是 MP 的 IPage return userMapper.paginate(page, size, wrapper); } /** * 创建用户密码自动加密 */ public void createUser(User user) { // 密码字段标注了 ColumnEncrypt插入时自动 AES 加密 userMapper.insert(user); } /** * 更新用户信息仅更新非空字段 */ public void updateUser(Long id, User updateUser) { UpdateWrapper wrapper UpdateWrapper.of(User.class) .setIfPresent(username, updateUser.getUsername()) .setIfPresent(email, updateUser.getEmail()) .setIfNotBlank(password, updateUser.getPassword()) // 密码为空字符串时不更新 .set(update_time, LocalDateTime.now()) .eq(id, id) .eq(version, updateUser.getVersion()); // 乐观锁条件 int updated userMapper.update(wrapper); if (updated 0) { throw new RuntimeException(更新失败可能版本号不匹配); } } /** * 逻辑删除用户 */ public void deleteUser(Long id) { UpdateWrapper wrapper UpdateWrapper.of(User.class) .set(deleted, 1) .set(update_time, LocalDateTime.now()) .eq(id, id); userMapper.update(wrapper); } }关键细节拆解paginate(page, size, wrapper)的返回值是PageT这个Page类包含list,pageNo,pageSize,totalRow,totalPage字段比 MP 的IPage更轻量且totalRow是long类型避免大数据量时int溢出。select(id, username, ...)的字段白名单机制这不仅是性能优化避免查大字段如 password更是安全加固。Flex 会校验这些字段是否存在于User实体类中如果写select(id, credit_card)而User没有creditCard字段启动时就会报错而不是运行时 SQL 异常。setIfNotBlank(password, ...)的字符串判空逻辑它调用的是StringUtils.isNotBlank()即同时检查null、空字符串、纯空白字符。这解决了热词里“mybatis updatebyid 更新值为 null 不更新问题”的变体——空字符串也不更新。乐观锁的eq(version, updateUser.getVersion())这是标准用法Flex 会在 UPDATE SQL 中生成WHERE id ? AND version ?更新后自动将version字段 1。如果并发更新导致 version 不匹配update()返回 0你可以据此抛出业务异常。3.5 高级技巧动态 SQL 与 SQL 扫描器实战热词里高频出现的“mybatis sql scanner”“mybatis动态sql”在 Flex 中有更现代的解法。Flex 提供SqlTemplate工具类支持类似 Thymeleaf 的模板语法且 IDE 可识别// 在 Service 中 public ListUser searchUsers(String keyword, Integer status) { // 使用 SqlTemplate 构建动态 SQL String sql SqlTemplate.select(u.*) .from(sys_user u) .where(u.deleted 0) .andIf(keyword ! null, () - AND (u.username LIKE #{keyword} OR u.email LIKE #{keyword})) .andIf(status ! null, () - AND u.status #{status}) .orderBy(u.create_time DESC) .build(); // 直接执行返回 ListMapString, Object return userMapper.selectListBySql(sql, Row.of(keyword, % keyword %), Row.of(status, status)); }SqlTemplate的优势类型安全#{keyword}中的keyword是字符串字面量IDE 可以检查变量名拼写SQL 高亮IntelliJ IDEA 的 Database Tools 插件能识别SqlTemplate生成的 SQL提供语法高亮和表字段提示防 SQL 注入所有#{}参数都走 PreparedStatement${}仅用于表名/字段名需白名单校验。对于更复杂的动态 SQLFlex 支持SelectProviderSelectProvider(type UserSqlProvider.class, method buildSearchSql) ListUser searchUsers(Param(keyword) String keyword, Param(status) Integer status); // UserSqlProvider.java public class UserSqlProvider { public static String buildSearchSql(MapString, Object params) { String keyword (String) params.get(keyword); Integer status (Integer) params.get(status); StringBuilder sql new StringBuilder(SELECT * FROM sys_user WHERE deleted 0); if (keyword ! null !keyword.trim().isEmpty()) { sql.append( AND (username LIKE ).append(SqlTemplate.likeParam(keyword)).append( OR email LIKE ).append(SqlTemplate.likeParam(keyword)).append()); } if (status ! null) { sql.append( AND status #{status}); } sql.append( ORDER BY create_time DESC); return sql.toString(); } }这里SqlTemplate.likeParam(keyword)会生成#{keyword}确保参数化。而#{status}是标准 MyBatis 语法完全兼容。4. 常见问题与避坑指南来自 3 个生产项目的血泪总结4.1 分页性能陷阱COUNT 查询慢的 3 种根因与解法热词里“mybatis 分页 怎么设置能全查出来”背后其实是 COUNT 查询性能问题。我在三个项目中遇到过典型场景场景现象根因Flex 解决方案多表 JOIN 分页SELECT * FROM user u LEFT JOIN dept d ON u.dept_id d.id的 COUNT 查询耗时 2sMyBatis 分页插件对 JOIN SQL 的 COUNT 生成逻辑是SELECT COUNT(*) FROM (原始SQL) tmp导致子查询全表扫描Flex 提供CountSql注解CountSql(SELECT COUNT(*) FROM sys_user u LEFT JOIN sys_dept d ON u.dept_id d.id WHERE u.deleted 0)手动指定高效 COUNT SQL绕过自动解析大数据量 COUNT用户表 5000 万行分页第 1 页就超时MySQL 的COUNT(*)在 InnoDB 中需遍历索引树Flex 的PaginationPlugin支持count: false关闭 COUNT前端用hasNext判断下一页或用EXPLAIN估算行数WHERE 条件含函数WHERE DATE(create_time) 2023-01-01导致 COUNT 无法走索引函数索引在 MySQL 5.7 才支持旧版本 COUNT 全表扫描Flex 的QueryWrapper支持between(create_time, start, end)生成create_time ? AND create_time ?可走索引实操心得在mybatis-flex-config.yml中设置plugins.pagination.count: false后paginate()返回的Page.totalRow为 -1前端需适配。我们项目采用“滚动分页”方案不显示总页数只提供“下一页”按钮用lastId作为游标SQL 改为WHERE id #{lastId} ORDER BY id LIMIT 20性能提升 10 倍。4.2 字段加密的密钥管理与性能瓶颈热词“spring boot mybatis实现数据库字段级加密”看似简单实操中密钥泄露和加解密开销是两大雷区密钥硬编码风险mybatis-flex-config.yml中的secret-key绝对不能明文提交 Git。正确做法是在application.yml中配置aes.secret-key: ${AES_SECRET_KEY:default-key}启动时通过-DASE_SECRET_KEYxxx或环境变量注入AesTypeHandlerFactory从Environment获取该属性创建AesTypeHandler。加解密性能问题AES 加密本身很快但TypeHandler的setParameter和getResult是同步阻塞调用。当批量查询 1000 条用户每条含 3 个加密字段就会触发 3000 次 AES 运算CPU 占用飙升。解决方案缓存解密结果AesTypeHandler内部用ConcurrentHashMapString, String缓存已解密的密文key 为密文 SHA256避免重复解密异步解密对非敏感字段如邮箱改用ColumnDecrypt(async true)解密操作放到CompletableFuture中主线程不阻塞字段分级密码字段必须加密手机号用国密 SM4邮箱用 AES-128降低密钥管理复杂度。4.3 逻辑删除与关联查询的坑JOIN 时 deleted 条件漏写这是最隐蔽的 Bug。假设Order表关联User表Order也有deleted字段。当查询订单列表时如果只在Order的QueryWrapper中加eq(deleted, 0)而User表的deleted条件没加就会查出已被逻辑删除的用户信息。Flex 的解法是JoinTable注解Table(sys_order) public class Order { Key(type KeyType.GENERATE) private Long id; Column(user_id) private Long userId; JoinTable( table sys_user, joinType JoinType.LEFT, on t.user_id u.id, condition u.deleted 0 // 关联表的逻辑删除条件 ) private User user; }condition u.deleted 0会自动注入到 JOIN ON 子句中生成LEFT JOIN sys_user u ON t.user_id u.id AND u.deleted 0。这样即使User表有逻辑删除关联查询也不会拉取已删除用户。注意JoinTable的condition只对当前 JOIN 生效不会污染其他查询。我在一个报表模块中用它实现了“统计各部门有效用户数”SQL 自动生成COUNT(DISTINCT u.id)且u.deleted 0条件精准命中避免了人工写 SQL 漏条件的事故。4.4 热词高频问题速查表热词问题Flex 解决方案关键代码/配置mybatis 中 # 和 的区别Flex 不改变语义但QueryWrapper的eq()方法内部用#{}select(${field})用${}wrapper.eq(name, 张三)→WHERE name ?wrapper.select(id, name, ${customField})→SELECT id, name, customFieldmybatis配置打印Flex 默认开启logging.level.com.mybatisflexDEBUGSQL 日志在com.mybatisflex.core.dialect.DialectFactory在application.yml中加logging.level.com.mybatisflex: DEBUGmybatis fetchsize1000Flex 的paginate()方法支持fetchSize参数userMapper.paginate(1, 20, wrapper, 1000)paginate(page, size, wrapper, fetchSize)mybatis 通用查询结果列Flex 的selectListBySql()返回ListRowRow是MapString, Object的封装支持row.getString(name),row.getLong(id)ListRow rows userMapper.selectListBySql(SELECT * FROM user);idea引入mybatis插件打开xml文件Flex 不依赖 XML但兼容。安装Free MyBatis Plugin后XML 文件仍可高亮#{}参数可跳转无需特殊配置插件自动识别mybatis小于号转义Flex 的SqlTemplate和Select字符串中直接写不需转义where(age #{maxAge})→WHERE age ?mybatis批量更新batchFlex 的updateBatch()方法userMapper.updateBatch(users, 100)updateBatch(ListT, batchSize)batchSize 控制每次提交条数springboot如何集成mybatis查询数据库表内容Flex 的QueryWrapper支持select(*)或用selectListBySql(SHOW TABLES)userMapper.selectList(QueryWrapper.of(User.class).select(*))5. 进阶实践Flex 与 MyBatis 原生能力的无缝协同5.1 混合编程在同一个项目里何时用 Flex何时用原生 MyBatisFlex 的定位是“增强”不是“替代”。我在架构设计时划定了明确的使用边界优先用 Flex 的场景CRUD 操作占项目 70% 以上selectList,updateById,deleteById等动态条件查询QueryWrapper的链式调用比 XML 的if更易读、易测试字段级处理加密、枚举转换、JSON 序列化用ColumnEncrypt、TableField(typeHandler ...)分页、乐观锁、逻辑删除等横切关注点。必须用原生 MyBatis 的场景复杂报表 SQL涉及多层子查询、窗口函数、UNION ALLFlex 的SqlTemplate表达力不足存储过程调用Select({CALL proc_name(#{param})})Flex本文还有配套的精品资源点击获取
返回列表