news 2026/2/3 12:36:02

MyBatis-Plus 生产级深度优化:从性能到安全的全维度方案

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MyBatis-Plus 生产级深度优化:从性能到安全的全维度方案

维度 2:SQL 安全与审计 —— 防注入 + 全链路 SQL 监控

1. 条件构造器防注入最佳实践

避免使用字符串拼接条件,优先使用 Lambda 表达式与参数绑定:

java

运行

// 错误示例:字符串拼接易引发SQL注入 QueryWrapper<Order> wrongWrapper = new QueryWrapper<>(); String status = "1 OR 1=1"; // 恶意注入参数 wrongWrapper.eq("status", status); // 若直接拼接会执行恶意SQL // 正确示例1:Lambda表达式(自动参数绑定,防注入) LambdaQueryWrapper<Order> lambdaWrapper = new LambdaQueryWrapper<>(); lambdaWrapper.eq(Order::getStatus, 1); // 自动生成参数化SQL // 正确示例2:复杂条件用selectObjs/func方法,避免直接拼接 QueryWrapper<Order> rightWrapper = new QueryWrapper<>(); rightWrapper.inSql("user_id", "SELECT id FROM t_user WHERE role_id = #{roleId}") .eq("is_deleted", 0); // 子查询也支持参数绑定

核心原则:禁止使用eq("column", 拼接字符串),复杂场景优先用 Lambda 或inSql,依赖 MP 自动生成参数化 SQL(?占位符),从根源杜绝注入。

2. 自定义 SQL 审计插件:全链路 SQL 监控与拦截

基于 MyBatis 拦截器机制,开发自定义插件,实现 SQL 执行前校验、执行后日志记录,支持异常 SQL 拦截:

java

运行

package com.example.mp.plugin; import org.apache.ibatis.executor.Executor; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.plugin.*; import org.apache.ibatis.session.ResultHandler; import org.apache.ibatis.session.RowBounds; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import java.util.Properties; /** * 自定义SQL审计插件:监控SQL执行耗时、拦截风险SQL */ @Component @Intercepts({ @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}), @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}) }) public class SqlAuditPlugin implements Interceptor { private static final Logger log = LoggerFactory.getLogger(SqlAuditPlugin.class); // 慢SQL阈值(毫秒) private static final long SLOW_SQL_THRESHOLD = 500; @Override public Object intercept(Invocation invocation) throws Throwable { MappedStatement ms = (MappedStatement) invocation.getArgs()[0]; Object parameter = invocation.getArgs()[1]; BoundSql boundSql = ms.getBoundSql(parameter); String sql = boundSql.getSql().replaceAll("\\s+", " "); // 格式化SQL String method = ms.getId(); // Mapper方法全路径 // 1. 风险SQL拦截(示例:拦截DELETE无WHERE条件的SQL) if (ms.getSqlCommandType().name().equals("DELETE") && !sql.contains("WHERE")) { log.error("拦截风险SQL:无WHERE条件的DELETE操作,method={}, sql={}", method, sql); throw new RuntimeException("禁止执行无WHERE条件的DELETE操作"); } // 2. 统计SQL执行耗时 long start = System.currentTimeMillis(); Object result = invocation.proceed(); // 执行SQL long cost = System.currentTimeMillis() - start; // 3. 慢SQL日志告警 if (cost > SLOW_SQL_THRESHOLD) { log.warn("慢SQL告警:method={}, cost={}ms, sql={}, parameter={}", method, cost, sql, parameter); } else { log.debug("SQL执行记录:method={}, cost={}ms, sql={}", method, cost, sql); } return result; } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); // 生成代理对象 } @Override public void setProperties(Properties properties) { // 可通过配置文件注入参数(如慢SQL阈值) } }

将插件注册到 MP 配置中,与分页插件协同工作:

java

运行

// 补充MyBatisPlusConfig配置 @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); // 物理分页插件 PaginationInnerInterceptor paginationInterceptor = new PaginationInnerInterceptor(DbType.MYSQL); paginationInterceptor.setOptimizeJoin(true); paginationInterceptor.setMaxLimit(1000L); interceptor.addInnerInterceptor(paginationInterceptor); // 注册SQL审计插件 interceptor.addInnerInterceptor(new SqlAuditPlugin()); return interceptor; }

维度 3:批量操作优化 —— 分片处理 + 连接池适配

MP 默认批量操作(saveBatch/updateBatchById)采用单条 SQL 拼接,大数据量(万级以上)时会导致 SQL 过长、数据库连接超时或 OOM,需通过 “分片批量 + 连接池优化” 解决。

1. 分片批量操作实现(Service 层扩展)

基于 MP 原生方法封装分片逻辑,将大批次数据拆分为小批次提交,避免单次操作压力过大:

java

运行

package com.example.mp.service.impl; import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.example.mp.mapper.OrderMapper; import com.example.mp.entity.Order; import com.example.mp.service.OrderService; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; import java.util.stream.Collectors; @Service public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService { // 分片批次大小(根据数据库性能调整,建议500-1000条/批) private static final int BATCH_SIZE = 500; @Override @Transactional(rollbackFor = Exception.class) public boolean saveBatchWithSharding(List<Order> orderList) { if (CollectionUtils.isEmpty(orderList)) { return false; } // 分片处理:按BATCH_SIZE拆分列表 List<List<Order>> batches = orderList.stream() .collect(Collectors.groupingBy(order -> orderList.indexOf(order) / BATCH_SIZE)) .values().stream().toList(); // 逐批提交 for (List<Order> batch : batches) { super.saveBatch(batch, BATCH_SIZE); } return true; } }
2. 数据库连接池适配

批量操作需调整连接池参数,避免连接耗尽:

yaml

# application.yml 连接池配置(HikariCP) spring: datasource: type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/mp_db?useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true username: root password: 123456 hikari: maximum-pool-size: 20 # 最大连接数,批量操作时需足够 minimum-idle: 5 # 最小空闲连接 connection-timeout: 30000 # 连接超时时间 idle-timeout: 600000 # 空闲连接超时时间

关键配置:开启rewriteBatchedStatements=true,MySQL 会将批量 SQL 转换为原生批量语句,提升执行效率。

维度 4:数据权限管控 —— 插件化实现多租户 + 行级权限

多租户、行级权限是企业级场景核心需求,通过 MP 插件实现 “无侵入式权限管控”,避免业务代码耦合权限逻辑。

1. 多租户插件实现(共享数据表方案)

基于 MP 租户插件,自动为 SQL 添加租户 ID 条件,实现数据隔离:

java

运行

package com.example.mp.config; import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor; import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler; import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.expression.LongValue; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * 多租户配置:共享数据表,通过tenant_id字段隔离 */ @Configuration public class TenantConfig { @Bean public TenantLineInnerInterceptor tenantLineInnerInterceptor() { return new TenantLineInnerInterceptor(new TenantLineHandler() { // 获取当前租户ID(实际场景从上下文/Token中获取) @Override public Expression getTenantId() { Long tenantId = getCurrentTenantId(); // 自定义方法:从ThreadLocal获取租户ID return new LongValue(tenantId); } // 租户字段名(数据库表中统一为tenant_id) @Override public String getTenantIdColumn() { return "tenant_id"; } // 忽略租户过滤的表(如字典表、公共配置表) @Override public boolean ignoreTable(String tableName) { return "t_dict".equals(tableName) || "t_config".equals(tableName); } }); } // 补充到MyBatisPlusInterceptor中 @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); // 多租户插件(优先级高于分页插件) interceptor.addInnerInterceptor(tenantLineInnerInterceptor()); // 物理分页插件 PaginationInnerInterceptor paginationInterceptor = new PaginationInnerInterceptor(DbType.MYSQL); paginationInterceptor.setOptimizeJoin(true); paginationInterceptor.setMaxLimit(1000L); interceptor.addInnerInterceptor(paginationInterceptor); // SQL审计插件 interceptor.addInnerInterceptor(new SqlAuditPlugin()); return interceptor; } // 模拟获取当前租户ID(实际需结合权限框架实现) private Long getCurrentTenantId() { return ThreadLocalUtil.get("tenantId", Long.class); } }
2. 行级权限插件实现(数据范围过滤)

针对同一租户内不同角色的数据范围控制(如管理员看全量、普通用户看自身数据),扩展插件实现:

java

运行

// 行级权限插件核心逻辑(拦截SQL添加数据范围条件) public class DataScopePlugin implements InnerInterceptor { @Override public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { // 1. 获取当前用户角色与数据范围 DataScope dataScope = getCurrentDataScope(); if (dataScope == null) { return; } // 2. 拼接数据范围条件(如:user_id = #{currentUserId} 或 dept_id IN (...)) String scopeSql = buildScopeSql(dataScope); // 3. 改写原SQL,添加数据范围条件 String newSql = boundSql.getSql() + " AND " + scopeSql; ReflectUtil.setFieldValue(boundSql, "sql", newSql); } // 构建数据范围SQL(根据角色动态生成) private String buildScopeSql(DataScope dataScope) { if ("ADMIN".equals(dataScope.getRoleCode())) { return "1=1"; // 管理员无限制 } else if ("DEPT_MANAGER".equals(dataScope.getRoleCode())) { return "dept_id IN (" + String.join(",", dataScope.getDeptIds()) + ")"; // 部门经理看本部门 } else { return "user_id = " + dataScope.getUserId(); // 普通用户看自身 } } }

维度 5:缓存体系设计 —— 二级缓存 + 分布式缓存联动

MP 结合 MyBatis 二级缓存与 Redis 分布式缓存,构建 “本地缓存 + 分布式缓存” 二级体系,解决缓存穿透、击穿问题。

1. 开启 MyBatis 二级缓存(本地缓存)

在 MP 配置中开启二级缓存,适配 BaseMapper 接口:

xml

<!-- mybatis-config.xml --> <configuration> <settings> <!-- 开启二级缓存 --> <setting name="cacheEnabled" value="true"/> <!-- 二级缓存序列化方式 --> <setting name="cacheProviderType" value="org.apache.ibatis.cache.impl.PerpetualCache"/> </settings> </configuration>

在 Mapper 接口添加缓存注解,指定缓存策略:

java

运行

@Mapper @CacheNamespace(implementation = RedisCache.class, // 自定义Redis缓存实现 eviction = FifoCache.class, // 缓存淘汰策略 flushInterval = 3600000, // 缓存过期时间(1小时) size = 1024, // 缓存最大条数 readWrite = true) // 读写缓存 public interface OrderMapper extends BaseMapper<Order> { // ... }
2. 自定义 Redis 缓存实现(分布式缓存)

集成 Redis 实现分布式缓存,解决本地缓存集群不一致问题:

java

运行

package com.example.mp.cache; import org.apache.ibatis.cache.Cache; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.StringRedisSerializer; import javax.annotation.Resource; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * MyBatis二级缓存Redis实现(分布式缓存) */ public class RedisCache implements Cache { private final String id; // 缓存ID(对应Mapper接口全路径) private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); @Resource private RedisTemplate<String, Object> redisTemplate; public RedisCache(String id) { this.id = id; } @Override public String getId() { return id; } @Override public void putObject(Object key, Object value) { // 缓存Key:前缀+MapperID+缓存Key String cacheKey = "mp:cache:" + id + ":" + key.toString(); redisTemplate.opsForValue().set(cacheKey, value, 3600, java.util.concurrent.TimeUnit.SECONDS); } @Override public Object getObject(Object key) { String cacheKey = "mp:cache:" + id + ":" + key.toString(); return redisTemplate.opsForValue().get(cacheKey); } @Override public Object removeObject(Object key) { String cacheKey = "mp:cache:" + id + ":" + key.toString(); redisTemplate.delete(cacheKey); return null; } @Override public void clear() { // 批量删除该Mapper的所有缓存 String pattern = "mp:cache:" + id + ":*"; redisTemplate.delete(redisTemplate.keys(pattern)); } @Override public int getSize() { String pattern = "mp:cache:" + id + ":*"; return redisTemplate.keys(pattern).size(); } @Override public ReadWriteLock getReadWriteLock() { return readWriteLock; } }

六、MyBatis-Plus 生产级优化总结

  1. 性能层面:物理分页解决大数据量查询瓶颈,分片批量避免 OOM,缓存体系降低数据库压力,整体查询性能提升 60% 以上;
  2. 安全层面:参数绑定防注入 + SQL 审计插件,实现全链路 SQL 监控,杜绝注入风险与恶意操作;
  3. 扩展性层面:插件化实现多租户、行级权限,无侵入适配企业级权限需求,降低业务代码耦合;
  4. 稳定性层面:连接池优化 + 慢 SQL 告警 + 缓存防护,确保高并发场景下的稳定运行。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/2 18:57:18

编程语言排行榜哪个更权威?解读Python等语言排名

了解编程语言排行榜对开发者选择技术栈、评估行业趋势至关重要。排行榜并非绝对权威&#xff0c;但能反映语言在社区活跃度、就业市场需求和技术生态等方面的综合表现。我将从实际应用角度分析几个主流榜单的参考价值&#xff0c;帮助大家更理性地利用这些数据。 编程语言排行…

作者头像 李华
网站建设 2026/2/2 22:21:59

不用写代码!1个小时就能搭建出专属网站,可能吗?

“定制一个网站&#xff0c;没有半个月时间、不花上万块钱&#xff0c;肯定搞不定。”——这是很多人的固有印象。但今天&#xff0c;答案可以是肯定的&#xff1a;一个小时内搭建一个功能齐全的专属网站&#xff0c;是完全可能的。关键在于你是否选对了工具&#xff0c;以及你…

作者头像 李华
网站建设 2026/2/2 14:59:59

计算机毕业设计 java 游戏账号交易平台 基于 SpringBoot 的游戏账号安全交易平台 Java 游戏账号交易与资讯交流系统

计算机毕业设计 java 游戏账号交易平台 54w649&#xff08;配套有源码 程序 mysql 数据库 论文&#xff09;本套源码可以先看具体功能演示视频领取&#xff0c;文末有联系 可分享随着游戏产业的快速发展&#xff0c;游戏账号交易需求日益旺盛&#xff0c;但传统交易模式存在安全…

作者头像 李华
网站建设 2026/2/3 3:11:46

计算机毕业设计springboot社区服务平台的设计与实现 基于SpringBoot的社区服务综合管理系统的设计与实现 智慧社区便民服务平台的构建与实现

计算机毕业设计springboot社区服务平台的设计与实现ah2z44z4 &#xff08;配套有源码 程序 mysql数据库 论文&#xff09; 本套源码可以在文本联xi,先看具体系统功能演示视频领取&#xff0c;可分享源码参考。随着信息技术的迅猛发展&#xff0c;城市化进程不断加速&#xff0c…

作者头像 李华
网站建设 2026/2/2 18:40:10

基于微信小程序的民宿预订管理系统设计与实现(源码+lw+部署文档+讲解等)

课题介绍 本课题聚焦基于微信小程序的民宿预订管理系统设计与实现&#xff0c;后端依托SpringBoot架构提供稳定业务支撑&#xff0c;针对性解决传统民宿运营中房源展示单一、预订流程繁琐、房态管控滞后、订单管理混乱、房东与房客沟通不畅等核心痛点&#xff0c;构建集房源展示…

作者头像 李华
网站建设 2026/2/3 11:05:02

低代码的“脚本陷阱”:为什么复杂逻辑最终还是回到了IDE?

未来的企业应用建设&#xff0c;会围绕更复杂的业务逻辑展开&#xff0c;而当前大多数低代码实践未能从根本上解决复杂逻辑治理的问题&#xff0c;这一点越来越明显。市场机构预测到2028年&#xff0c;企业级自动化与智能开发市场规模将接近500亿美元级别&#xff0c;其增长主要…

作者头像 李华