Spring Boot + MyBatis Plus 性能优化全攻略:从SQL优化到缓存策略的最佳实践

WiseBronze
WiseBronze 2026-01-29T11:09:29+08:00
0 0 2

引言

在现代企业级应用开发中,性能优化是确保系统稳定运行和用户体验的关键因素。Spring Boot作为主流的Java应用框架,结合MyBatis Plus强大的数据库操作能力,为开发者提供了高效的开发体验。然而,在高并发、大数据量的场景下,如何优化Spring Boot + MyBatis Plus项目的性能成为了一个重要课题。

本文将从多个维度系统梳理Spring Boot + MyBatis Plus项目的性能优化路径,涵盖数据库连接池配置、SQL执行优化、二级缓存使用、分页查询优化等实用技巧,帮助开发者显著提升应用响应速度和系统整体性能。

一、数据库连接池优化

1.1 连接池选择与配置

在Spring Boot项目中,常用的数据库连接池有HikariCP、Druid、Tomcat JDBC Pool等。其中HikariCP作为当前最流行的连接池,以其高性能和低延迟著称。

# application.yml 配置示例
spring:
  datasource:
    hikari:
      # 连接池名称
      pool-name: MyHikariCP
      # 最小空闲连接数
      minimum-idle: 10
      # 最大连接数
      maximum-pool-size: 50
      # 连接超时时间
      connection-timeout: 30000
      # 空闲连接超时时间
      idle-timeout: 600000
      # 连接生命周期
      max-lifetime: 1800000
      # 测试连接有效性
      validation-timeout: 5000
      # 自动提交
      auto-commit: true

1.2 连接池参数调优

合理的连接池配置对系统性能至关重要。以下是一些关键参数的调优建议:

  • minimum-idle:设置为CPU核心数的2倍,确保有足够的空闲连接
  • maximum-pool-size:根据数据库最大连接数和应用并发需求设置,一般建议设置为100-200
  • connection-timeout:设置为30秒,避免长时间等待连接
  • idle-timeout:设置为5-10分钟,及时回收空闲连接
// 连接池监控配置
@Component
public class DataSourceMonitor {
    
    @Autowired
    private HikariDataSource dataSource;
    
    @Scheduled(fixedRate = 60000)
    public void monitorPool() {
        HikariPoolMXBean poolBean = dataSource.getHikariPoolMXBean();
        System.out.println("Active connections: " + poolBean.getActiveConnections());
        System.out.println("Idle connections: " + poolBean.getIdleConnections());
        System.out.println("Total connections: " + poolBean.getTotalConnections());
    }
}

二、SQL执行优化

2.1 SQL语句优化原则

基础查询优化

// ❌ 不推荐:全表扫描
@Select("SELECT * FROM user WHERE name LIKE CONCAT('%', #{name}, '%')")
List<User> selectUsersByName(String name);

// ✅ 推荐:使用索引优化
@Select("SELECT id, name, email FROM user WHERE name = #{name}")
User selectUserByName(String name);

复杂查询优化

// 使用MyBatis Plus的条件构造器进行优化
@Service
public class UserService {
    
    @Autowired
    private UserMapper userMapper;
    
    public List<User> queryUsers(UserQueryDTO query) {
        // 使用LambdaQueryWrapper避免SQL注入
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        
        // 精确查询
        if (StringUtils.isNotBlank(query.getName())) {
            wrapper.eq(User::getName, query.getName());
        }
        
        // 范围查询
        if (query.getAgeFrom() != null) {
            wrapper.ge(User::getAge, query.getAgeFrom());
        }
        
        // 排序和分页
        wrapper.orderByDesc(User::getCreateTime);
        
        return userMapper.selectList(wrapper);
    }
}

2.2 批量操作优化

// 批量插入优化
@Service
public class BatchUserService {
    
    @Autowired
    private UserMapper userMapper;
    
    // 使用批量插入提高效率
    public void batchInsertUsers(List<User> users) {
        // 方式1:使用MyBatis Plus的批量插入
        userMapper.insertBatchSomeColumn(users);
        
        // 方式2:自定义SQL批量插入
        userMapper.batchInsert(users);
    }
    
    // 批量更新优化
    public void batchUpdateUsers(List<User> users) {
        userMapper.updateBatchById(users);
    }
}

2.3 查询结果集优化

// 使用DTO映射减少不必要的字段传输
public class UserQueryDTO {
    private String name;
    private Integer age;
    private Date createTime;
    
    // getter/setter方法
}

// Mapper接口定义
@Mapper
public interface UserMapper extends BaseMapper<User> {
    
    // 查询特定字段
    @Select("SELECT id, name, email FROM user WHERE status = 1")
    List<UserDTO> selectActiveUsers();
    
    // 使用XML配置复杂查询
    List<UserDTO> selectUserWithDept(@Param("userId") Long userId);
}

三、二级缓存策略

3.1 MyBatis二级缓存基础配置

# application.yml
mybatis-plus:
  configuration:
    # 开启二级缓存
    cache-enabled: true
    # 开启本地缓存
    local-cache-size: 1000
    # 懒加载开关
    lazy-loading-enabled: true
    # 延迟加载的代理方式
    aggressive-lazy-loading: false

3.2 实现自定义缓存策略

// 自定义缓存注解
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Cacheable {
    String key() default "";
    int expireTime() default 300; // 缓存过期时间(秒)
}

// 缓存管理器
@Component
public class CacheManager {
    
    private final RedisTemplate<String, Object> redisTemplate;
    
    public CacheManager(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }
    
    public <T> T getCache(String key, Class<T> clazz) {
        Object value = redisTemplate.opsForValue().get(key);
        return value != null ? (T) value : null;
    }
    
    public void putCache(String key, Object value, int expireTime) {
        redisTemplate.opsForValue().set(key, value, expireTime, TimeUnit.SECONDS);
    }
    
    public void evictCache(String key) {
        redisTemplate.delete(key);
    }
}

// 缓存切面实现
@Aspect
@Component
public class CacheAspect {
    
    @Autowired
    private CacheManager cacheManager;
    
    @Around("@annotation(cacheable)")
    public Object around(ProceedingJoinPoint joinPoint, Cacheable cacheable) throws Throwable {
        String key = generateKey(joinPoint, cacheable);
        
        // 先从缓存获取
        Object result = cacheManager.getCache(key, Object.class);
        if (result != null) {
            return result;
        }
        
        // 缓存未命中,执行方法
        result = joinPoint.proceed();
        
        // 将结果放入缓存
        cacheManager.putCache(key, result, cacheable.expireTime());
        
        return result;
    }
    
    private String generateKey(ProceedingJoinPoint joinPoint, Cacheable cacheable) {
        String key = cacheable.key();
        if (StringUtils.isBlank(key)) {
            Object[] args = joinPoint.getArgs();
            key = joinPoint.getSignature().toString() + Arrays.toString(args);
        }
        return "cache:" + key;
    }
}

3.3 实体类缓存策略

// User实体类配置二级缓存
@CacheNamespace(
    implementation = MyBatisRedisCache.class,
    size = 1000,
    flushInterval = 60000,
    readOnly = true,
    blocking = true
)
public class User {
    private Long id;
    private String name;
    private String email;
    private Integer age;
    
    // getter/setter方法
}

// 自定义缓存实现
public class MyBatisRedisCache implements Cache {
    
    private final String id;
    private final RedisTemplate<String, Object> redisTemplate;
    
    public MyBatisRedisCache(String id) {
        this.id = id;
        this.redisTemplate = SpringContextUtil.getBean(RedisTemplate.class);
    }
    
    @Override
    public String getId() {
        return id;
    }
    
    @Override
    public void putObject(Object key, Object value) {
        redisTemplate.opsForValue().set(getKey(key), value, 30, TimeUnit.MINUTES);
    }
    
    @Override
    public Object getObject(Object key) {
        return redisTemplate.opsForValue().get(getKey(key));
    }
    
    @Override
    public Object removeObject(Object key) {
        Object result = getObject(key);
        redisTemplate.delete(getKey(key));
        return result;
    }
    
    @Override
    public void clear() {
        redisTemplate.delete(id + ":*");
    }
    
    @Override
    public int getSize() {
        return 0;
    }
    
    private String getKey(Object key) {
        return id + ":" + key.toString();
    }
}

四、分页查询优化

4.1 分页插件配置

# MyBatis Plus分页插件配置
mybatis-plus:
  configuration:
    # 开启分页插件
    pagination: true
  global-config:
    db-config:
      # 主键策略
      id-type: auto
  page:
    # 分页大小限制
    max-size: 1000

4.2 高效分页查询实现

@Service
public class UserService {
    
    @Autowired
    private UserMapper userMapper;
    
    // 优化的分页查询
    public IPage<UserDTO> queryUsersWithPage(UserQueryDTO query, Page<User> page) {
        // 构建查询条件
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        
        if (StringUtils.isNotBlank(query.getName())) {
            wrapper.like(User::getName, query.getName());
        }
        
        if (query.getAgeFrom() != null) {
            wrapper.ge(User::getAge, query.getAgeFrom());
        }
        
        // 添加排序
        wrapper.orderByDesc(User::getCreateTime);
        
        // 执行分页查询
        IPage<User> userPage = userMapper.selectPage(page, wrapper);
        
        // 转换为DTO
        List<UserDTO> dtoList = userPage.getRecords().stream()
            .map(this::convertToDTO)
            .collect(Collectors.toList());
            
        // 构造返回结果
        IPage<UserDTO> result = new Page<>();
        result.setRecords(dtoList);
        result.setTotal(userPage.getTotal());
        result.setSize(userPage.getSize());
        result.setCurrent(userPage.getCurrent());
        
        return result;
    }
    
    // 批量查询优化
    public List<UserDTO> queryUsersBatch(Long[] userIds) {
        if (CollectionUtils.isEmpty(Arrays.asList(userIds))) {
            return Collections.emptyList();
        }
        
        // 使用IN查询优化
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.in(User::getId, Arrays.asList(userIds));
        wrapper.orderByDesc(User::getCreateTime);
        
        List<User> users = userMapper.selectList(wrapper);
        return users.stream().map(this::convertToDTO).collect(Collectors.toList());
    }
    
    private UserDTO convertToDTO(User user) {
        UserDTO dto = new UserDTO();
        BeanUtils.copyProperties(user, dto);
        return dto;
    }
}

4.3 大数据量分页优化策略

// 流式分页查询实现
@Service
public class LargeDataUserService {
    
    @Autowired
    private UserMapper userMapper;
    
    // 分批处理大数据量查询
    public void processLargeUserList() {
        int pageSize = 1000;
        int currentPage = 1;
        
        while (true) {
            Page<User> page = new Page<>(currentPage, pageSize);
            IPage<User> userPage = userMapper.selectPage(page, null);
            
            List<User> users = userPage.getRecords();
            if (CollectionUtils.isEmpty(users)) {
                break;
            }
            
            // 处理当前批次数据
            processBatchUsers(users);
            
            if (users.size() < pageSize) {
                break; // 最后一批数据
            }
            
            currentPage++;
        }
    }
    
    private void processBatchUsers(List<User> users) {
        // 批量处理逻辑
        users.forEach(user -> {
            // 处理单个用户
            System.out.println("Processing user: " + user.getName());
        });
    }
}

五、JVM调优策略

5.1 JVM参数优化配置

# 启动脚本中的JVM参数优化
JAVA_OPTS="-server \
-Xms2g -Xmx4g \
-XX:+UseG1GC \
-XX:MaxGCPauseMillis=200 \
-XX:+UseStringDeduplication \
-XX:+UseCompressedOops \
-Djava.awt.headless=true \
-Dfile.encoding=UTF-8"

# 应用启动命令
nohup java $JAVA_OPTS -jar app.jar > app.log 2>&1 &

5.2 内存监控与调优

// JVM内存监控工具类
@Component
public class MemoryMonitor {
    
    private final MeterRegistry meterRegistry;
    
    public MemoryMonitor(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;
        registerMetrics();
    }
    
    private void registerMetrics() {
        // 注册内存使用率指标
        Gauge.builder("jvm.memory.used")
            .description("JVM memory used")
            .register(meterRegistry, this, monitor -> 
                getMemoryUsage().getUsed());
                
        Gauge.builder("jvm.memory.max")
            .description("JVM memory max")
            .register(meterRegistry, this, monitor -> 
                getMemoryUsage().getMax());
    }
    
    private MemoryUsage getMemoryUsage() {
        return ManagementFactory.getMemoryMXBean().getHeapMemoryUsage();
    }
}

5.3 垃圾回收优化

# GC优化配置
spring:
  jvm:
    gc:
      enabled: true
      log:
        level: INFO
      options:
        -XX:+UseG1GC
        -XX:MaxGCPauseMillis=200
        -XX:+UseStringDeduplication
        -XX:+UseCompressedOops

六、数据库索引优化

6.1 索引设计原则

-- 创建高效索引的示例
-- 用户表创建复合索引
CREATE INDEX idx_user_name_age ON user(name, age);
CREATE INDEX idx_user_email_status ON user(email, status);

-- 复合索引使用优化
SELECT * FROM user WHERE name = 'John' AND age > 25;
-- 这个查询可以有效利用 idx_user_name_age 索引

6.2 索引监控与分析

// 索引使用情况监控
@Service
public class IndexMonitorService {
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    public void analyzeIndexUsage() {
        String sql = """
            SELECT 
                table_name,
                index_name,
                rows_selected,
                rows_inserted,
                rows_updated,
                rows_deleted
            FROM performance_schema.table_statistics 
            WHERE table_name = 'user'
            """;
            
        List<Map<String, Object>> results = jdbcTemplate.queryForList(sql);
        results.forEach(System.out::println);
    }
    
    // 查询慢SQL分析
    public void analyzeSlowQueries() {
        String sql = """
            SELECT 
                DIGEST_TEXT,
                COUNT_STAR,
                AVG_TIMER_WAIT,
                SUM_ROWS_EXAMINED
            FROM performance_schema.events_statements_summary_by_digest 
            WHERE AVG_TIMER_WAIT > 1000000000000 
            ORDER BY AVG_TIMER_WAIT DESC 
            LIMIT 10
            """;
            
        List<Map<String, Object>> results = jdbcTemplate.queryForList(sql);
        results.forEach(System.out::println);
    }
}

七、缓存穿透与雪崩防护

7.1 缓存穿透防护

// 缓存空值防止穿透
@Service
public class UserService {
    
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    public User getUserById(Long id) {
        String key = "user:" + id;
        
        // 先从缓存获取
        Object cachedUser = redisTemplate.opsForValue().get(key);
        if (cachedUser != null) {
            return (User) cachedUser;
        }
        
        // 缓存未命中,查询数据库
        User user = userMapper.selectById(id);
        
        if (user == null) {
            // 缓存空值,防止缓存穿透
            redisTemplate.opsForValue().set(key, "", 300, TimeUnit.SECONDS);
            return null;
        }
        
        // 缓存用户数据
        redisTemplate.opsForValue().set(key, user, 3600, TimeUnit.SECONDS);
        return user;
    }
}

7.2 缓存雪崩防护

// 缓存随机过期时间防止雪崩
@Component
public class CacheProtectionService {
    
    private final RedisTemplate<String, Object> redisTemplate;
    
    public CacheProtectionService(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }
    
    public void setCacheWithRandomExpire(String key, Object value, int baseExpireTime) {
        // 添加随机时间防止集中过期
        Random random = new Random();
        int randomTime = random.nextInt(300) + 1; // 1-300秒随机值
        int expireTime = baseExpireTime + randomTime;
        
        redisTemplate.opsForValue().set(key, value, expireTime, TimeUnit.SECONDS);
    }
    
    // 使用分布式锁防止缓存击穿
    public User getUserWithLock(Long id) {
        String key = "user:" + id;
        String lockKey = "lock:user:" + id;
        
        try {
            // 获取分布式锁
            if (redisTemplate.opsForValue().setIfAbsent(lockKey, "locked", 10, TimeUnit.SECONDS)) {
                // 获取缓存
                Object cachedUser = redisTemplate.opsForValue().get(key);
                if (cachedUser != null) {
                    return (User) cachedUser;
                }
                
                // 缓存未命中,查询数据库
                User user = userMapper.selectById(id);
                if (user != null) {
                    redisTemplate.opsForValue().set(key, user, 3600, TimeUnit.SECONDS);
                }
                
                return user;
            } else {
                // 等待一段时间后重试
                Thread.sleep(100);
                return getUserWithLock(id);
            }
        } catch (Exception e) {
            throw new RuntimeException("获取用户信息失败", e);
        } finally {
            // 释放锁
            redisTemplate.delete(lockKey);
        }
    }
}

八、性能监控与调优工具

8.1 Prometheus + Grafana 监控配置

# application.yml
management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,prometheus
  metrics:
    export:
      prometheus:
        enabled: true

8.2 自定义监控指标

@Component
public class PerformanceMetrics {
    
    private final MeterRegistry meterRegistry;
    private final Counter requestCounter;
    private final Timer responseTimer;
    private final Gauge databaseConnectionGauge;
    
    public PerformanceMetrics(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;
        
        // 请求计数器
        this.requestCounter = Counter.builder("http.requests")
            .description("HTTP request count")
            .register(meterRegistry);
            
        // 响应时间计时器
        this.responseTimer = Timer.builder("http.response.time")
            .description("HTTP response time")
            .register(meterRegistry);
            
        // 数据库连接数监控
        this.databaseConnectionGauge = Gauge.builder("database.connections")
            .description("Database connections count")
            .register(meterRegistry, this, monitor -> 
                getDatabaseConnectionCount());
    }
    
    public void recordRequest(String method, String uri) {
        requestCounter.increment();
    }
    
    public Timer.Sample startTimer() {
        return Timer.start(meterRegistry);
    }
    
    private int getDatabaseConnectionCount() {
        // 实现获取数据库连接数的逻辑
        return 0;
    }
}

结论

通过本文的详细介绍,我们可以看到Spring Boot + MyBatis Plus项目的性能优化是一个系统工程,需要从多个维度进行综合考虑和调优。合理的数据库连接池配置、高效的SQL执行优化、智能的缓存策略、以及完善的监控体系,都是提升应用性能的关键因素。

在实际项目中,建议根据具体的业务场景和性能瓶颈,有针对性地选择和实施这些优化策略。同时,建立完善的监控机制,持续跟踪系统性能指标,及时发现和解决潜在问题,才能确保系统的长期稳定运行。

性能优化是一个持续的过程,需要开发团队不断学习、实践和总结经验。希望本文提供的最佳实践能够帮助开发者构建更加高效、稳定的Spring Boot应用。

相关推荐
广告位招租

相似文章

    评论 (0)

    0/2000