MyBatisPlus高级特性与实战技巧全解析

发布时间:2026/7/21 21:55:57
MyBatisPlus高级特性与实战技巧全解析 1. MyBatisPlus隐藏功能全景扫描作为MyBatis的增强工具MyBatisPlus在日常开发中确实能极大提升效率。但很多开发者仅仅停留在基础CRUD操作忽略了框架提供的诸多高级特性。根据官方文档和实际项目经验我梳理了12个最容易被忽视的实用功能这些功能在特定场景下能发挥惊人效果。注意本文基于MyBatisPlus 3.5.x版本部分特性在旧版本可能不完全支持。建议先通过mybatis-plus.version确认当前项目使用的版本号。1.1 动态表名处理器在多租户系统或分表场景中动态表名是刚需。MyBatisPlus提供了DynamicTableNameInnerInterceptor拦截器通过简单配置即可实现public class MyTableNameHandler implements TableNameHandler { Override public String dynamicTableName(String sql, String tableName) { return t_ TenantContext.getCurrentTenant() _ tableName; } } // 配置拦截器 Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); DynamicTableNameInnerInterceptor dynamicTableNameInterceptor new DynamicTableNameInnerInterceptor(); dynamicTableNameInterceptor.setTableNameHandler(new MyTableNameHandler()); interceptor.addInnerInterceptor(dynamicTableNameInterceptor); return interceptor; }实战技巧结合ThreadLocal保存租户信息避免每次请求都传递参数对于分表场景可以通过日期或ID哈希计算表名后缀注意SQL注入风险动态拼接的表名需要做白名单校验1.2 自动填充策略进阶用法大多数项目只用到了基础的TableField(fill FieldFill.INSERT)其实填充策略可以更灵活public class MyMetaObjectHandler implements MetaObjectHandler { Override public void insertFill(MetaObject metaObject) { // 根据当前字段值决定是否填充 Object status getFieldValByName(status, metaObject); if(status null) { this.strictInsertFill(metaObject, status, String.class, ACTIVE); } // 支持SpEL表达式 this.strictInsertFill(metaObject, createBy, String.class, SecurityUtils.getCurrentUser() # System.getProperty(user.name)); } }避坑指南避免在填充器中进行数据库查询操作可能导致循环依赖对于分布式系统建议填充逻辑幂等且不依赖本地环境变量使用strictInsertFill代替setFieldValByName可以获得更好的类型安全2. 拦截器深度应用技巧2.1 自定义SQL拦截改写通过InnerInterceptor可以拦截执行的SQL语句进行改写这在处理遗留系统或特殊需求时非常有用public class MySqlInterceptor implements InnerInterceptor { Override public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { String sql boundSql.getSql(); // 替换所有is_deleted0为逻辑删除条件 if(sql.contains(where)) { sql sql.replace(where, where is_deleted 0 and); } else { sql where is_deleted 0; } resetSql(ms, boundSql, sql); } private void resetSql(MappedStatement ms, BoundSql boundSql, String newSql) { Field field ReflectUtil.getField(BoundSql.class, sql); ReflectUtil.setFieldValue(boundSql, field, newSql); } }典型应用场景强制添加租户隔离条件统一添加数据权限过滤SQL方言自动转换如MySQL到Oracle敏感字段自动加解密2.2 执行Mapper方法信息获取在拦截器中获取当前执行的Mapper接口和方法信息public class MyInterceptor implements InnerInterceptor { Override public void beforeExecute(Executor executor, MappedStatement ms, Object parameter) { String mapperName ms.getId().substring(0, ms.getId().lastIndexOf(.)); String methodName ms.getId().substring(ms.getId().lastIndexOf(.) 1); // 通过反射获取方法注解 Class? mapperClass Class.forName(mapperName); Method method Arrays.stream(mapperClass.getMethods()) .filter(m - m.getName().equals(methodName)) .findFirst() .orElse(null); if(method ! null method.isAnnotationPresent(DataAuth.class)) { // 处理数据权限逻辑 } } }3. 高级查询功能揭秘3.1 多表关联查询方案虽然MyBatisPlus主打单表操作但通过Wrapper也能实现优雅的多表查询// 使用QueryWrapper实现联表 ListUserDTO list userMapper.selectJoinList( new QueryWrapperUser() .select(u.*, d.dept_name) .eq(u.status, 1) .like(d.dept_name, 技术) .apply(u.dept_id d.id) ); // 对应Mapper方法 Select(select ${ew.sqlSelect} from user u, department d ${ew.customSqlSegment}) ListUserDTO selectJoinList(Param(Constants.WRAPPER) WrapperUser wrapper);性能优化建议对于复杂联表建议还是使用XML方式编写SQL可以使用SqlParser(filtertrue)注解跳过SQL解析大数据量查询时注意关闭MyBatisPlus的自动count优化3.2 自定义TypeHandler处理JSON字段处理数据库JSON类型字段时可以创建通用TypeHandlerMappedTypes({Object.class}) MappedJdbcTypes(JdbcType.VARCHAR) public class JsonTypeHandler extends BaseTypeHandlerObject { private static final ObjectMapper mapper new ObjectMapper(); Override public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) { ps.setString(i, toJson(parameter)); } Override public Object getNullableResult(ResultSet rs, String columnName) { return parse(rs.getString(columnName)); } private String toJson(Object obj) { try { return mapper.writeValueAsString(obj); } catch (JsonProcessingException e) { throw new RuntimeException(e); } } private Object parse(String json) { try { return mapper.readValue(json, Object.class); } catch (IOException e) { throw new RuntimeException(e); } } } // 实体类使用 TableName(autoResultMap true) public class User { TableField(typeHandler JsonTypeHandler.class) private MapString, Object attributes; }4. 扩展机制深度应用4.1 自定义全局方法注入在BaseMapper基础上扩展通用方法public interface MyBaseMapperT extends BaseMapperT { /** * 批量插入MySQL语法 */ Insert(scriptinsert into ${tableName} foreach collectionlist itemitem separator, (#{item.name},#{item.age}) /foreach/script) int mysqlBatchInsert(Param(tableName) String tableName, Param(list) ListT list); /** * 根据ID列表查询并返回Map */ Select(scriptselect * from ${tableName} where id in foreach collectionids itemid open( separator, close)#{id}/foreach/script) MapKey(id) MapLong, T selectMapByIds(Param(tableName) String tableName, Param(ids) ListLong ids); } // 使用自定义SQL注入器 public class MySqlInjector extends DefaultSqlInjector { Override public ListAbstractMethod getMethodList(Class? mapperClass) { ListAbstractMethod methodList super.getMethodList(mapperClass); methodList.add(new MysqlBatchInsert()); methodList.add(new SelectMapByIds()); return methodList; } }4.2 突破单页500条限制默认情况下MyBatisPlus会限制单次查询返回500条记录。可以通过以下方式修改// 1. 全局配置 Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); PaginationInnerInterceptor paginationInterceptor new PaginationInnerInterceptor(); paginationInterceptor.setMaxLimit(1000L); // 修改为1000 interceptor.addInnerInterceptor(paginationInterceptor); return interceptor; } // 2. 单次查询覆盖 PageUser page new Page(1, 1000); page.setMaxLimit(1000L); userMapper.selectPage(page, queryWrapper);性能警告大数据量分页查询应该使用优化方案如游标分页避免在前端一次性请求过多数据考虑使用SqlParser(filtertrue)跳过count查询5. 企业级特性实战5.1 多租户方案完整实现完整的多租户解决方案需要结合多个组件// 租户上下文 public class TenantContext { private static final ThreadLocalString CURRENT_TENANT new ThreadLocal(); public static void setCurrentTenant(String tenant) { CURRENT_TENANT.set(tenant); } public static String getCurrentTenant() { return CURRENT_TENANT.get(); } public static void clear() { CURRENT_TENANT.remove(); } } // 租户拦截器 public class TenantInterceptor implements InnerInterceptor { Override public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { String tenantId TenantContext.getCurrentTenant(); if(StringUtils.isNotBlank(tenantId)) { String sql boundSql.getSql(); sql sql.replace(where, where tenant_id tenantId and); resetSql(ms, boundSql, sql); } } } // 自动填充租户ID public class TenantMetaObjectHandler implements MetaObjectHandler { Override public void insertFill(MetaObject metaObject) { this.strictInsertFill(metaObject, tenantId, String.class, TenantContext.getCurrentTenant()); } }5.2 字段加解密集成敏感字段自动加解密方案public class EncryptTypeHandler extends BaseTypeHandlerString { private static final String KEY your-encryption-key; Override public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) { ps.setString(i, encrypt(parameter)); } Override public String getNullableResult(ResultSet rs, String columnName) { return decrypt(rs.getString(columnName)); } private String encrypt(String content) { // 实现加密逻辑 return encrypted: content; } private String decrypt(String content) { // 实现解密逻辑 return content.replace(encrypted:, ); } } // 实体类使用 public class User { TableField(typeHandler EncryptTypeHandler.class) private String mobile; TableField(typeHandler EncryptTypeHandler.class) private String idCard; }6. 性能优化与监控6.1 SQL执行监控通过拦截器实现SQL监控public class SqlMonitorInterceptor implements InnerInterceptor { Override public void beforeExecute(Executor executor, MappedStatement ms, Object parameter) { long start System.currentTimeMillis(); RequestContextHolder.getRequestAttributes() .setAttribute(sql_start_time, start, RequestAttributes.SCOPE_REQUEST); } Override public void afterExecute(Executor executor, MappedStatement ms, Object parameter, Object result) { long end System.currentTimeMillis(); long start (long) RequestContextHolder.getRequestAttributes() .getAttribute(sql_start_time, RequestAttributes.SCOPE_REQUEST); if(end - start 1000) { // 慢SQL阈值1秒 log.warn(Slow SQL detected: {}ms - {}, (end - start), ms.getId()); } } }6.2 二级缓存优化MyBatisPlus与MyBatis二级缓存整合Configuration public class MybatisPlusCacheConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); // 必须先添加缓存拦截器 interceptor.addInnerInterceptor(new CachingInnerInterceptor()); // 其他拦截器 interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); return interceptor; } Bean public CacheKeyGenerator cacheKeyGenerator() { return (target, method, params) - { StringBuilder key new StringBuilder(); key.append(target.getClass().getSimpleName()).append(:); key.append(method.getName()).append(:); for (Object param : params) { if(param ! null) { if(param instanceof BaseQuery) { key.append(((BaseQuery)param).cacheKey()); } else { key.append(param.toString()); } } key.append(|); } return key.toString(); }; } }7. 与SpringBoot深度集成7.1 自动配置扩展自定义MyBatisPlus自动配置AutoConfigureAfter(MybatisPlusAutoConfiguration.class) public class MyMybatisPlusAutoConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor( ListInnerInterceptor innerInterceptors) { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); innerInterceptors.forEach(interceptor::addInnerInterceptor); return interceptor; } Bean public InnerInterceptor tenantInterceptor() { return new TenantInterceptor(); } Bean public InnerInterceptor sqlMonitorInterceptor() { return new SqlMonitorInterceptor(); } }7.2 多数据源集成结合dynamic-datasource实现多数据源Configuration MapperScan(basePackages com.xxx.mapper) public class DataSourceConfig { Bean ConfigurationProperties(spring.datasource.druid.master) public DataSource masterDataSource() { return DruidDataSourceBuilder.create().build(); } Bean ConfigurationProperties(spring.datasource.druid.slave) public DataSource slaveDataSource() { return DruidDataSourceBuilder.create().build(); } Bean public DataSource dynamicDataSource() { MapObject, Object dataSourceMap new HashMap(); dataSourceMap.put(master, masterDataSource()); dataSourceMap.put(slave, slaveDataSource()); DynamicDataSource dynamicDataSource new DynamicDataSource(); dynamicDataSource.setDefaultTargetDataSource(masterDataSource()); dynamicDataSource.setTargetDataSources(dataSourceMap); return dynamicDataSource; } Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); // 注意分页拦截器需要放在最前面 interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); // 多租户拦截器 interceptor.addInnerInterceptor(new TenantInterceptor()); return interceptor; } }8. 生产环境最佳实践8.1 代码生成器定制定制化代码生成器模板public class MyGenerator { public static void main(String[] args) { AutoGenerator generator new AutoGenerator(); // 全局配置 GlobalConfig gc new GlobalConfig(); gc.setOutputDir(System.getProperty(user.dir) /src/main/java); gc.setAuthor(yourname); gc.setOpen(false); gc.setSwagger2(true); generator.setGlobalConfig(gc); // 数据源配置 DataSourceConfig dsc new DataSourceConfig(); dsc.setUrl(jdbc:mysql://localhost:3306/test?useSSLfalse); dsc.setDriverName(com.mysql.cj.jdbc.Driver); dsc.setUsername(root); dsc.setPassword(123456); generator.setDataSource(dsc); // 包配置 PackageConfig pc new PackageConfig(); pc.setParent(com.example); pc.setEntity(domain); pc.setMapper(dao); generator.setPackageInfo(pc); // 策略配置 StrategyConfig strategy new StrategyConfig(); strategy.setNaming(NamingStrategy.underline_to_camel); strategy.setColumnNaming(NamingStrategy.underline_to_camel); strategy.setEntityLombokModel(true); strategy.setRestControllerStyle(true); strategy.setInclude(user, role); // 生成的表 strategy.setControllerMappingHyphenStyle(true); strategy.setTablePrefix(t_); // 表前缀 generator.setStrategy(strategy); // 自定义模板 TemplateConfig templateConfig new TemplateConfig(); templateConfig.setEntity(templates/entity.java); templateConfig.setMapper(templates/mapper.java); generator.setTemplate(templateConfig); generator.execute(); } }8.2 生产环境配置清单推荐的生产环境配置mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl cache-enabled: true default-executor-type: REUSE local-cache-scope: STATEMENT global-config: db-config: id-type: ASSIGN_ID logic-delete-field: isDeleted logic-not-delete-value: 0 logic-delete-value: 1 banner: false mapper-locations: classpath*:mapper/**/*.xml关键配置说明log-impl: 使用SLF4J代替StdOutImpldefault-executor-type: REUSE比SIMPLE性能更好local-cache-scope: STATEMENT级别避免内存泄漏banner: 生产环境关闭启动banner9. 常见问题解决方案9.1 类型处理器不生效问题现象TableField(typeHandler XXXTypeHandler.class)配置后不生效排查步骤检查实体类是否添加TableName(autoResultMap true)确认TypeHandler是否实现了正确的泛型类型检查MyBatis配置中是否注册了该TypeHandler对于查询操作确认ResultMap是否正确生成9.2 分页查询性能问题优化方案对于大数据量分页使用SqlParser(filtertrue)跳过count查询考虑使用游标分页代替传统分页Select(select * from user where #{ew.sqlSegment}) Options(resultSetType ResultSetType.FORWARD_ONLY, fetchSize 1000) ResultType(User.class) void selectByCursor(Param(Constants.WRAPPER) WrapperUser wrapper, ResultHandlerUser handler);添加合适的索引特别是order by字段9.3 Lambda表达式NPE问题安全写法// 不安全的写法 queryWrapper.lambda().eq(User::getName, user.getName()); // 安全的写法 queryWrapper.lambda() .eq(user ! null user.getName() ! null, User::getName, user.getName()) .eq(User::getStatus, 1);10. 与MyBatis的混合使用策略10.1 XML与注解混合开发推荐的项目结构src/main/java ├── com.xxx.mapper │ ├── UserMapper.java // 接口定义 │ └── CustomMapper.java // 自定义扩展接口 └── com.xxx.domain └── User.java // 实体类 src/main/resources ├── mapper │ ├── UserMapper.xml // 复杂SQL │ └── CustomMapper.xml // 自定义SQL └── application.yml最佳实践简单CRUD使用MyBatisPlus提供的方法复杂查询、多表操作使用XML方式通过MapperScan同时扫描接口和XML10.2 自定义SQL注入扩展BaseMapper功能public interface AllSqlInjector extends ISqlInjector { Override void inspectInject(MapperBuilderAssistant builderAssistant, Class? mapperClass) { // 先注入BaseMapper的方法 super.inspectInject(builderAssistant, mapperClass); // 注入自定义方法 addSelectMysqlPage(builderAssistant, mapperClass); } private void addSelectMysqlPage(MapperBuilderAssistant assistant, Class? mapperClass) { SqlSource sqlSource languageDriver.createSqlSource( configuration, select * from ${tableName} ${ew.customSqlSegment}, Object.class); String methodName selectMysqlPage; assistant.addMappedStatement( methodName, sqlSource, StatementType.PREPARED, SqlCommandType.SELECT, null, null, null, null, null, null, null, true, true, false, null, null, null, configuration.getDatabaseId(), languageDriver, null); } }11. 插件生态整合11.1 MybatisX插件高级用法IntelliJ IDEA的MybatisX插件提供了强大功能从表结构生成实体类支持Lombok、Swagger等XML与Mapper接口方法快速跳转SQL自动补全与语法检查一键生成CRUD方法实用技巧使用AltEnter快速生成TableId注解通过Generate MyBatis Sql动作生成复杂查询配置自定义模板生成DTO、VO等类11.2 Mybatis-Mate企业组件Mybatis-Mate提供了更多企业级特性dependency groupIdcom.baomidou/groupId artifactIdmybatis-mate-annotation/artifactId version1.2.5/version /dependency包含功能数据权限行级、列级字段加密AES、RSA等数据审计创建人、修改人等数据绑定枚举转换等12. 未来版本特性预览虽然MyBatisPlus保持API稳定性但一些新特性值得关注响应式编程支持与Spring WebFlux深度集成GraalVM原生镜像支持提升启动速度和内存效率增强的分布式事务与Seata更深度整合更智能的代码生成基于DDL自动生成领域模型对于生产环境建议保持对1-2个次要版本的跟进既享受新特性又避免兼容性问题。可以通过官方GitHub的Milestone了解路线图。