作为一个合格的架构师,今天这篇文章算是划水,但是又是不得不会的。
我的项目是SpringBoot+Mybatis通用Mapper + 多数据源(druid),如果连基本Mybatis都没整合的,请先见我这篇文章:
SpringCloud+Nacos+Mybatis+Redis+Kafka 微服务整套框架环境搭建githubs.xyz
准备两个数据源
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
druid:
# 个微 数据源
wechat:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://aaaaa:3306/logdb?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai
username: wangfeng
password: aaaa
maxActive: 100
# 企微 数据源
enterprise:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://bbbbb:4000/logdb?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai
username: wangfeng
password: aaaa
maxActive: 100
- 第一个数据源配置:spring.datasource.druid.wechat
- 第二个数据源配置: spring.datasource.druid.enterprise
druid连接池版本:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.5</version>
</dependency>
SpringBoot项目代码
首先定义一个ThreadLocal类 来存储当前要切换到什么数据源:
public class HandleDataSource {
public static final ThreadLocal<String> holder = new ThreadLocal<String>();
/**
* title : 绑定当前线程数据源
*
* @param key
*/
public static void putDataSource(String datasource) {
holder.set(datasource);
}
/**
* title : 获取当前线程的数据源
*
* @return
*/
public static String getDataSource() {
return holder.get();
}
public static void clear() {
holder.remove();
}
}
定义一个DataSource注解,用在service方法里面,指定该方法走哪个数据源:
/***
* title: 指定改方法走哪个数据源
*
* @author HadLuo
* @since JDK1.7
* @history 2019年6月14日 新建
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DataSource {
/***
*
* title: 指定该方法使用的数据源 名称
*
* @return
* @author HadLuo 2021-3-11 10:28:37
*/
String value();
}
定义两个数据源类型名称:
public class Type{
/***
* title : 企业微信 数据源名称
*/
public static final String EnterpriseDataSource = "enterprise";
/***
* title : 个微 数据源名称
*/
public static final String WechatDataSource = "wechat";
}
然后向spring注入两个数据源实例bean(就在你的MybatisConfigure类中定义):
/**
* title: 个微数据源配置
*/
@Bean(name = Type.WechatDataSource, initMethod = "init", destroyMethod = "close")
@Primary
@ConfigurationProperties(prefix = "spring.datasource.druid.wechat")
public DataSource masterDataSource() {
DataSource dataSource = DataSourceBuilder.create(this.getClass().getClassLoader())
.type(com.alibaba.druid.pool.DruidDataSource.class).build();
return dataSource;
}
/**
* title: 企业微信数据源配置
*/
@Bean(name = Type.EnterpriseDataSource, initMethod = "init", destroyMethod = "close")
@ConfigurationProperties(prefix = "spring.datasource.druid.enterprise")
public DataSource slaveDataSource() {
return DataSourceBuilder.create(this.getClass().getClassLoader())
.type(com.alibaba.druid.pool.DruidDataSource.class).build();
}
spring.datasource.druid.wechat , spring.datasource.druid.enterprise 就是文章开头yml数据源配置。两个数据源bean的名称 就是为 我们的Type中的名称。
定义spring动态切换的数据源(可以在你的MybatisConfigure类中定义):
// 向spring中注册动态数据源,这样调用mapper的数据库操作就会自动切换数据源
@Bean(name = "dynamicDataSource")
public DynamicDataSource dataSource(@Qualifier(Type.WechatDataSource) DataSource wechatDataSource,
@Qualifier(Type.EnterpriseDataSource) DataSource enterpriseDataSource) {
Map<Object, Object> allDataSources = new HashMap<>();
// 设置了两个数据源:bean名称 Type.WechatDataSource Type.EnterpriseDataSource
allDataSources.put(WechatDataSource, wechatDataSource);
allDataSources.put(EnterpriseDataSource, enterpriseDataSource);
// 默认是 Type.WechatDataSource 这个数据源
return new DynamicDataSource(wechatDataSource, allDataSources);
}
/***
*
* title: spring 动态数据源路由类
*
* @author HadLuo
* @date 2021-3-11 11:07:45
*/
public static class DynamicDataSource extends AbstractRoutingDataSource {
public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
super.setDefaultTargetDataSource(defaultTargetDataSource);
super.setTargetDataSources(targetDataSources);
super.afterPropertiesSet();
}
@Override
protected Object determineCurrentLookupKey() {
// 核心: 调用mybatis的mapper操作数据库时,会先通过这个方法来选择用哪个数据源
// 这里我们 取的 是 Threadlocal的,具体在哪设置,后面一个切面会讲到
return HandleDataSource.getDataSource();
}
}
注意注入到spring的名称为 :dynamicDataSource 。
Mybatis的SqlSessionFactory 设置 为 上面的动态数据源:
@Bean
public SqlSessionFactoryBean createSqlSessionFactoryBean(
@Autowired @Qualifier("dynamicDataSource") DataSource dynamicDataSource) throws IOException {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
/** 设置动态的 数据源 dynamicDataSource */
sqlSessionFactoryBean.setDataSource(dynamicDataSource);
// 设置mapper,pojo路径等 我就不说了........
return sqlSessionFactoryBean;
}
最后就是我们的切面
@Aspect
@Component
public class DataSourceAspect {
@Pointcut("execution(public * com.uc..*.*(..))&&@annotation(dataSource)")
public void pointCut(DataSource dataSource) {}
/***
* title: 执行之后清除数据源
*
* @param point
* @param processor
* @author HadLuo 2019年4月18日 新建
*/
@After("pointCut(dataSource)")
public void after(JoinPoint point, DataSource dataSource) {
HandleDataSource.clear();
}
/***
* 执行之前 ,
*
* @param point
* @param processor
* @author HadLuo 2019年4月18日 新建
*/
@Before("pointCut(dataSource)")
public void before(JoinPoint point, DataSource dataSource) {
Object target = point.getTarget();
String method = point.getSignature().getName();
Class<?>[] parameterTypes = ((MethodSignature)point.getSignature()).getMethod().getParameterTypes();
Method m;
try {
m = target.getClass().getMethod(method, parameterTypes);
if (m != null && m.isAnnotationPresent(DataSource.class)) {
DataSource data = m.getAnnotation(DataSource.class);
// 数据源放到当前线程中
HandleDataSource.putDataSource(data.value());
} else {
HandleDataSource.clear();
}
} catch (NoSuchMethodException | SecurityException e) {
e.printStackTrace();
}
}
}
这个切面就是拦截 配置了@DataSource 注解的方法,将注解的value 设置到 HandleDataSource 的ThreadLocal中。
还记得我们上面定义的动态数据源bean吗:
public static class DynamicDataSource extends AbstractRoutingDataSource {
public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
super.setDefaultTargetDataSource(defaultTargetDataSource);
super.setTargetDataSources(targetDataSources);
super.afterPropertiesSet();
}
@Override
protected Object determineCurrentLookupKey() {
// 核心: 调用mybatis的mapper操作数据库时,会先通过这个方法来选择用哪个数据源
// 这里我们 取的 是 Threadlocal的,具体在哪设置,后面一个切面会讲到
return HandleDataSource.getDataSource();
}
}
于是就从 ThreadLocal 中 取到了 要用哪个数据源。
业务Service测试
@Override
@DataSource(Type.EnterpriseDataSource) // 企业微信数据源
public void test() {
System.err.println("当前数据源: " + HandleDataSource.getDataSource());
GroupSendMessage message = new GroupSendMessage();
// 插入企业微信 的数据源
App.getBean(GroupSendMessageMapper.class).insertSelective(message);
}
会打印: 当前数据源: enterprise 。 并且数据会插入到:
# 企微 数据源
enterprise:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://bbbbb:4000/logdb?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai
username: wangfeng
password: aaaa
maxActive: 100
这个数据源里面。
本文完~
推荐一个Java架构师博客,带你一起实践写架构: