文章目录
一、前言
项目需要,单一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搭建了副本集。关于副本集的搭建可以移步
这里
使用如下:
以上:内容部分参考网络
如有侵扰,联系删除。 内容仅用于自我记录学习使用。如有错误,欢迎指正