在实际的应用开发中,往往会遇到需要从多个数据库中获取数据的场景。MyBatis 提供了灵活的方式来整合和配置多数据源,适应不同的数据存储需求。本文将详细探讨 MyBatis 如何整合多数据源,并通过两种常见的方式(分包配置与 AOP 动态切换)进行实现。
为什么需要多数据源?
场景 1:不同数据库
在某些复杂系统中,可能会使用不同类型的数据库来存储不同的数据。例如,一个商城网站和一个游戏网站,它们可能需要共享一些公共数据,但由于各自的业务特性,它们分别使用不同的数据库。商城网站涉及大量的交易数据,需要保证安全性;而游戏网站可能流量更大,可能会选择不同的数据库来提升性能或应对大规模并发。
通过使用多数据源,我们可以把不同的数据库隔离开来,降低一个系统故障对另一个系统的影响,同时提高性能与安全性。
场景 2:同一数据库,分别访问
随着业务的发展,单一数据库可能无法满足性能要求。此时,为了提高性能或扩展性,可以考虑对数据库进行拆分,使用多个数据源进行轮询访问。例如,当数据库中的数据量大到一定程度时,可能需要对数据进行分库分表,以减轻单个数据库的负担,提升查询性能。
多数据源的配置方法
MyBatis 提供了多种方式来整合多个数据源,主要包括 分包配置 和 AOP 动态切换 两种方式。
1. 分包配置(场景 1)
分包配置的核心思想是为每个数据源配置不同的 SqlSessionFactory
和 SqlSessionTemplate
,分别处理不同的数据源请求。以下是具体的配置步骤:
1.1 配置数据源
首先,我们在 application.yml
或 application.properties
文件中配置多个数据源。例如,配置两个数据源 boot1
和 boot2
:
# 数据源 1
spring.datasource.one.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.one.username=root
spring.datasource.one.password=root
spring.datasource.one.jdbc-url=jdbc:mysql://localhost:3306/boot1?characterEncoding=utf8&serverTimezone=GMT%2B8
# 数据源 2
spring.datasource.two.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.two.username=root
spring.datasource.two.password=root
spring.datasource.two.jdbc-url=jdbc:mysql://localhost:3306/boot2?characterEncoding=utf8&serverTimezone=GMT%2B8
1.2 配置 DataSource
接下来,我们需要通过 Java 配置类定义这两个数据源。使用 @ConfigurationProperties
注解来加载配置,并提供两个不同的 DataSource
Bean。
@Configuration
public class DataSourceConfig {
@Primary
@Bean("dsOne")
@ConfigurationProperties(prefix = "spring.datasource.one")
public DataSource dsOne() {
return DataSourceBuilder.create().build();
}
@Bean("dsTwo")
@ConfigurationProperties(prefix = "spring.datasource.two")
public DataSource dsTwo() {
return DataSourceBuilder.create().build();
}
}
1.3 配置 SqlSessionFactory 和 SqlSessionTemplate
然后,我们为每个数据源分别配置 SqlSessionFactory
和 SqlSessionTemplate
,并指定每个数据源所使用的 Mapper 包。
@Configuration
@MapperScan(basePackages = "com.example.springbootpro1.mapper", sqlSessionTemplateRef = "sqlSessionTemplate1")
public class MyBatisConfigOne {
@Resource(name = "dsOne")
private DataSource dsOne;
@Bean("sqlSessionFactory1")
public SqlSessionFactory sqlSessionFactory1() throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dsOne);
factoryBean.setTypeAliasesPackage("com.example.springbootpro1.entity");
factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/*.xml"));
return factoryBean.getObject();
}
@Bean
public SqlSessionTemplate sqlSessionTemplate1() {
return new SqlSessionTemplate(sqlSessionFactory1());
}
}
1.4 配置第二个数据源的 SqlSessionFactory
和 SqlSessionTemplate
同样的,我们也为第二个数据源配置 SqlSessionFactory
和 SqlSessionTemplate
:
@Configuration
@MapperScan(basePackages = "com.example.springbootpro1.dao", sqlSessionTemplateRef = "sqlSessionTemplate2")
public class MyBatisConfigTwo {
@Resource(name = "dsTwo")
private DataSource dsTwo;
@Bean("sqlSessionFactory2")
public SqlSessionFactory sqlSessionFactory2() throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dsTwo);
factoryBean.setTypeAliasesPackage("com.example.springbootpro1.bean");
factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/*.xml"));
return factoryBean.getObject();
}
@Bean
public SqlSessionTemplate sqlSessionTemplate2() {
return new SqlSessionTemplate(sqlSessionFactory2());
}
}
2. AOP 动态切换数据源(场景 2)
AOP 动态切换数据源的核心思想是通过 AOP 拦截方法调用,根据注解或上下文决定使用哪个数据源。实现的关键是使用 AbstractRoutingDataSource
来动态决定当前使用的数据源。
2.1 配置动态数据源
首先,我们配置多个数据源,并使用 AbstractRoutingDataSource
来实现数据源的动态切换。
@Configuration
@EnableTransactionManagement
public class DataSourceConfig {
@Primary
@Bean(name = "master")
@ConfigurationProperties(prefix = "spring.datasource.one")
public DataSource master() {
return DataSourceBuilder.create().build();
}
@Bean(name = "slave")
@ConfigurationProperties(prefix = "spring.datasource.two")
public DataSource slave() {
return DataSourceBuilder.create().build();
}
@Bean(name = "dynamicDataSource")
public DataSource dynamicDataSource() {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
Map<Object, Object> dataSourceMap = new HashMap<>(2);
dataSourceMap.put("master", master());
dataSourceMap.put("slave", slave());
dynamicDataSource.setDefaultTargetDataSource(master());
dynamicDataSource.setTargetDataSources(dataSourceMap);
return dynamicDataSource;
}
}
2.2 实现数据源上下文
为了在方法调用时动态决定数据源,我们需要一个上下文类来存储当前的数据源。
public class DynamicDataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>() {
@Override
protected String initialValue() {
return "master";
}
};
public static String getDataSourceKey() {
return contextHolder.get();
}
public static void setDataSourceKey(String key) {
contextHolder.set(key);
}
public static void clearDataSourceKey() {
contextHolder.remove();
}
}
2.3 实现 AOP 切面
通过 AOP 切面拦截标有 @DataSource
注解的方法,在方法执行前切换数据源。
@Aspect
@Component
@Slf4j
@Order(-1)
public class DynamicDataSourceAspect {
@Pointcut("@annotation(com.example.springbootpro1.dsconfig.DataSource)")
public void dataSourcePointCut() {}
@Around("dataSourcePointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
DataSource ds = method.getAnnotation(DataSource.class);
if (ds == null) {
DynamicDataSourceContextHolder.setDataSourceKey("master");
} else {
DynamicDataSourceContextHolder.setDataSourceKey(ds.name());
}
try {
return point.proceed();
} finally {
DynamicDataSourceContextHolder.clearDataSourceKey();
}
}
}
2.4 使用注解切换数据源
最后,我们可以通过注解 @DataSource
来指定方法使用哪个数据源:
@Mapper
public interface CaseMapper {
@Select("select * from base_cache_area")
List<BaseCacheArea> list();
@DataSource(name = "slave")
List<BaseCacheArea2> selectAll();
}
结论
通过 MyBatis 配置多数据源,可以有效地应对复杂的业务需求。在性能要求高或数据源分布式的场景下,多数据源提供了灵活的解决方案。本文介绍了两种常见的多数据源配置方式:分包配置和AOP 动态切换,根据实际需求选择合适的方式来实现数据源的管理与切换,从而提升系统的扩展性、可维护性及性能。