前言

接上一篇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定义我们用的是构造函数方式,需要用到RootBeanDefinitionConstructorArgumentValues两个类,和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);
    }
}

示例很多关键点需要开发者去研究琢磨,很多细节我忽略并非恶意,要告诫开发者的是一个技术必须要全局思考和实践,如果只是拿来主义那别人问细节你回答不出来会异常尴尬。共勉!

上一篇 下一篇