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

来源:互联网 发布:android 打开淘宝链接 编辑:程序博客网 时间:2024/06/05 15:23

实现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<Object, Object> 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<Object, Object> 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<String> contextHolder = new ThreadLocal<String>();    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@Componentpublic 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

<!-- mongo多数据源切换 -->    <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>    <!-- MongoDB相关配置 -->    <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>    <!-- 去掉MongoDB数据中的_class属性的方法 -->    <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>
1 0