目录
- 一. 相关博客
- 二. 基本环境
- 三. 配置过程
- 1. 引入依赖
- 2. 修改 yml 配置文件
- 3. 数据源枚举 DBTypeEnum
- 4. 标记数据源的注解 MyDataSource
- 5. 动态数据源管理器 DataSourceContextHolder
- 6. 动态数据源决策 DynamicDataSource
- 7. Mybatis Plus 的配置类 MybatisPlusConfig
- 8. 使用AOP实现数据源的动态设置
- 9.实际使用:在ServiceImpl 类中使用注解进行标识
- 10.测试结果
一. 相关博客
之前的几篇博客中分析了 Mybatis Plus 的基本用法:
MyBatis Plus 的使用之入门
MyBatis Plus 的自动填充功能
二. 基本环境
本篇博客针对相对复杂的多数据源和动态事务的配置展开介绍,基本环境如下:
SpringBoot : 2.2.4.RELEASE
Druid : 1.1.21
Mybatis Plus : 3.4.0
三. 配置过程
1. 引入依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.21</version>
<scope>compile</scope>
</dependency>
2. 修改 yml 配置文件
spring:
datasource:
druid:
db1:
driverClassName: com.MysqL.cj.jdbc.Driver
url: jdbc:MysqL://127.0.0.1:3306/backend_dev?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
username: root
password: 123456
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
db2:
driverClassName: com.MysqL.cj.jdbc.Driver
url: jdbc:MysqL://127.0.0.1:3306/backend_log_dev?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
username: root
password: 123456
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
3. 数据源枚举 DBTypeEnum
public enum DBTypeEnum {
DB1("db1"), DB2("db2");
private String value;
DBTypeEnum(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
4. 标记数据源的注解 MyDataSource
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyDataSource {
DBTypeEnum value() default DBTypeEnum.DB1;
}
5. 动态数据源管理器 DataSourceContextHolder
public class DataSourceContextHolder {
private static final ThreadLocal contextHolder = new ThreadLocal<>();
public static void setDbType(DBTypeEnum dbTypeEnum) {
contextHolder.set(dbTypeEnum.getValue());
}
public static String getDbType() {
return (String) contextHolder.get();
}
public static void clearDbType() {
contextHolder.remove();
}
}
6. 动态数据源决策 DynamicDataSource
@Slf4j
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
String datasource = DataSourceContextHolder.getDbType();
log.debug("当前使用数据源:{}", datasource);
return datasource;
}
}
7. Mybatis Plus 的配置类 MybatisPlusConfig
mapper 的目录结构修改如下:

如果使用了 xml 文件进行多表查询,则还需要修改对应的 xml 文件中的路径 namespace:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.system.domain.mapper.db1.DataMapper">
<select id="pageDatas" resultType="map">
...
</select>
</mapper>
mapper 所在目录为 com.system.domain.mapper,因此 @MapperScan 需要扫描的路径为mapper下的 db1 和 db2,配置如下:
com.system.domain.mapper.db*
MybatisPlusConfig 配置类
@EnableTransactionManagement
@Configuration
@MapperScan("com.system.domain.mapper.db*")
public class MybatisPlusConfig {
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
@Bean(name = "db1")
@ConfigurationProperties(prefix = "spring.datasource.druid.db1")
public DataSource db1() {
return DruidDataSourceBuilder.create().build();
}
@Bean(name = "db2")
@ConfigurationProperties(prefix = "spring.datasource.druid.db2")
public DataSource db2() {
return DruidDataSourceBuilder.create().build();
}
@Bean(name = "multipleDataSource")
@Primary
public DataSource multipleDataSource(@Qualifier("db1") DataSource db1,
@Qualifier("db2") DataSource db2) {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DBTypeEnum.DB1.getValue(), db1);
targetDataSources.put(DBTypeEnum.DB2.getValue(), db2);
dynamicDataSource.setTargetDataSources(targetDataSources);
dynamicDataSource.setDefaultTargetDataSource(db1);
return dynamicDataSource;
}
@Bean("sqlSessionFactory")
public sqlSessionFactory sqlSessionFactory() throws Exception {
MybatissqlSessionfactorybean sqlSessionFactory = new MybatissqlSessionfactorybean();
sqlSessionFactory.setDataSource(multipleDataSource(db1(), db2()));
sqlSessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/*.xml"));
MybatisConfiguration configuration = new MybatisConfiguration();
configuration.setJdbcTypeForNull(JdbcType.NULL);
// 驼峰和下划线转换
configuration.setMapUnderscoreToCamelCase(true);
configuration.setCacheEnabled(false);
configuration.setCallSettersOnNulls(true);
sqlSessionFactory.setConfiguration(configuration);
sqlSessionFactory.setTypeAliasesPackage("com.intyt.jcyy.system.domain");
// 数据库查询结果驼峰式返回
sqlSessionFactory.setObjectWrapperFactory(new MybatisMapWrapperFactory());
// 添加分页功能
sqlSessionFactory.setPlugins(new Interceptor[]{
paginationInterceptor()
});
// 实现自动填充功能
sqlSessionFactory.setGlobalConfig(globalConfiguration());
return sqlSessionFactory.getObject();
}
@Bean(name = "multipleTransactionManager")
@Primary
public DataSourceTransactionManager multipleTransactionManager(@Qualifier("multipleDataSource") DataSource dataSource) {
// 动态事务配置
return new DataSourceTransactionManager(dataSource);
}
@Bean
public GlobalConfig globalConfiguration() {
// 自动填充创建时间和更新时间(MyMetaObjectHandler的实现参考 “MyBatis Plus 的自动填充功能” 博客)
GlobalConfig conf = new GlobalConfig();
conf.setMetaObjectHandler(new MyMetaObjectHandler());
return conf;
}
8. 使用AOP实现数据源的动态设置
@Component
@Order(value = -100)
@Slf4j
@Aspect
public class DataSourceAspect {
@Before("execution(* com.system.service.impl.*.*(..)) && @annotation(com.common.annotation.MyDataSource)")
public void before(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
MyDataSource myDataSource = null;
if (method.isAnnotationPresent(MyDataSource.class)) {
myDataSource = method.getAnnotation(MyDataSource.class);
DataSourceContextHolder.setDbType(myDataSource.value());
} else if (method.getDeclaringClass().isAnnotationPresent(MyDataSource.class)) {
myDataSource = method.getDeclaringClass().getAnnotation(MyDataSource.class);
DataSourceContextHolder.setDbType(myDataSource.value());
}
if (myDataSource != null) {
log.info("注解方式选择数据源---" + myDataSource.value().getValue());
}
}
@After("execution(* com.system.service.impl.*.*(..)) && @annotation(com.common.annotation.MyDataSource)")
public void after(JoinPoint point){
DataSourceContextHolder.clearDbType();
}
}
9.实际使用:在ServiceImpl 类中使用注解进行标识

即所有被标识了以下事务注解 @Transactional 和数据源注解 @MyDataSource 的service实现类都可以实现动态数据源和事务的切换:
@MyDataSource(DBTypeEnum.DB1)
@Transactional
10.测试结果

如果在按照以上步骤配置后,发现数据源切换不生效,可检查是否是 @Transactional 注解失效,可参考以下博客:
Spring的声明式事务@Transactional注解的6种失效场景
欢迎关注我的公众号,用讲故事的方式学技术。
这里有脑洞大开的奇葩故事,也有温暖文艺的心灵感悟。
技术知识,也可以很有趣。
 (编辑:北几岛)
【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!
|