前言
接上一篇MysQL多数据源,这篇我们来实现MongoDB多数据源创建和路由。
- 自定义Bean实现我用的是BeanDefinitionRegistryPostProcessor。
- 连接池用的是MongoTemplate。
- 提供路由切换数据源统一模板DynamicMongoTemplate。
动态路由模板
MongoDB和MySQL不一样,它没有方便的Spring路由支持,我研究了很久并参考了一些网上的实现打算这么做:
- 继承MongoTemplate类
- 把所有配置中的数据源连接在启动时统一创建MongoDbFactory放到Map中,避免后续不断的去创建连接有大性能损耗
- 使用时统一通过本地线程指定使用哪一个连接源,选择来自这个Map中的对象
public class DynamicMongoTemplate extends MongoTemplate {
public DynamicMongoTemplate(MongoDbFactory mongoDbFactory) {
super(mongoDbFactory);
}
// 必须重写以下两个方法,因为在MongoTemplate类中它就是得到连接的工厂,这里是本地线程的使用方式,请看MongoDynamicDataSourceContextHolder。
@Override
public MongoDatabase getDb() {
return MongoDynamicDataSourceContextHolder.getContextKey().getDb();
}
@Override
protected MongoDatabase doGetDatabase() {
return MongoDynamicDataSourceContextHolder.getContextKey().getDb();
}
}
ThreadLocal本地线程和所有配置中需要创建连接的Mongodb工厂
public class MongoDynamicDataSourceContextHolder {
/**
* 数据源对应的MongoDbFactory Map
*/
private static final Map<String, MongoDbFactory> MONGO_CLIENT_DB_FACTORY_MAP = new HashMap<>();
/**
* 当前线程ThreadLocal绑定的数据源工厂
*/
private static final ThreadLocal<MongoDbFactory> DATASOURCE_CONTEXT_KEY_HOLDER = new ThreadLocal<>();
public static void putFactory(String key, MongoDbFactory factory) {
MONGO_CLIENT_DB_FACTORY_MAP.put(key, factory);
}
public static MongoDbFactory getOneFactory() {
return MONGO_CLIENT_DB_FACTORY_MAP.values().iterator().next();
}
/**
* 设置数据源
*
* @param key
*/
public static void setContextKey(String key) {
DATASOURCE_CONTEXT_KEY_HOLDER.set(MONGO_CLIENT_DB_FACTORY_MAP.get(key));
}
public static MongoDbFactory getContextKey() {
return DATASOURCE_CONTEXT_KEY_HOLDER.get();
}
public static void removeContextKey() {
DATASOURCE_CONTEXT_KEY_HOLDER.remove();
}
}
Bean定义
基本上和上一篇的定义差不多就不再赘述,只看关键:
- 给每一个数据源起名
dataName
。 - 全局设置一个为主库
primary
,因为在动态切换数据源功能会需要指定,也避免无主数据源会出一些启动问题。
[{
"mongo": {
"mongoConnectConfigs": [{
"dataName": "mongo1",
"database": "dev",
"url": "mongodb://mongo:xxxxx@localhost:1234/admin"
}, {
"dataName": "mongo2",
"database": "test",
"url": "mongodb://mongo:xxxxxx@localhost:1234/admin"
}]
}
}]
Bean定义我们用的是构造函数方式,需要用到RootBeanDefinition
和ConstructorArgumentValues
两个类,和MySQL那种不一样。
public class InitMongoTemplate implements EnvironmentAware, BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
for (Tenants tenant : tenants) {
Mongo mongo = tenant.getMongo();
if (mongo == null) {
continue;
}
for (MongoConnectConfigs config : mongo.getMongoConnectConfigs()) {
// 维护租户和mongo的名字列表
MongoDataNameMappingUtils.setTenantDataNames(tenant.getTenantId(), config.getDataName());
//如果有重名的则提示,直接按覆盖处理
if (beanDefinitionRegistry.containsBeanDefinition(config.getDataName())) {
log.warn("The Same Data Name By MongoDB : {}, Overwrite!", config.getDataName());
}
RootBeanDefinition beanDefinition = new RootBeanDefinition();
beanDefinition.setBeanClass(DynamicMongoTemplate.class);
if (config.isPrimary()) {
beanDefinition.setPrimary(true);
}
ConstructorArgumentValues constructorArgumentValues = new ConstructorArgumentValues();
MongoDbFactory factory = mongoTemplate(config.getUrl(), config.getDatabase());
constructorArgumentValues.addIndexedArgumentValue(0, factory);
beanDefinition.setConstructorArgumentValues(constructorArgumentValues);
beanDefinitionRegistry.registerBeanDefinition(config.getDataName(), beanDefinition);
MongoDynamicDataSourceContextHolder.putFactory(config.getDataName(), factory);
}
}
}
public MongoDbFactory mongoTemplate(String url, String database) {
MongoClient client = new MongoClient(new MongoClientURI(url, MongoClientOptions.builder()));
return new SimpleMongoDbFactory(client, database);
}
}
使用方式
还是和MySQL一样新增一个注解去用,AOP切面也是,逻辑自己去实现。
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MongoDynamicData {
//可设置数据源名称
String value() default "";
}
示例和MySQL也无差别
@Service
public class TestService {
@Autowired
private MongoTemplate dynamicMongoTemplate;
@MongoDynamicData("mongo1")
public void test() {
Query query = new Query();
query.addCriteria(Criteria.where("myId").is("1234567890"));
System.out.println("db name : " + dynamicMongoTemplate.getDb().getName());
InfoEntity entity = dynamicMongoTemplate.findOne(query, InfoEntity.class, "test_info");
System.out.println(entity);
}
public void test1() {
Query query = new Query();
query.addCriteria(Criteria.where("myId").is("1234567890"));
System.out.println("db name : " + dynamicMongoTemplate.getDb().getName());
InfoEntity entity = dynamicMongoTemplate.findOne(query, InfoEntity.class, "test_info");
System.out.println(entity);
}
}
示例很多关键点需要开发者去研究琢磨,很多细节我忽略并非恶意,要告诫开发者的是一个技术必须要全局思考和实践,如果只是拿来主义那别人问细节你回答不出来会异常尴尬。共勉!