SpringBoot2.x整合Mongo实现多数据库切换

SpringBoot2.x整合Mongo实现多数据库切换

前言

本项目Mongo的多数据库切换的核心其实还是利用AOP技术以及自定义的MongoTemplate。切点就是每一个dao操作类,大致思路就是在进行操作之前构造对应数据库的MongoTemplate,这样就可以利用构造的MongoaTemplate进行操作了。

MultiMongoTemplate

首先是自定义的MongoTemplate,这个不用多讲,就是对mongo执行各种操作的关键所在。这里多数据库的切换,说白了就是在需要切换的时候,利用MongoDbFactory构造对应的MongoTemplate即可。然后保存到ThreadLocal中,每次获取MongoDatabase的时候从里面取即可

Component(value = "mongoTemplate")
@Slf4j
public class MultiMongoTemplate extends MongoTemplate {

    private static volatile ThreadLocal<MongoDbFactory> mongoDbFactoryThreadLocal;

    public MultiMongoTemplate(MongoDbFactory mongoDbFactory) {
        super(mongoDbFactory);
        if (mongoDbFactoryThreadLocal == null) {
            synchronized (MultiMongoTemplate.class) {
                if (mongoDbFactoryThreadLocal == null) {
                    mongoDbFactoryThreadLocal = new ThreadLocal<>();
                    mongoDbFactoryThreadLocal.set(mongoDbFactory);
                }
            }
        }
    }
    public void setMongoDbFactory(MongoDbFactory factory){
        mongoDbFactoryThreadLocal.set(factory);
    }

    public void removeMongoDbFactory(){
        mongoDbFactoryThreadLocal.remove();
    }

    @Override
    public MongoDatabase getDb() {
        return mongoDbFactoryThreadLocal.get().getDb();
    }

    @Override
    protected MongoDatabase doGetDatabase(){
        return mongoDbFactoryThreadLocal.get().getDb();
    }
}

数据库动态切换

@Component
@Aspect
@Slf4j
public class MongoSwitchAspect {

    @Value("${spring.data.mongodb.uri}")
    private String mongoUri;

    private Map<String, MongoDbFactory> mongoDbFactoryMap = new ConcurrentHashMap<>();

    // 切点除了MongoTemplate的set方法
    @Pointcut("execution(* com.itoyoung.dao.mongo.*.*(..)) && !execution(* com.itoyoung.dao.mongo.*.set*(..))")
    public void routeMongoDB() {
    }

    @Around("routeMongoDB()")
    public Object routeMongoDB(ProceedingJoinPoint joinPoint) {

        // 判断数据库是否和之前的一致,一致就不切换
        if (DynamicMongoDBContextHolder.switchDB()) {

            //获取需要访问的项目数据库
            String dbName = DynamicMongoDBContextHolder.getDBRouterKey();
            try {

                Object o = joinPoint.getTarget();
                Class<?> clazz = o.getClass();

                Class<?> superclass = clazz.getSuperclass();
                if (superclass != BaseMongoDao.class) {
                    log.error("MongoSwitch: class: {} 未继承BaseMongoDao或非Mongo操作类,请勿放入dao.mongo路径下",
                            clazz.getName());
                    throw new BaseRuntimeException("数据库切换异常");
                }

                Field mongoTemplateField = superclass.getDeclaredField("mongoTemplate");
                if (null == mongoTemplateField) {
                    log.error("MongoSwitch: class: {} 不存在mongoTemplate", clazz.getName());
                    throw new BaseRuntimeException("数据库切换异常");
                }
                mongoTemplateField.setAccessible(true);

                MultiMongoTemplate mongoTemplate = (MultiMongoTemplate) mongoTemplateField.get(o);

                //设置MongoFactory
                if (null == mongoTemplate) {
                    mongoTemplate = new MultiMongoTemplate(getDbFactory(dbName));
                } else {
                    mongoTemplate.setMongoDbFactory(getDbFactory(dbName));
                }
                //重新赋值
                mongoTemplateField.set(o, mongoTemplate);
            } catch (Exception e) {
                log.error("MongoSwitch: DB: {} 切换数据库失败: {}", dbName, e);
            }
        }

        Object result = null;
        try {
            result = joinPoint.proceed();
//            if (StringUtils.isBlank(switchOrg)) {
//                //清理ThreadLocal的变量
//                mongoTemplate.removeMongoDbFactory();
//            }

        } catch (Throwable t) {
            log.error("MongoSwitch: DB: {} 方法执行失败: {}", DynamicMongoDBContextHolder.getDBRouterKey(), t);
        }
        return result;
    }

    /**
     * 根据需要切换的数据库获取MongoDbFactory
     *
     * @param dbName
     * @return
     */
    private SimpleMongoDbFactory getDbFactory(String dbName) {
        //查找项目对应的MongFactory
        SimpleMongoDbFactory simpleMongoDbFactory = (SimpleMongoDbFactory) mongoDbFactoryMap.get(dbName);

        //实例化
        if (simpleMongoDbFactory == null) {
            MongoClient mongoClient = new MongoClient(new MongoClientURI(mongoUri));

            simpleMongoDbFactory = new SimpleMongoDbFactory(mongoClient, dbName);

            mongoDbFactoryMap.put(dbName, simpleMongoDbFactory);
        }
        return simpleMongoDbFactory;
    }

}

动态切换操作类

协助在业务代码中切换mongo数据库

@Slf4j
public class DynamicMongoDBContextHolder {

    /**
     * 线程级别的私有变量(存储需切换的组织)
     */
    private static final ThreadLocal<String> MONGO_DB_HOLDER = new ThreadLocal<>();

    private static final ThreadLocal<String> BEFORE_DB__HOLDER = new ThreadLocal<>();

    /**
     * 获取当前操作的数据库
     *
     * @return
     */
    public static String getDBRouterKey() {
        return MONGO_DB_HOLDER.get();
    }

    /**
     * 切换mongo库
     *
     * @param dbName
     */
    public static void setDBRouterKey(String dbName) {
        if (StringUtils.isEmpty(dbName)) {
            log.info("要切换的mongo库为空");
            throw new BaseRuntimeException("当前切换的数据源不存在");
        }
        // 将切换之前的数据库保存
        String beforeDB = MONGO_DB_HOLDER.get();
        if (StringUtils.isNotEmpty(beforeDB) && !beforeDB.equals(dbName)) {
            BEFORE_DB__HOLDER.set(beforeDB);
        }
        //将需切的mongo库存到当前线程的threadLocal内
        MONGO_DB_HOLDER.set(dbName);
    }

    /**
     * 切换请求最开始的数据库
     */
    public static void setRootDBRouterKey() {
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        if (requestAttributes == null) {
            throw new BaseRuntimeException("非Request请求,切换Root数据源失败");
        }

        String dbName = ((ServletRequestAttributes) requestAttributes).getRequest().getParameter("dbName");
        setDBRouterKey(dbName);
    }

    /**
     * 判断是否需要切换数据库
     *
     * @return
     */
    public static boolean switchDB() {
        String nowDB = MONGO_DB_HOLDER.get();
        if (StringUtils.isEmpty(nowDB)) {
            throw new BaseRuntimeException("当前数据源不存在");
        }
        // 之前的集合和当前的集合不一致 就需要切换
        return !nowDB.equals(BEFORE_DB__HOLDER.get());
    }

    /**
     * 移除
     */
    public static void remove() {
        MONGO_DB_HOLDER.remove();
        BEFORE_DB__HOLDER.remove();
    }
}

Mongo操作基础类

之前整合Mongo的那篇有写,但是需要做出一点修改

@Slf4j
public abstract class BaseMongoDao<T> {

    /**
     * 反射获取泛型类型
     *
     * @return
     */
    protected abstract Class<T> getEntityClass();

    @Resource
    public MultiMongoTemplate mongoTemplate;

    public MultiMongoTemplate getMongoTemplate() {
        return mongoTemplate;
    }

    public void setMongoTemplate(MultiMongoTemplate mongoTemplate) {
        this.mongoTemplate = mongoTemplate;
    }

    /***
     * 保存一个对象
     * @param t
     */
    public void saveWithColletion(T t, String collection) {
        log.info("-------------->MongoDB save start");
        this.mongoTemplate.save(t, collection);
    }

    /**
    * 根据条件查询集合
    *
    * @param object
    * @return
    */
    public List<T> queryListWithCollection(T object, String collection) {
        Query query = getQueryByObject(object);
        log.info("-------------->MongoDB find start");
        return mongoTemplate.find(query, this.getEntityClass(), collection);
    }
    // 省略...
}

拦截器

在请求进来时进行拦截,切换到对应的数据库

/**
 * 拦截器配置类
 * 
 * @author :itoyoung
 * @date :2019-06-02 17:23
 * 
 */
@Configuration
public class WebConfigurer implements WebMvcConfigurer {

    @Resource
    private HttpRequestInterceptor httpRequestInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //将自定义的拦截器加入配置中。
        registry.addInterceptor(httpRequestInterceptor).addPathPatterns("/**");
    }
}

@Component
public class HttpRequestInterceptor extends HandlerInterceptorAdapter {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String dbName = request.getParameter("dbName");
        if (StringUtil.isEmpty(dbName)) {
            // 当没有传mongo数据库时,给一个默认的数据库
            dbName = "xzjj";
        } 
        DynamicMongoDBContextHolder.setDBRouterKey(dbName);
        return super.preHandle(request, response, handler);
    }
}

简单应用

/**
 * 用户日志mongo服务
 *
 * @author : itoyoung
 * @date :   2019-06-02 18:06
 */
@Service
public class UserLogMongoServiceImpl implements IUserLogMongoService {

    private final static String USER_LOG = "user_log";

    @Resource
    private UserLogMongoDao userLogMongoDao;

    @Override
    public void insert(MongoUserLog userLog) {
        // 请求的数据库
        userLogMongoDao.saveWithColletion(userLog, USER_LOG);
        // 手动切换数据库
        DynamicMongoDBContextHolder.setDBRouterKey("yjdf");
        userLogMongoDao.saveWithColletion(userLog, USER_LOG);
    }


    @Override
    public List<MongoUserLog> listByQuery(MongoUserLog query) {
        return userLogMongoDao.queryListWithCollection(query, USER_LOG);
    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
Spring Boot 中整合 MongoDB 多数据源,需要按照以下步骤操作: 1. 引入 MongoDB 的依赖 在 pom.xml 文件中引入 MongoDB 的依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId> </dependency> ``` 2. 配置数据源 在 application.properties 文件中配置多个数据源: ```properties # 数据源1 spring.data.mongodb.uri=mongodb://localhost:27017/db1 # 数据源2 mongodb2.uri=mongodb://localhost:27017/db2 ``` 3. 配置多数据源 创建多个数据源的配置类,继承自 `AbstractMongoConfiguration`,并重写 `mongoClient()` 方法: ```java @Configuration public class DataSourceConfig1 extends AbstractMongoConfiguration { @Value("${spring.data.mongodb.uri}") private String uri; @Override protected String getDatabaseName() { return "db1"; } @Override public MongoClient mongoClient() { return new MongoClient(new MongoClientURI(uri)); } } @Configuration public class DataSourceConfig2 extends AbstractMongoConfiguration { @Value("${mongodb2.uri}") private String uri; @Override protected String getDatabaseName() { return "db2"; } @Override public MongoClient mongoClient() { return new MongoClient(new MongoClientURI(uri)); } } ``` 4. 配置动态数据源 创建一个动态数据源,实现 `AbstractRoutingDataSource` 接口,重写 `determineCurrentLookupKey()` 方法: ```java public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DataSourceContextHolder.getDataSource(); } } ``` 5. 配置数据源上下文 创建一个数据源上下文,用于保存当前使用的数据源的名称: ```java public class DataSourceContextHolder { private static final ThreadLocal<String> contextHolder = new ThreadLocal<>(); public static void setDataSource(String dataSource) { contextHolder.set(dataSource); } public static String getDataSource() { return contextHolder.get(); } public static void clearDataSource() { contextHolder.remove(); } } ``` 6. 配置事务管理器 创建一个事务管理器,用于管理多个数据源的事务: ```java @Configuration @EnableTransactionManagement public class TransactionConfig implements TransactionManagementConfigurer { @Autowired private DynamicDataSource dynamicDataSource; @Override public PlatformTransactionManager annotationDrivenTransactionManager() { return new DataSourceTransactionManager(dynamicDataSource); } } ``` 7. 完成动态数据源配置 在 `Application` 类中完成动态数据源的配置: ```java @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } @Bean public DynamicDataSource dynamicDataSource(DataSourceConfig1 dataSourceConfig1, DataSourceConfig2 dataSourceConfig2) { Map<Object, Object> targetDataSources = new HashMap<>(); targetDataSources.put("db1", dataSourceConfig1.mongoClient()); targetDataSources.put("db2", dataSourceConfig2.mongoClient()); DynamicDataSource dynamicDataSource = new DynamicDataSource(); dynamicDataSource.setDefaultTargetDataSource(dataSourceConfig1.mongoClient()); dynamicDataSource.setTargetDataSources(targetDataSources); return dynamicDataSource; } @Bean public MongoClient mongoClient(DynamicDataSource dynamicDataSource) { return dynamicDataSource; } @Bean public MongoTemplate mongoTemplate(DynamicDataSource dynamicDataSource) { return new MongoTemplate(dynamicDataSource); } } ``` 至此,就完成了 Spring Boot 整合 MongoDB 多数据源动态切换的配置。在需要切换数据源的地方,可以使用 `DataSourceContextHolder.setDataSource("db1")` 进行动态切换

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值