文章目录
    
    
    
    一、前言
   
项目需要,单一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方法
}
    
     三次文件保存,可以看到分发到了不同的数据源中保存。
    
    
     
   
- 查询试试
    @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搭建了副本集。关于副本集的搭建可以移步
    
     这里
    
    
    使用如下:
    
     
   
    
     以上:内容部分参考网络
     
     如有侵扰,联系删除。 内容仅用于自我记录学习使用。如有错误,欢迎指正
    
   
 
