【完整源码】SpringBoot+MongoDB实现多租户切换

目标:实现不同分组的用户访问自己的专用数据库 (数据库在同一个服务器). 例:A 公司用户,仅可访问 A 公司的专用数据库

要求:MongoDB + SpringBoot

Pom 依赖:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.3.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>
 
<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>1.8</java.version>
</properties>
 
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-mongodb</artifactId>
    </dependency>
 
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>spring-data-mongodb</artifactId>
    </dependency>
    <dependency>
        <groupId>org.mongodb</groupId>
        <artifactId>mongo-java-driver</artifactId>
    </dependency>
    <!--添加REST依赖-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-rest</artifactId>
    </dependency>
</dependencies>

1. 自定义 MongoDBFactoty

/**
 * 类的描述:
 * <p>自定义MongoDB Factory </p>
 * 自定义MongoDb 配置工厂,可实现多租户切换访问。
 *
 * @author jei0439
 * @date 2018/9/14 16:30
 */
public class MultiTenantMongoDbFactory extends SimpleMongoDbFactory {
 
    private static final Logger logger = LoggerFactory.getLogger(MultiTenantMongoDbFactory.class);
 
    /**
     * 默认数据库名称
     **/
    private final String defaultName;
 
    /**
     * MongoDB模板类
     **/
    private MongoTemplate mongoTemplate;
 
    /**
     * 用户所在线程使用数据库集合
     **/
    private static final ThreadLocal<String> dbName = new ThreadLocal<String>();
    /**
     *
     **/
    private static final HashMap<String, Object> databaseIndexMap = new HashMap<String, Object>();
 
    public MultiTenantMongoDbFactory(final MongoClient mongo, final String defaultDatabaseName) {
        super(mongo, defaultDatabaseName);
        logger.debug("Instantiating " + MultiTenantMongoDbFactory.class.getName() + " with default database name: " + defaultDatabaseName);
        this.defaultName = defaultDatabaseName;
    }
 
    /**
     *
     * 功能描述:  dirty but ... what can I do?
     *
     * @param
     * @author jei0439
     * @date 2018/9/17 10:55
     */
    public void setMongoTemplate(final MongoTemplate mongoTemplate) {
        Assert.isNull(this.mongoTemplate, "You can set MongoTemplate just once");
        this.mongoTemplate = mongoTemplate;
    }
 
    /**
     *
     * 功能描述: 将databaseName放入dbName
     *
     * @param databaseName database/scheme名称
     * @author jei0439
     * @date 2018/9/17 10:56
     */
    public static void setDatabaseNameForCurrentThread(final String databaseName) {
        logger.debug("Switching to database: " + databaseName);
        dbName.set(databaseName);
    }
 
    /**
     *
     * 功能描述: 清空dbName
     *
     * @author jei0439
     * @date 2018/9/17 10:57
     */
    public static void clearDatabaseNameForCurrentThread() {
        if (logger.isDebugEnabled()) {
            logger.debug("Removing database [" + dbName.get() + "]");
        }
        dbName.remove();
    }
 
    @Override
    public MongoDatabase getDb() {
        final String tlName = dbName.get();
        final String dbToUse = (tlName != null ? tlName : this.defaultName);
        logger.debug("Acquiring database: " + dbToUse);
        createIndexIfNecessaryFor(dbToUse);
        return super.getDb(dbToUse);
    }
 
    /**
     *
     * 功能描述: TODO: 没搞明白作用
     *
     * @param database  database/scheme 名称
     * @author jei0439
     * @date 2018/9/17 10:58
     */
    private void createIndexIfNecessaryFor(final String database) {
        if (this.mongoTemplate == null) {
            logger.error("MongoTemplate is null, will not create any index.");
            return;
        }
//        sync and init once
        boolean needsToBeCreated = false;
        synchronized (MultiTenantMongoDbFactory.class) {
            final Object syncObj = databaseIndexMap.get(database);
            if (syncObj == null) {
                databaseIndexMap.put(database, new Object());
                needsToBeCreated = true;
            }
        }
//        make sure only one thread enters with needsToBeCreated = true
        synchronized (databaseIndexMap.get(database)) {
            if (needsToBeCreated) {
                logger.debug("Creating indices for database name=[" + database + "]");
                createIndexes();
                logger.debug("Done with creating indices for database name=[" + database + "]");
            }
        }
    }
 
    private void createIndexes() {
        final MongoMappingContext mappingContext = (MongoMappingContext) this.mongoTemplate.getConverter().getMappingContext();
        final MongoPersistentEntityIndexResolver indexResolver = new MongoPersistentEntityIndexResolver(mappingContext);
        for (BasicMongoPersistentEntity<?> persistentEntity : mappingContext.getPersistentEntities()) {
            checkForAndCreateIndexes(indexResolver, persistentEntity);
        }
    }
 
    private void checkForAndCreateIndexes(final MongoPersistentEntityIndexResolver indexResolver, final MongoPersistentEntity<?> entity) {
//        make sure its a root document
        if (entity.findAnnotation(Document.class) != null) {
            for (IndexDefinitionHolder indexDefinitionHolder : indexResolver.resolveIndexFor(entity.getTypeInformation())) {
//                work because of javas reentered lock feature
                this.mongoTemplate.indexOps(entity.getType()).ensureIndex(indexDefinitionHolder);
            }
        }
    }
}

2.MongoConfig

/**
 * 类的描述:
 * <p>自定义MongoDB Config </p>
 * Spring 此处文件配置初始化 MongoDB 配置、自定义MongoDB Factory
 *
 * @author jei0439
 * @date 2018/9/14 16:30
 */
@Configuration
@EnableAutoConfiguration
public class MongoConfig {
    private final MongoClientOptions options;
    private final MongoClientFactory factory;
    private MongoClient mongo;
 
    /**
     *
     * 功能描述: 构造函数  直接使用MongoAutoConfiguration构造函数 参数初始化由Spring完成
     *
     * @param properties MongoDB 配置数据
     * @param options  TODO: 待完善 断点跟踪Spring框架传参进来是null
     * @param environment TODO: 待完善 断点跟踪Spring框架传参进来是null
     * @author jei0439
     * @date 2018/9/17 10:48
     */
    public MongoConfig(MongoProperties properties, ObjectProvider<MongoClientOptions> options, Environment environment) {
        this.options = (MongoClientOptions)options.getIfAvailable();
        this.factory = new MongoClientFactory(properties, environment);
    }
    /**
     *
     * 功能描述: 覆盖默认的MongoDbFactory
     *
     * @author jei0439
     * @date 2018/9/17 10:51
     */
    @Bean
    MultiTenantMongoDbFactory mongoDbFactory() {
        this.mongo = this.factory.createMongoClient(this.options);
        MultiTenantMongoDbFactory mongoDbFactory = new MultiTenantMongoDbFactory(mongo,"pvQC_dev");
        return mongoDbFactory;
    }
 
    /**
     *
     * 功能描述: Create MongoTemplate
     *
     * @author jei0439
     * @date 2018/9/17 10:51
     */
    @Bean
    public MongoTemplate mongoTemplate() throws Exception {
        return new MongoTemplate(mongoDbFactory());
    }
 
}

3. 测试 Entity

/**
 * 类的描述:
 * <p></p>
 *
 * @author jei0439
 * @date 2018/9/17 10:30
 */
 
public class User {
    @Id
    private Long id;
 
    private String name;
 
    public Long getId() {
        return id;
    }
 
    public void setId(Long id) {
        this.id = id;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
}

4.Repositoty

/**
 * 类的描述:
 * <p></p>
 *
 * @author jei0439
 * @date 2018/9/17 10:31
 */
@Component
public interface Repository extends MongoRepository<User,Long> {
    User getById(Long id);
    User getByName(String name);
}

5.Application (随便测试)

@SpringBootApplication
public class DemoApplication  implements CommandLineRunner {
 
 
 
    @Autowired
    Repository repository;
 
 
 
    @Override
    public void run(final String... args) throws Exception {
//        add something to the default database (test)
        User user = new User();
        user.setName("张三");
        user.setId(1L);
//      this.repository.save(user);
 
        System.out.println("data from test: " + this.repository.findAll());
//        okay? fine. - lets switch the database
        MultiTenantMongoDbFactory.setDatabaseNameForCurrentThread("test666");
 
        this.repository.save(user);
//        should be empty
        System.out.println("data from test666: " + this.repository.findAll());
 
//        switch back and clean up
        MultiTenantMongoDbFactory.setDatabaseNameForCurrentThread("test");
        this.repository.save(user);
    }
 
 
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

运行测试结果


作者:jei0439

来源链接:

https://blog.csdn.net/jei0439/article/details/82735055

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值