0
点赞
收藏
分享

微信扫一扫

MongoDB多租户方案设计

程序员知识圈 2022-03-12 阅读 122

MongoDB多租户方案设计

文章目录

一、前言

二、常见的多租户方案

  • DB per tenant
  • Schema per tenant
  • Discriminator field

三、MongoDB 多租户方案

1.pom.xml

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-mongodb</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

2.application.yml

spring:
  data:
    mongodb:
      host: localhost
      port: 27017
      database: tenant-default
      username: admin
      password: 123456
      authentication-database: admin
      auto-index-creation: false
logging:
  level:
    org.springframework.data.mongodb.core: debug

3.multi-mongo-spring-boot-starter

D:.
│  pom.xml
│
├─src
│  └─main
│      ├─java
│      │  └─com
│      │      └─example
│      │          └─demo
│      │              └─mongo
│      │                  ├─autoconfigure
│      │                  │      MongoMultiTenantAutoConfiguration.java 自动配置类
│      │                  │
│      │                  ├─context
│      │                  │      MongoContextHolder.java ThreadLocal DB上下文
│      │                  │
│      │                  ├─factory
│      │                  │      MongoMultiTenantFactory.java  MongoDB数据库工厂类(非连接工厂) 
│      │                  │
│      │                  ├─filter
│      │                  │      MongoContextFilter.java Web Filter
│      │                  │      OrderedMongoContextFilter.java Ordered Web Filter
│      │                  │
│      │                  └─provider
│      │                          MongoMultiTenantNameProvider.java 多租户DB名称提供者(接口)
│      │
│      └─resources
│          └─META-INF
│                  spring.factories

4.代码

  • MongoMultiTenantAutoConfiguration
package com.example.demo.mongo.autoconfigure;

import com.example.demo.mongo.factory.MongoMultiTenantFactory;
import com.example.demo.mongo.filter.MongoContextFilter;
import com.example.demo.mongo.filter.OrderedMongoContextFilter;
import com.example.demo.mongo.helper.MongoMultiTenantHelper;
import com.example.demo.mongo.provider.MongoMultiTenantNameProvider;
import com.mongodb.client.MongoClient;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.MongoProperties;
import org.springframework.boot.autoconfigure.web.servlet.ConditionalOnMissingFilterBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.MongoDatabaseFactory;
import org.springframework.data.mongodb.core.MongoDatabaseFactorySupport;
import org.springframework.data.mongodb.core.MongoTemplate;

@Configuration(proxyBeanMethods = false)
@AutoConfigureAfter(MongoAutoConfiguration.class)
public class MongoMultiTenantAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean(MongoDatabaseFactory.class)
    MongoDatabaseFactorySupport<?> mongoDatabaseFactory(MongoClient mongoClient, MongoProperties properties) {
        return new MongoMultiTenantFactory(mongoClient, properties.getMongoClientDatabase());
    }

    /**
     * 默认MongoDB租户数据库
     *
     * @param properties MongoDB配置
     * @return MongoDB租户数据库名
     */
    @Bean
    @ConditionalOnMissingBean(MongoMultiTenantNameProvider.class)
    public MongoMultiTenantNameProvider defaultTenantProvider(MongoProperties properties) {
        return properties::getDatabase;
    }

    /**
     * 线程上下文(租户)
     *
     * @param mongoMultiTenantNameProvider MongoDB租户数据库名提供者
     * @return 线程上下文(租户)Filter
     */
    @Bean
    @ConditionalOnWebApplication
    @ConditionalOnBean(MongoMultiTenantNameProvider.class)
    @ConditionalOnMissingBean(MongoContextFilter.class)
    @ConditionalOnMissingFilterBean(MongoContextFilter.class)
    public MongoContextFilter mongodbContextFilter(MongoMultiTenantNameProvider mongoMultiTenantNameProvider) {
        return new OrderedMongoContextFilter(mongoMultiTenantNameProvider);
    }

}

  • MongoContextHolder
package com.example.demo.mongo.context;

import com.example.demo.mongo.filter.MongoContextFilter;

/**
 * MongoDB上下文对象
 *
 */
public abstract class MongoContextHolder {

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

    public static void setDbName(String dbName) {
        context.set(dbName);
    }

    public static String getDbName() {
        return context.get();
    }

    public static void reset() {
        context.remove();
    }

}

  • MongoMultiTenantFactory
package com.example.demo.mongo.factory;

import com.example.demo.mongo.context.MongoContextHolder;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoDatabase;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.mongodb.core.SimpleMongoClientDatabaseFactory;
import org.springframework.util.StringUtils;

public class MongoMultiTenantFactory extends SimpleMongoClientDatabaseFactory {

    private static final Logger logger = LoggerFactory.getLogger(MongoMultiTenantFactory.class);

    public MongoMultiTenantFactory(MongoClient mongoClient, String databaseName) {
        super(mongoClient, databaseName);
    }

    /**
     * 切换租户MongoDB数据库
     *
     * @param dbName 数据库名称
     * @return 租户MongoDB数据库
     */
    @Override
    protected MongoDatabase doGetMongoDatabase(String dbName) {
        // 从线程上下文获取MongoDB数据库名称 
        final String context = MongoContextHolder.getDbName();
        String target = dbName;
        if (StringUtils.hasLength(context)) {
            target = context;
            logger.debug("MongoDB switch to {}", context);
        }
        return super.doGetMongoDatabase(target);
    }

}
  • MongoContextFilter
package com.example.demo.mongo.filter;

import com.example.demo.mongo.context.MongoContextHolder;
import com.example.demo.mongo.provider.MongoMultiTenantNameProvider;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 线程上下文(租户)
 *
 */
public class MongoContextFilter extends OncePerRequestFilter {

    private MongoMultiTenantNameProvider mongoMultiTenantNameProvider;

    public MongoContextFilter(MongoMultiTenantNameProvider mongoMultiTenantNameProvider) {
        this.mongoMultiTenantNameProvider = mongoMultiTenantNameProvider;
    }

    @Override
    protected boolean shouldNotFilterAsyncDispatch() {
        return false;
    }

    @Override
    protected boolean shouldNotFilterErrorDispatch() {
        return false;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        initContextHolders();
        try {
            filterChain.doFilter(request, response);
        } finally {
            resetContextHolders();
        }
    }

    private void initContextHolders() {
        final String dbName = mongoMultiTenantNameProvider.getTenantMongodbName();
        MongoContextHolder.setDbName(dbName);
    }

    private void resetContextHolders() {
        MongoContextHolder.reset();
    }

}

  • OrderedMongoContextFilter
package com.example.demo.mongo.filter;

import com.example.demo.mongo.provider.MongoMultiTenantNameProvider;
import org.springframework.core.Ordered;

/**
 * 线程上下文(租户)
 *
 */
public class OrderedMongoContextFilter extends MongoContextFilter implements Ordered {

    private int order = Ordered.LOWEST_PRECEDENCE;

    public OrderedMongoContextFilter(MongoMultiTenantNameProvider mongoMultiTenantNameProvider) {
        super(mongoMultiTenantNameProvider);
    }

    @Override
    public int getOrder() {
        return order;
    }

    public void setOrder(int order) {
        this.order = order;
    }

}
  • MongoMultiTenantNameProvider
package com.example.demo.mongo.provider;

public interface MongoMultiTenantNameProvider {
    /**
     * 获取数据库
     * @return 数据库名
     */
    String getTenantMongodbName();
}

  • MongoConfiguration
package com.example.demo.bussiness.config;

import com.example.demo.common.context.TenantContextHolder;
import com.example.demo.mongo.provider.MongoMultiTenantNameProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MongoConfiguration {

    /**
     * @return MongoDB租户数据库名提供者
     */
    @Bean
    public MongoMultiTenantNameProvider mongodbTenantProvider() {
        return TenantContextHolder::getTenant;
    }
}

四、调用链

说明:从上面调用可以看出Spring Data Mongo本质是调用MongoTemplate模板类中的方法,所有你也不用担心直接使用MongoTemplate会不会有问题。

举报

相关推荐

0 条评论