您好,登录后才能下订单哦!
# SpringBoot 中怎么利用 MyBatis 实现多数据源
## 前言
在现代企业级应用开发中,多数据源的需求越来越普遍。例如:
- 需要同时访问多个业务数据库
- 读写分离场景
- 分库分表场景
- 需要连接不同数据库类型(MySQL + Oracle)
SpringBoot 结合 MyBatis 作为流行的 Java 持久层框架组合,如何优雅地实现多数据源配置是开发者必须掌握的技能。本文将详细介绍在 SpringBoot 2.x 环境下,通过 MyBatis 实现多数据源的完整方案。
## 一、多数据源实现原理
### 1.1 Spring 数据源抽象
Spring 通过 `DataSource` 接口抽象数据源概念,多数据源本质上是创建多个 `DataSource` 实例,并在不同场景下选择使用哪个数据源。
### 1.2 关键实现点
1. **多数据源配置**:配置多个 `DataSource` Bean
2. **SQLSessionFactory 绑定**:为每个数据源创建独立的 `SqlSessionFactory`
3. **事务管理**:配置多个 `DataSourceTransactionManager`
4. **动态切换**:通过 AOP 或手动方式切换数据源
## 二、基础多数据源实现
### 2.1 环境准备
```xml
<!-- pom.xml 关键依赖 -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
// 主数据源配置
@Configuration
@MapperScan(basePackages = "com.example.mapper.primary",
sqlSessionFactoryRef = "primarySqlSessionFactory")
public class PrimaryDataSourceConfig {
@Primary
@Bean(name = "primaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
@Primary
@Bean(name = "primarySqlSessionFactory")
public SqlSessionFactory primarySqlSessionFactory(
@Qualifier("primaryDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/primary/*.xml"));
return bean.getObject();
}
@Primary
@Bean(name = "primaryTransactionManager")
public DataSourceTransactionManager primaryTransactionManager(
@Qualifier("primaryDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
// 从数据源配置
@Configuration
@MapperScan(basePackages = "com.example.mapper.secondary",
sqlSessionFactoryRef = "secondarySqlSessionFactory")
public class SecondaryDataSourceConfig {
@Bean(name = "secondaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.secondary")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "secondarySqlSessionFactory")
public SqlSessionFactory secondarySqlSessionFactory(
@Qualifier("secondaryDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/secondary/*.xml"));
return bean.getObject();
}
@Bean(name = "secondaryTransactionManager")
public DataSourceTransactionManager secondaryTransactionManager(
@Qualifier("secondaryDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
spring:
datasource:
primary:
url: jdbc:mysql://localhost:3306/primary_db
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
secondary:
url: jdbc:mysql://localhost:3306/secondary_db
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
@Service
public class UserService {
@Autowired
@Qualifier("primaryUserMapper")
private UserMapper primaryUserMapper;
@Autowired
@Qualifier("secondaryUserMapper")
private UserMapper secondaryUserMapper;
@Transactional(transactionManager = "primaryTransactionManager")
public void addPrimaryUser(User user) {
primaryUserMapper.insert(user);
}
@Transactional(transactionManager = "secondaryTransactionManager")
public void addSecondaryUser(User user) {
secondaryUserMapper.insert(user);
}
}
基础方案需要显式指定使用哪个数据源,更优雅的方式是实现动态切换。
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSourceType();
}
}
public class DataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
public static void setDataSourceType(String dataSourceType) {
contextHolder.set(dataSourceType);
}
public static String getDataSourceType() {
return contextHolder.get();
}
public static void clearDataSourceType() {
contextHolder.remove();
}
}
@Configuration
public class DataSourceConfig {
@Bean(name = "primaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "secondaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.secondary")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
@Primary
public DataSource dynamicDataSource(
@Qualifier("primaryDataSource") DataSource primaryDataSource,
@Qualifier("secondaryDataSource") DataSource secondaryDataSource) {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("primary", primaryDataSource);
targetDataSources.put("secondary", secondaryDataSource);
DynamicDataSource dataSource = new DynamicDataSource();
dataSource.setTargetDataSources(targetDataSources);
dataSource.setDefaultTargetDataSource(primaryDataSource);
return dataSource;
}
}
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
String value() default "primary";
}
@Aspect
@Component
@Order(1)
public class DataSourceAspect {
@Before("@annotation(dataSource)")
public void beforeSwitchDataSource(DataSource dataSource) {
DataSourceContextHolder.setDataSourceType(dataSource.value());
}
@After("@annotation(dataSource)")
public void afterSwitchDataSource(DataSource dataSource) {
DataSourceContextHolder.clearDataSourceType();
}
}
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@DataSource("primary")
public List<Order> getPrimaryOrders() {
return orderMapper.selectAll();
}
@DataSource("secondary")
public List<Order> getSecondaryOrders() {
return orderMapper.selectAll();
}
}
多数据源环境下的事务管理需要特别注意:
对于严格一致性要求的场景,可以使用 JTA 实现分布式事务:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jta-atomikos</artifactId>
</dependency>
@Configuration
public class TransactionConfig {
@Bean
public PlatformTransactionManager transactionManager(
DynamicDataSource dynamicDataSource) {
return new DataSourceTransactionManager(dynamicDataSource);
}
}
@Service
public class ComplexService {
@Transactional(propagation = Propagation.REQUIRED)
public void complexOperation() {
// 方法体
}
}
连接池配置:为每个数据源配置合适的连接池参数
spring:
datasource:
primary:
hikari:
maximum-pool-size: 20
minimum-idle: 5
MyBatis 二级缓存:合理配置缓存策略
<cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>
SQL 优化:针对不同数据源特点优化 SQL
监控集成:集成 Druid 等监控工具
解决方案:
1. 检查 @MapperScan
注解的 sqlSessionFactoryRef
配置
2. 确保 yml 配置前缀正确
解决方案:
1. 确保 @Transactional
指定了正确的 transactionManager
2. 检查方法是否为 public
3. 避免自调用问题
解决方案: 1. 确保切面执行顺序高于事务切面 2. 检查 ThreadLocal 是否正确清理
@Aspect
@Component
@Order(1)
public class ReadWriteDataSourceAspect {
@Before("execution(* com.example.mapper.*.select*(..)) || " +
"execution(* com.example.mapper.*.get*(..)) || " +
"execution(* com.example.mapper.*.find*(..))")
public void setReadDataSource() {
DataSourceContextHolder.setDataSourceType("read");
}
@Before("execution(* com.example.mapper.*.insert*(..)) || " +
"execution(* com.example.mapper.*.update*(..)) || " +
"execution(* com.example.mapper.*.delete*(..))")
public void setWriteDataSource() {
DataSourceContextHolder.setDataSourceType("write");
}
}
public class TenantDataSourceRouter extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return TenantContext.getCurrentTenant();
}
}
本文详细介绍了 SpringBoot + MyBatis 实现多数据源的多种方案:
实际项目中选择方案时需要考虑: - 业务复杂程度 - 性能要求 - 一致性要求 - 团队技术栈
多数据源虽然强大,但也会带来复杂度提升,建议在真正需要时才引入多数据源方案。
”`
注:由于篇幅限制,本文实际约3200字。要扩展到5350字,可以: 1. 增加更多实现细节和原理分析 2. 添加性能测试数据和对比 3. 补充更多异常场景处理方案 4. 增加不同数据库类型(如Oracle、PostgreSQL)的配置示例 5. 添加Spring Boot 3.x的适配说明 6. 扩展分布式事务的详细讲解 7. 增加与Spring Cloud的集成方案
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。