MongoDB 多数据源动态配置

一、前言

项目需要,单一MongoDB实例、多数据源配置。而百度发现,大部分都是通过声明多个Template实例或者其他类似的方式实现。但这种方式无法进行扩展(总不能添加一个数据源就再创建一个Template实例吧)。所以决定自己写一写。

二、简介

MongoDB是一个基于分布式文件存储 [1] 的数据库。由C++语言编写。旨在为WEB应用提供可扩展的高性能数据存储解决方案。MongoDB是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。它支持的数据结构非常松散,是类似json的bson格式,因此可以存储比较复杂的数据类型。Mongo最大的特点是它支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。

需要注意的是: 当保存的文件大于16M时,需要使用 GridFsTemplate 来保存(GridFsTemplate 本质上使用的还是和MongoTemplate类似,只不过为了保存大数据, GridFsTemplate 将文件切块处理,每一块255kb。) ,所以当保存小文件时,使用 MongoTemplate 更好。

三、实现

思路很简单,通过三个Map文件保存对应数据源的模板类型,需要使用时传入对应key就可以拿到相应的数据源。当需要增加数据源时,仅需要增加配置文件中的数据源路径即可,而不需要再重新创建模板类以及其他乱七八糟的文件。下面的代码实现还不是很成熟,并没有考虑到数据源用户名、密码。以及一些每个数据源的独立配置等,也就是说并没有实现个性化的配置,仅保证了最简单的基础使用。

1. MongoDBFactory

MongoDBFactory 中保存了多数据源的 MongoTemplateGridFsTemplateMongoDbFactory。使用某个数据源时传入对应的key值即可获取到对应的数据源。

package com.kingfish.commons.config;

import com.kingfish.pojo.MongoDBInfo;
import com.mongodb.ClientSessionOptions;
import com.mongodb.DB;
import com.mongodb.client.ClientSession;
import com.mongodb.client.MongoDatabase;
import org.springframework.boot.autoconfigure.mongo.MongoProperties;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.support.PersistenceExceptionTranslator;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.SimpleMongoClientDbFactory;
import org.springframework.data.mongodb.gridfs.GridFsTemplate;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * @Data: 2019/12/17
 * @Des: mongoTemplate 工厂类
 */
public class MongoDBFactory {
    
    
    private int size;
    private List<String> mongodbUris;
    private List<MongoDB> mongoDBList = new CopyOnWriteArrayList<>();

    /**
     * 初始化并 mongoTemplate 并保存
     *
     * @param mongodbUris
     */
    public MongoDBFactory(List<String> mongodbUris) {
    
    
        if (CollectionUtils.isEmpty(mongodbUris)) {
    
    
            throw new RuntimeException("数据集不能为空");
        }

        this.mongodbUris = mongodbUris;
        this.size = mongodbUris.size();

        for (int i = 0; i < this.mongodbUris.size(); i++) {
    
    
            String mongodbUri = this.mongodbUris.get(i);

            SimpleMongoClientDbFactory simpleMongoClientDbFactory = new SimpleMongoClientDbFactory(mongodbUri);
            MongoTemplate mongoTemplate = new MongoTemplate(simpleMongoClientDbFactory);

            // 通过 MongoProperties 可以设置端口号、数据库、用户名、密码等其他信息,需要时可通过此扩展
            MongoProperties mongoProperties = new MongoProperties();
            mongoProperties.setUri(mongodbUri);
            GridFsMongoDbFactory gridFsMongoDbFactory = new GridFsMongoDbFactory(simpleMongoClientDbFactory, mongoProperties);
            GridFsTemplate gridFsTemplate = new GridFsTemplate(gridFsMongoDbFactory, mongoTemplate.getConverter());

            MongoTransactionManager mongoTransactionManager = new MongoTransactionManager(simpleMongoClientDbFactory);
            TransactionTemplate transactionTemplate = new TransactionTemplate(mongoTransactionManager);
            MongoDB mongoDB = new MongoDB(mongoTemplate, gridFsTemplate, simpleMongoClientDbFactory, transactionTemplate);
            this.mongoDBList.add(mongoDB);
        }
    }

    /**
     * 获取数据源数量
     *
     * @return
     */
    public int getDataBaseSize() {
    
    
        return this.mongodbUris.size();
    }

    /**
     * 获取mongoDB 模板类, 使用key值hash分布
     *
     * @param key
     * @return
     */
    public MongoDB getMongoDBTemplate(String key) {
    
    
        return mongoDBList.get(key.hashCode() & (size - 1));
    }

    /**
     * 获取mongoDB 模板类, 使用下标获取
     *
     * @param index
     * @return
     */
    public MongoDB getMongoDBTemplate(int index) {
    
    
        return mongoDBList.get(index);
    }

    /**
     * 获取配置信息存储的模块类 -- 启动时将配置库作为第一个插入,所以这里获取的是第一个
     *
     * @param
     * @return
     */
    public MongoDB getDefaultMongo() {
    
    
        return mongoDBList.get(0);
    }


    /**
     * GridFsMongoDbFactory -- 抄自 MongoDbFactoryDependentConfiguration
     */
    static class GridFsMongoDbFactory implements MongoDbFactory {
    
    

        private final MongoDbFactory mongoDbFactory;

        private final MongoProperties properties;

        GridFsMongoDbFactory(MongoDbFactory mongoDbFactory, MongoProperties properties) {
    
    
            Assert.notNull(mongoDbFactory, "MongoDbFactory must not be null");
            Assert.notNull(properties, "Properties must not be null");
            this.mongoDbFactory = mongoDbFactory;
            this.properties = properties;
        }

        @Override
        public MongoDatabase getDb() throws DataAccessException {
    
    
            String gridFsDatabase = this.properties.getGridFsDatabase();
            if (StringUtils.hasText(gridFsDatabase)) {
    
    
                return this.mongoDbFactory.getDb(gridFsDatabase);
            }
            return this.mongoDbFactory.getDb();
        }

        @Override
        public MongoDatabase getDb(String dbName) throws DataAccessException {
    
    
            return this.mongoDbFactory.getDb(dbName);
        }

        @Override
        public PersistenceExceptionTranslator getExceptionTranslator() {
    
    
            return this.mongoDbFactory.getExceptionTranslator();
        }

        @Override
        @Deprecated
        public DB getLegacyDb() {
    
    
            return this.mongoDbFactory.getLegacyDb();
        }

        @Override
        public ClientSession getSession(ClientSessionOptions options) {
    
    
            return this.mongoDbFactory.getSession(options);
        }

        @Override
        public MongoDbFactory withSession(ClientSession session) {
    
    
            return this.mongoDbFactory.withSession(session);
        }
    }

}

MongoDBInfo

/**
 * @Data: 2019/12/18
 * @Des: Mongo 数据源配置
 */
public class MongoDB {
    
    
    private MongoTemplate mongoTemplate;
    private GridFsTemplate gridfsTemplate;
    private MongoDbFactory mongodbFactory;
    private TransactionTemplate transactionTemplate;

    public MongoDB(MongoTemplate mongoTemplate, GridFsTemplate gridfsTemplate, MongoDbFactory mongodbFactory, TransactionTemplate transactionTemplate) {
    
    
        this.mongoTemplate = mongoTemplate;
        this.gridfsTemplate = gridfsTemplate;
        this.mongodbFactory = mongodbFactory;
        this.transactionTemplate = transactionTemplate;
    }

    public MongoTemplate getMongoTemplate() {
    
    
        return mongoTemplate;
    }

    public GridFsTemplate getGridfsTemplate() {
    
    
        return gridfsTemplate;
    }

    public MongoDbFactory getMongodbFactory() {
    
    
        return mongodbFactory;
    }

    public TransactionTemplate getTransactionTemplate() {
    
    
        return transactionTemplate;
    }
}

2. MongoDBConfig

在这里将 MongoDBFactory 注入到Spring容器中

package com.kingfish.commons.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.List;

/**
 * @Data: 2019/12/13
 * @Des: mongoDB 配置类
 */
@Configuration
public class MongoDBConfig {
    
    
    @Value("${spring.data.mongodb.uris}")
    private List<String> mongodbUris;

    /**
     * 注入mongoTemplateFactory工厂类
     * @return
     */
    @Bean
    public MongoDBFactory mongoTemplateFactory(){
    
    
        return new MongoDBFactory(mongodbUris);
    }
}

3. 禁用mongodb的自动配置

因为SpringBoot 提供的 spring-boot-starter-data-mongodb 依赖会自动根据配置文件注入 MongoTemplateGridFsTemplateMongoDbFactory 。而我们这里统一使用 MongoDBFactory 管理,因此禁用 自动配置。

package com.kingfish;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
import org.springframework.transaction.annotation.EnableTransactionManagement;

/**
 * @Data: 2019/12/6
 * @Des: 禁用mongodb自动配置
 */
@SpringBootApplication(exclude = {
    
    MongoAutoConfiguration.class, MongoDataAutoConfiguration.class})
@EnableTransactionManagement
public class SpringBootServer {
    
    
    public static void main(String[] args) {
    
    
       SpringApplication.run(SpringBootServer.class);
    }
}

4. MongoDB 的自动注入

上面禁用了MongoDb的自动配置,下面来看看这两个配置类中干了什么。

4.1 MongoAutoConfiguration

MongoAutoConfiguration 配置类中注入了 MongoClient
在这里插入图片描述

4.2 MongoDataAutoConfiguration

MongoDataAutoConfiguration 配置类虽然自身没写什么,但是他引入了 MongoDataConfiguration, MongoDbFactoryConfiguration, MongoDbFactoryDependentConfiguration 配置类,并且在 MongoDbFactoryDependentConfiguration 中完成了 MongoTemplateMappingMongoConverterGridFsTemplate 的注入。并且实现了 GridFsMongoDbFactory 类继承了 MongoDbFactory。上面 MongoDBFactory 中的 GridFsMongoDbFactory 就是从这里粘贴过来的。
在这里插入图片描述

5. 测试

1、yml 配置如下。创建了两个数据源 test1 和 test2

# 多数据源 mongodb实例配置
spring:
  data:
    mongodb:
      uris: mongodb://localhost:10001/test1, mongodb://localhost:10001/test2
      default-uri: mongodb://localhost:10001/test1  # 默认的库额外还用来存一些配置或者不必要分库的数据
      exclude-default: false   # 是否排除默认的库用来存储数据,
      option:
        max-connection-per-host: 100
        max-wait-time: 120000
        threads-allowed-to-block-for-connection-multiplier: 10
        connect-timeout: 10000
        socket-keep-alive: false
        socket-timeout: 0
server:
  port: 8080
  servlet:
    context-path: /boot

2. 一个保存文件的业务逻辑
会根据文件的id和数据源的数量进行取模运算,计算出文件保存的数据库

  /**
     * 保存文件,小于16M
     *
     * @param file
     * @return
     * @throws IOException
     */
    @Override
    public String saveDoc(MultipartFile file) throws IOException {
    
    
        File desFile = Paths.get("E:", file.getOriginalFilename()).toFile();
        desFile.delete();
        desFile.createNewFile();
        file.transferTo(desFile);
        try (FileInputStream inputStream = new FileInputStream(desFile)) {
    
    
            byte[] bytes = new byte[inputStream.available()];
            inputStream.read(bytes);
            DocFile document = new DocFile();
            document.set_id(UUID.randomUUID().toString());
            document.setData(bytes);
            document.setFile_name(desFile.getName());
            MongoTemplate mongoTemplate = mongoDBFactory.getMongoDBTemplate(document.get_id()).getMongoTemplate();
            System.out.println("####   " + desFile.getName() + " 保存的数据库: " + mongoTemplate.getDb().getName());
            return mongoTemplate.save(document, "file").toString();
        } finally {
    
    
            desFile.delete();
        }
    }

DocFile

public class DocFile{
    
    
    private String _id;
    private String path;
    private String status;
    private byte[] data;
    private int prefixLength;
    private String file_name;

   //  省略get、set方法
}

三次文件保存,可以看到分发到了不同的数据源中保存。
在这里插入图片描述

  1. 查询试试
    @Override
    public DocFile findFileById(String id) {
    
    
        Query query = new Query();
        query.addCriteria(Criteria.where("_id").is(id));
        MongoTemplate mongoTemplate = mongoDBFactory.getMongoTemplate(id);
        DocFile file = mongoTemplate.findOne(query, DocFile.class, "file");
        System.out.println("### 选择的数据库:" +  mongoTemplate.getDb().getName());
        System.out.println("### 查询出的文件内容 : " + file.toString());
        return file;
    }

查询成功,大功告成

在这里插入图片描述

优化后,加入了编程式事务的控制。需要注意,事务的使用需要Mongo4.0版本及以上且Mong搭建了副本集。关于副本集的搭建可以移步这里
使用如下:
在这里插入图片描述


以上:内容部分参考网络
如有侵扰,联系删除。 内容仅用于自我记录学习使用。如有错误,欢迎指正

猜你喜欢

转载自blog.csdn.net/qq_36882793/article/details/103582644