news 2026/9/3 5:08:13

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/9/2 14:22:45

90%的老板用人翻车,问题究竟出在哪一步?

问&#xff1a;为什么大多数企业招聘效果不佳&#xff0c;用人频频"翻车"&#xff1f;核心痛点是什么&#xff1f;答&#xff1a;深度调研显示&#xff0c;90%的企业用人问题根源在于缺乏系统化的人才甄别工具&#xff0c;导致招聘决策过度依赖主观经验而非科学数据。…

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

AOP动态代理与静态织入区别详解,哪种方式更适合你的项目?

在软件开发中&#xff0c;AOP&#xff08;面向切面编程&#xff09;是解决横切关注点的重要技术。其中动态代理和静态织入是实现AOP的两种主要方式。作为长期应用这两种技术的架构师&#xff0c;我认为理解它们的区别和适用场景对设计健壮的系统至关重要。 AOP动态代理和静态织…

作者头像 李华
网站建设 2026/9/3 4:05:47

基于Java的市政工程勘察设计智慧管理系统的设计与实现全方位解析:附毕设论文+源代码

1. 为什么这个毕设项目值得你 pick ? 市政工程勘察设计智慧管理系统旨在提升传统管理模式的效率与准确性。相比常规系统&#xff0c;本项目聚焦于具体功能模块构建&#xff0c;如会员管理、字典管理和各类项目及任务管理等&#xff0c;从而实现数据录入快速准确且易于审核的功…

作者头像 李华
网站建设 2026/9/2 21:52:18

2026年国内低代码与AI应用构建工具选型指南

到了2026年&#xff0c;低代码工具早已不是简单的“拖拉拽”弄个表单那么简单了。随着AI Native&#xff08;AI原生&#xff09;趋势的深化&#xff0c;我们构建应用的方式发生了本质变化&#xff1a;以前是人去适应工具的操作逻辑&#xff0c;现在是工具通过AI来理解人的意图。…

作者头像 李华
网站建设 2026/9/2 21:52:28

什么是VDSL

文章目录VDSL和ADSL的区别VDSL工作系统VDSL速率VDSL应用场景有哪些VDSL技术演进VDSL&#xff08;超高速数字用户线路&#xff09;是一种基于传统电话铜线的高速宽带接入技术&#xff0c;属于xDSL&#xff08;数字用户线路&#xff09;家族的重要成员。VDSL旨在提供比早期ADSL&a…

作者头像 李华