MongoDB 多数据源动态配置

  • Post author:
  • Post category:其他




一、前言

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



二、简介

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


需要注意的是

: 当保存的文件大于16M时,需要使用

GridFsTemplate

来保存(

GridFsTemplate

本质上使用的还是和

MongoTemplate

类似,只不过为了保存大数据,

GridFsTemplate

将文件切块处理,每一块255kb。) ,所以当保存小文件时,使用

MongoTemplate

更好。



三、实现

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



1. MongoDBFactory


MongoDBFactory

中保存了多数据源的

MongoTemplate



GridFsTemplate



MongoDbFactory

。使用某个数据源时传入对应的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

依赖会自动根据配置文件注入

MongoTemplate



GridFsTemplate



MongoDbFactory

。而我们这里统一使用

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

中完成了

MongoTemplate



MappingMongoConverter



GridFsTemplate

的注入。并且实现了

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搭建了副本集。关于副本集的搭建可以移步

这里


使用如下:

在这里插入图片描述



以上:内容部分参考网络

如有侵扰,联系删除。 内容仅用于自我记录学习使用。如有错误,欢迎指正



版权声明:本文为qq_36882793原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。