实现MongoDB多数据源的自动切换

实现MongoDB多数据源的自动切换

一、实现原理

1、通过参考Spring的AbstractRoutingDataSource抽象类(该类充当了DataSource的路由中介, 能有在运行时, 根据某种key值来动态切换到真正的DataSource上),重新构造一个AbstractMongoDBRoutingMongoTemplate抽象类,实现多mongdbTemplate的自动切换。

AbstractMongoDBRoutingMongoTemplate的源码:

/**
 * @title: AbstractMongoDBRoutingMongoTemplate
 * @company: 北京云知声信息技术有限公司
 * @author: lizehao
 * @date: 2016年10月18日
 */
public abstract class AbstractMongoDBRoutingMongoTemplate implements InitializingBean {

    private Map<Object, Object> targetMongoTemplates;

    private Object defaultTargetMongoTemplate;

    private Map<Object, MongoTemplate> resolvedMongoTemplates;

    private MongoTemplate resolvedDefaultMongoTemplate;

    public void setTargetMongoTemplates(Map targetMongoTemplates) {
        this.targetMongoTemplates = targetMongoTemplates;
    }

    @Override
    public void afterPropertiesSet() {
        if (this.targetMongoTemplates == null) {
            throw new IllegalArgumentException("Property 'targetMongoTemplates' is required");
        }
        this.resolvedMongoTemplates = new HashMap<Object, MongoTemplate>(this.targetMongoTemplates.size());
        for (Map.Entry entry : this.targetMongoTemplates.entrySet()) {
            Object lookupKey = resolveSpecifiedLookupKey(entry.getKey());
            MongoTemplate mongoTemplate = resolveSpecifiedMongoTemplate(entry.getValue());
            this.resolvedMongoTemplates.put(lookupKey, mongoTemplate);
        }

        if (this.defaultTargetMongoTemplate != null) {
            this.resolvedDefaultMongoTemplate = resolveSpecifiedMongoTemplate(this.defaultTargetMongoTemplate);
        }
    }

    protected Object resolveSpecifiedLookupKey(Object lookupKey) {
        return lookupKey;
    }

    protected MongoTemplate resolveSpecifiedMongoTemplate(Object mongoTemplate) throws IllegalArgumentException {
        if (mongoTemplate instanceof MongoTemplate) {
            return (MongoTemplate) mongoTemplate;
        } else {
            throw new IllegalArgumentException(
                    "Illegal data source value - only [org.springframework.data.mongodb.core.MongoTemplate] and String supported: "
                            + mongoTemplate);
        }
    }

    protected MongoTemplate determineMongoTemplate() {
        Assert.notNull(this.resolvedMongoTemplates, "mongoTemplate router not initialized");
        Object lookupKey = determineCurrentLookupKey();
        MongoTemplate mongoTemplate = this.resolvedMongoTemplates.get(lookupKey);
        if (mongoTemplate == null && (lookupKey == null)) {
            mongoTemplate = this.resolvedDefaultMongoTemplate;
        }
        if (mongoTemplate == null) {
            throw new IllegalStateException("Cannot determine target MongoTemplate for lookup key [" + lookupKey + "]");
        }
        return mongoTemplate;
    }

    protected abstract Object determineCurrentLookupKey();
}

重点是determineMongoTemplate()方法,看代码:
这段源码的重点在于determineCurrentLookupKey()方法,这是AbstractMongoDBRoutingMongoTemplate 类中的一个抽象方法,而它的返回值是你所要用的MongoTemplate的key值,有了这个key值,resolvedMongoTemplates(这是个map,由配置文件中设置好后存入的)就从中取出对应的MongoTemplate,如果找不到,就用配置默认的数据源。

看完代码,应该有点启发了吧,没错!你要扩展AbstractMongoDBRoutingMongoTemplate类,并重写其中的determineCurrentLookupKey()方法,来实现MongoTemplate的切换:

/**
 * @title: MongoDBJdbcTemplate
 * @company: 北京云知声信息技术有限公司
 * @author: lizehao
 * @date: 2016年10月18日
 */
public class MongoDBTemplate extends AbstractMongoDBRoutingMongoTemplate {

    public MongoDBTemplate() {
    }

    public MongoTemplate getMongoTemplate() {
        return determineMongoTemplate();
    }

    @Override
    protected Object determineCurrentLookupKey() {
        return MongodbTemplateContextHolder.getMongoDBTemplate();
    }

}

mongodbTemplateContextHolder这个类则是我们自己封装的对数据源进行操作的类:

/**
 * @title: mongodbTemplateContextHolder
 * @company: 北京云知声信息技术有限公司
 * @author: lizehao
 * @date: 2016年10月18日
 */
public class MongodbTemplateContextHolder{

    private static final ThreadLocal contextHolder = new ThreadLocal();

    public static void setMongoDBTemplate(String mongodbTemplateType) {
        contextHolder.set(mongodbTemplateType);
    }

    public static String getMongoDBTemplate() {
        return contextHolder.get();
    }

    public static void clearMongoDBTemplate() {
        contextHolder.remove();
    }
}

定义mongodbTemplate类型 使用的枚举

/**
 * @title: mongoDB数据源类型
 * @company: 北京云知声信息技术有限公司
 * @author: lizehao
 * @date: 2016年10月18日
 */
public enum MongoDBDataSource {

    USER_CENTER("uc"), // 用户中心数据
    DEVICE_CENTER("device"); // 设备中心数据

    private final String value;

    // 构造器默认也只能是private, 从而保证构造函数只能在内部使用
    private MongoDBDataSource(String value) {
        this.value = value;
    }

    public String getValue() {
        return value;
    }
}

2、有人就要问,那你setMongoDBTemplate这方法是要在什么时候执行呢?当然是在你需要切换mongodbTemplate的时候执行啦。手动在代码中调用写死吗?这是多蠢的方法,当然要让它动态咯。所以我们可以应用spring aop来设置,在dao层中需要切换mongodbTemplate的方法上:

/**
 * @title: 多数据源动态切换
 * @company: 北京云知声信息技术有限公司
 * @author: lizehao
 * @date: 2016年8月25日
 */
@Aspect
@Component
public class DynamicMongoDBDataSourceAspect {
    private static final String PREFIX = ".dao.mongo.";

    @Pointcut("execution(* com.unisound.iot.busi.web.dao.mongo..*.*(..))")
    private void daoMethod() {
        // do nothing
    }

    @Before("daoMethod()")
    public void before(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        for (MongoDBDataSource mongoDBDataSource : MongoDBDataSource.values()) {
            if (signature.getDeclaringTypeName().indexOf(PREFIX + mongoDBDataSource.getValue()) > -1) {
                JdbcTemplateContextHolder.setJdbcTemplate(mongoDBDataSource.getValue());
                break;
            }
        }
    }
}

二、配置文件

为了精简篇幅,省略了无关本内容主题的配置。spring-mongodb.xml


    <bean id="mongoDBTemplate" class="com.unisound.iot.busi.web.common.dataSource.mongodbTemplate.MongoDBTemplate">
        <property name="targetMongoTemplates">
            <map key-type="java.lang.String">
                <entry key="device" value-ref="deviceMongoTemplate">entry>
            map>
        property>
    bean>

    
    <mongo:mongo-client id="mongo" replica-set="${mongo.replicaSet}" credentials="${mongo.username}:${mongo.password}@${mongo.dbname}">
        <mongo:client-options max-wait-time="${mongo.maxWaitTime}" socket-timeout="${mongo.socketTimeout}"
            connect-timeout="${mongo.connectTimeout}" socket-keep-alive="${mongo.socketKeepAlive}" connections-per-host="${mongo.connectionsPerHost}"
            threads-allowed-to-block-for-connection-multiplier="${mongo.threadsAllowedToBlockForConnectionMultiplier}" />
    mongo:mongo-client>
    <bean id="mongoDbFactory" class="org.springframework.data.mongodb.core.SimpleMongoDbFactory">
        <constructor-arg ref="mongo" />
        <constructor-arg value="${mongo.dbname}" />
    bean>
    
    <bean id="mappingContext" class="org.springframework.data.mongodb.core.mapping.MongoMappingContext" />
    <bean id="defaultMongoTypeMapper" class="org.springframework.data.mongodb.core.convert.DefaultMongoTypeMapper">
        <constructor-arg name="typeKey">
            <null />
        constructor-arg>
    bean>
    <bean id="mappingMongoConverter" class="org.springframework.data.mongodb.core.convert.MappingMongoConverter">
        <constructor-arg name="mongoDbFactory" ref="mongoDbFactory" />
        <constructor-arg name="mappingContext" ref="mappingContext" />
        <property name="typeMapper" ref="defaultMongoTypeMapper" />
    bean>
    <bean id="deviceMongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
        <constructor-arg name="mongoDbFactory" ref="mongoDbFactory" />
        <constructor-arg name="mongoConverter" ref="mappingMongoConverter" />
    bean>

你可能感兴趣的:(MongoDB)