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 告警 + 缓存防护,确保高并发场景下的稳定运行。
相关推荐
love530love32 分钟前
【ComfyUI】解决 ModuleNotFoundError: No module named ‘inference_core_nodes‘ 问题
人工智能·windows·python·comfyui·inference-core
宇木灵1 小时前
C语言基础-十、文件操作
c语言·开发语言·学习
badwomen__2 小时前
MOV 指令的数据流向
服务器·性能优化
云泽8082 小时前
C++ 多态入门:虚函数、重写、虚析构及 override/final 实战指南(附腾讯面试题)
开发语言·c++
亚亚的学习和分享2 小时前
python基础语法----条件语句
python
yanghuashuiyue3 小时前
lambda+sealed+record
java·开发语言
国科安芯3 小时前
基于RISC-V架构的抗辐照MCU在空间EDFA控制单元中的可靠性分析
单片机·嵌入式硬件·性能优化·架构·risc-v·安全性测试
Zzz 小生3 小时前
LangChain Streaming-Overview:流式处理使用完全指南
人工智能·python·语言模型·langchain·github
yzx9910133 小时前
Python数据结构入门指南:从基础到实践
开发语言·数据结构·python
百锦再4 小时前
Jenkins 全面精通指南:从入门到脚本大师
运维·后端·python·servlet·django·flask·jenkins