SpringBoot通过@EnableTransactionManagement
激活声明式事务管理,其核心在于DataSourceTransactionManager
与MyBatis的整合。当使用@Transactional
注解时:
TransactionInterceptor
-> PlatformTransactionManager
-> DataSourceUtils
MyBatis的SqlSessionTemplate
通过动态代理机制,确保每个事务内使用同一个SqlSession,其生命周期由Spring管理而非开发者手动控制。
当系统需要跨数据源操作时,典型解决方案包括:
@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();
}
}
特性 | 一级缓存 | 二级缓存 |
---|---|---|
作用域 | SqlSession | Mapper(Namespace) |
存储结构 | PerpetualCache | 自定义缓存实现 |
失效策略 | 会话关闭/update操作 | 配置TTL/LRU策略 |
事务影响 | 同会话内共享 | 跨会话可见 |
<dependency>
<groupId>org.mybatis.cachesgroupId>
<artifactId>mybatis-redisartifactId>
<version>1.0.0-beta2version>
dependency>
配置redis.properties:
redis.host=cluster.example.com
redis.port=6379
redis.maxTotal=200
redis.maxIdle=50
redis.minIdle=10
缓存雪崩防护策略:
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);
}
}
在 MySQL 和 MyBatis-Plus 中,有多种方式可以实现批量插入,下面为你详细介绍常见的几种方式、用法及性能对比。
saveBatch
方法这是 MyBatis-Plus 提供的批量插入方法,底层会自动处理批量操作。
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
@Data
@TableName("user")
public class User {
private Long id;
private String name;
private Integer age;
}
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);
}
}
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.";
}
}
通过自定义 XML 文件或注解方式编写批量插入的 SQL 语句。
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("")
void batchInsertCustom(@Param("userList") List<User> userList);
}
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
参数,进一步优化批量插入性能。
application.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
saveBatch
方法saveBatch
方法
rewriteBatchedStatements
配合 saveBatch
总体来说,在数据量较小的情况下,方式一足够使用;数据量适中时,可以考虑方式二;而在数据量非常大的场景下,推荐使用方式三。
插入方式 | 时间 | 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 语句,但仍需要在内存中存储一定数量的数据用于批量插入。 | 结合了便捷性和高性能,适合大数据量插入场景。 |
事务边界控制原则
@Transactional(readOnly=true)
NESTED
传播级别缓存失效策略
@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);
}
监控指标采集
@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"
Q3:事务超时配置
spring.transaction.default-timeout=30 # 默认事务超时时间
本文深入剖析了SpringBoot与MyBatis整合中的核心机制,通过事务控制、缓存优化、批量操作三个维度展示了性能调优的完整方案。建议根据实际业务场景进行组合使用,并配合监控系统持续优化。在微服务架构下,可进一步探索分库分表与读写分离的高级优化策略。