16、《SpringBoot+MyBatis集成(4) - 性能优化 - 事务与缓存机制剖析》

SpringBoot+MyBatis集成 - 深度解析事务机制与缓存性能优化实践

一、架构整合核心原理剖析

1.1 事务控制底层实现

SpringBoot通过@EnableTransactionManagement激活声明式事务管理,其核心在于DataSourceTransactionManager与MyBatis的整合。当使用@Transactional注解时:

  • 通过AOP代理创建事务边界
  • 使用ThreadLocal绑定Connection到当前线程
  • 关键源码路径:TransactionInterceptor -> PlatformTransactionManager -> DataSourceUtils

MyBatis的SqlSessionTemplate通过动态代理机制,确保每个事务内使用同一个SqlSession,其生命周期由Spring管理而非开发者手动控制。

1.2 多数据源事务挑战

当系统需要跨数据源操作时,典型解决方案包括:

java 复制代码
@Configuration
public class XADataSourceConfig {
    
    @Bean("xaDataSource")
    public DataSource xaDataSource() {
        AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
        ds.setXaDataSourceClassName("com.mysql.cj.jdbc.MysqlXADataSource");
        //...配置参数
        return ds;
    }

    @Bean("jtaTransactionManager")
    public JtaTransactionManager jtaTransactionManager() {
        return new JtaTransactionManager();
    }
}

二、缓存机制深度优化

2.1 缓存层级对比分析

特性 一级缓存 二级缓存
作用域 SqlSession Mapper(Namespace)
存储结构 PerpetualCache 自定义缓存实现
失效策略 会话关闭/update操作 配置TTL/LRU策略
事务影响 同会话内共享 跨会话可见

2.2 Redis集成高级配置

xml 复制代码
<dependency>
    <groupId>org.mybatis.caches</groupId>
    <artifactId>mybatis-redis</artifactId>
    <version>1.0.0-beta2</version>
</dependency>

配置redis.properties:

properties 复制代码
redis.host=cluster.example.com
redis.port=6379
redis.maxTotal=200
redis.maxIdle=50
redis.minIdle=10

缓存雪崩防护策略:

java 复制代码
public class RedisCache implements Cache {
    
    public String getId() { /*...*/ }
    
    public void putObject(Object key, Object value) {
        // 添加随机TTL偏移量
        int baseTtl = 3600;
        int randomTtl = baseTtl + new Random().nextInt(300);
        redisTemplate.opsForValue().set(key, value, randomTtl, TimeUnit.SECONDS);
    }
}

三、批量操作性能调优

3.1 三种方案实现对比

在 MySQL 和 MyBatis-Plus 中,有多种方式可以实现批量插入,下面为你详细介绍常见的几种方式、用法及性能对比。

方式一:MyBatis-Plus 的 saveBatch 方法

这是 MyBatis-Plus 提供的批量插入方法,底层会自动处理批量操作。

用法
  1. 实体类
java 复制代码
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

@Data
@TableName("user")
public class User {
    private Long id;
    private String name;
    private Integer age;
}
  1. 服务类
java 复制代码
import com.baomidou.mybatisplus.extension.service.IService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class UserService extends ServiceImpl<UserMapper, User> implements IService<User> {
    public void batchInsert(List<User> userList) {
        saveBatch(userList);
    }
}
  1. 调用示例
java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.List;

@RestController
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/batchInsert")
    public String batchInsert() {
        List<User> userList = new ArrayList<>();
        for (int i = 0; i < 1000; i++) {
            User user = new User();
            user.setName("User" + i);
            user.setAge(20 + i % 10);
            userList.add(user);
        }
        userService.batchInsert(userList);
        return "Batch insert completed.";
    }
}

方式二:自定义 SQL 批量插入

通过自定义 XML 文件或注解方式编写批量插入的 SQL 语句。

用法
  1. Mapper 接口
java 复制代码
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Param;

import java.util.List;

public interface UserMapper extends BaseMapper<User> {
    @Insert("<script>" +
            "INSERT INTO user (name, age) VALUES " +
            "<foreach collection='userList' item='user' separator=','>" +
            "(#{user.name}, #{user.age})" +
            "</foreach>" +
            "</script>")
    void batchInsertCustom(@Param("userList") List<User> userList);
}
  1. 服务类调用
java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    public void batchInsertCustom(List<User> userList) {
        userMapper.batchInsertCustom(userList);
    }
}

方式三:使用 rewriteBatchedStatements 配合 saveBatch

开启 MySQL JDBC 驱动的 rewriteBatchedStatements 参数,进一步优化批量插入性能。

用法
  1. 配置 JDBC 连接 URL
    application.properties 中添加:
properties 复制代码
spring.datasource.url=jdbc:mysql://localhost:3306/your_database?rewriteBatchedStatements=true
spring.datasource.username=your_username
spring.datasource.password=your_password
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
  1. 使用 saveBatch 方法
    同方式一的服务类和调用示例。

性能对比

  • 方式一:MyBatis-Plus 的 saveBatch 方法
    • 优点:使用简单,代码量少,MyBatis-Plus 会自动处理批量操作的事务和一些细节。
    • 缺点:性能相对方式二和方式三可能稍差,因为它在底层可能会将批量操作拆分成多个单条插入语句执行。
    • 适用场景:对于数据量较小(几百条以内)的批量插入,或者对性能要求不是特别高的场景。
  • 方式二:自定义 SQL 批量插入
    • 优点:通过一次性将所有数据插入,减少了与数据库的交互次数,性能相对较好。
    • 缺点:代码复杂度较高,需要手动编写 SQL 语句,并且如果数据量非常大,可能会导致 SQL 语句过长,超出 MySQL 允许的最大长度。
    • 适用场景:数据量适中(几千条),对性能有一定要求,且可以控制 SQL 语句长度的场景。
  • 方式三:使用 rewriteBatchedStatements 配合 saveBatch
    • 优点:结合了 MyBatis-Plus 的便捷性和 MySQL JDBC 驱动的批量优化功能,性能最佳。可以处理大量数据(上万条甚至更多)的批量插入。
    • 缺点:需要配置 JDBC 连接参数,对环境有一定要求。
    • 适用场景:数据量非常大,对性能要求极高的场景。

总体来说,在数据量较小的情况下,方式一足够使用;数据量适中时,可以考虑方式二;而在数据量非常大的场景下,推荐使用方式三。

3.2 性能测试数据对比

插入方式 时间 CPU 使用情况 内存使用情况 说明
MyBatis - Plus 的 saveBatch 方法(未开启 rewriteBatchedStatements 较长,可能需要数分钟。因为该方法在未开启优化时,底层可能将批量操作拆分成多个单条插入语句执行,与数据库的交互次数多,网络开销大。 相对较低。由于每次插入的数据量小,CPU 主要处理单条 SQL 的解析和执行,没有复杂的批量合并操作。 较低。每次只处理一条数据插入,内存中不会同时存储大量数据。 实现简单,但性能在大数据量时不佳。
自定义 SQL 批量插入 适中,可能在几十秒到一分钟左右。通过一次性将所有数据插入,减少了与数据库的交互次数,但如果 SQL 语句过长,数据库解析和执行也需要一定时间。 中等。需要处理较长的 SQL 语句解析和执行,CPU 有一定负载,但比多次单条插入的整体负载要低。 较高。需要在内存中构建较长的 SQL 语句,数据量越大,占用内存越多,可能存在 SQL 长度超出限制的风险。 性能有所提升,但代码复杂度增加,且有 SQL 长度限制问题。
MyBatis - Plus 的 saveBatch 方法(开启 rewriteBatchedStatements 较短,可能在十几秒到几十秒。开启该参数后,MySQL JDBC 驱动会将多个 SQL 语句重写成一个高效的 SQL 语句,一次性发送到数据库执行,大大减少了网络开销和数据库处理时间。 较低。虽然会有 SQL 合并和重写操作,但整体操作相对简单,CPU 负载不高。 适中。不需要像自定义 SQL 那样构建超长的 SQL 语句,但仍需要在内存中存储一定数量的数据用于批量插入。 结合了便捷性和高性能,适合大数据量插入场景。

四、生产环境最佳实践

  1. 事务边界控制原则

    • 事务方法内避免耗时IO操作
    • 只读事务使用@Transactional(readOnly=true)
    • 嵌套事务使用NESTED传播级别
  2. 缓存失效策略

    java 复制代码
    @CacheNamespace(
        implementation = RedisCache.class,
        eviction = FifoCache.class,
        flushInterval = 60000
    )
    public interface UserMapper {
        @Options(flushCache = Options.FlushCachePolicy.TRUE)
        @Update("UPDATE user SET name=#{name} WHERE id=#{id}")
        int updateName(User user);
    }
  3. 监控指标采集

    java 复制代码
    @Aspect
    @Component
    public class MapperMonitorAspect {
        
        @Around("execution(* com.example.mapper.*.*(..))")
        public Object monitor(ProceedingJoinPoint pjp) throws Throwable {
            long start = System.currentTimeMillis();
            try {
                return pjp.proceed();
            } finally {
                long cost = System.currentTimeMillis() - start;
                Metrics.counter("sql.execute.count").increment();
                Metrics.timer("sql.execute.time").record(cost, TimeUnit.MILLISECONDS);
            }
        }
    }

五、常见问题解决方案

Q1:二级缓存脏读问题

  • 采用Cache-aside模式,在更新操作后主动清除相关缓存
  • 使用@CacheNamespaceRef建立缓存依赖关系

Q2:批量插入ID获取异常

  • 使用BatchExecutor时需设置useGeneratedKeys="false"
  • 采用分布式ID生成方案(Snowflake、UUID)

Q3:事务超时配置

properties 复制代码
spring.transaction.default-timeout=30 # 默认事务超时时间

结语

本文深入剖析了SpringBoot与MyBatis整合中的核心机制,通过事务控制、缓存优化、批量操作三个维度展示了性能调优的完整方案。建议根据实际业务场景进行组合使用,并配合监控系统持续优化。在微服务架构下,可进一步探索分库分表与读写分离的高级优化策略。

相关推荐
嘻哈∠※1 小时前
基于SpringBoot+vue粮油商城小程序系统
vue.js·spring boot·小程序
brevity_souls5 小时前
Spring Boot 内置工具类
java·spring boot
luoluoal5 小时前
基于Spring Boot+Vue的宠物服务管理系统(源码+文档)
vue.js·spring boot·宠物
小钊(求职中)5 小时前
Java开发实习面试笔试题(含答案)
java·开发语言·spring boot·spring·面试·tomcat·maven
拥有一颗学徒的心6 小时前
鸿蒙第三方库MMKV源码学习笔记
笔记·学习·性能优化·harmonyos
嘵奇7 小时前
MyBatis 中 SqlMapConfig 配置文件详解
mybatis
毕业设计-018 小时前
0081.基于springboot+uni-app的垃圾分类小程序+论文
spring boot·小程序·uni-app
小猫猫猫◍˃ᵕ˂◍8 小时前
rabbitmq五种模式的实现——springboot
spring boot·rabbitmq·java-rabbitmq