package com.xiaoleilu.hutool.db.ds;
import java.io.Closeable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.bson.Document;
import com.mongodb.MongoClient;
import com.mongodb.MongoClientOptions;
import com.mongodb.MongoClientOptions.Builder;
import com.mongodb.ServerAddress;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import com.xiaoleilu.hutool.db.DbRuntimeException;
import com.xiaoleilu.hutool.exceptions.NotInitedException;
import com.xiaoleilu.hutool.log.Log;
import com.xiaoleilu.hutool.log.StaticLog;
import com.xiaoleilu.hutool.setting.Setting;
import com.xiaoleilu.hutool.util.ArrayUtil;
import com.xiaoleilu.hutool.util.CollectionUtil;
import com.xiaoleilu.hutool.util.NetUtil;
import com.xiaoleilu.hutool.util.StrUtil;
/**
* MongoDB工具类
* @author xiaoleilu
*
*/
public class MongoDS implements Closeable{
private final static Log log = StaticLog.get();
/** 默认配置文件 */
public final static String MONGO_CONFIG_PATH = "config/mongo.setting";
/** 各分组做组合key的时候分隔符 */
private final static String GROUP_SEPRATER = ",";
/** 数据源池 */
private static Map<String, MongoDS> dsMap = new HashMap<String, MongoDS>();
//MongoDB配置文件
private Setting setting;
//MongoDB实例连接列表
private String[] groups;
//MongoDB单点连接信息
private ServerAddress serverAddress;
//MongoDB客户端对象
private MongoClient mongo;
//JVM关闭前关闭MongoDB连接
static{
Runtime.getRuntime().addShutdownHook(new Thread(){
@Override
public void run() {
MongoDS.closeAll();
}
});
}
//------------------------------------------------------------------------ Get DS start
/**
* 获取MongoDB数据源<br>
* @param host 主机
* @param port 端口
* @return MongoDB连接
*/
public static MongoDS getDS(String host, int port) {
final String key = host + ":" + port;
MongoDS ds = dsMap.get(key);
if(null == ds) {
//没有在池中加入之
ds = new MongoDS(host, port);
dsMap.put(key, ds);
}
return ds;
}
/**
* 获取MongoDB数据源<br>
* @param groups 分组列表
* @return MongoDB连接
*/
public static MongoDS getDS(String... groups) {
final String key = ArrayUtil.join(groups, GROUP_SEPRATER);
MongoDS ds = dsMap.get(key);
if(null == ds) {
//没有在池中加入之
ds = new MongoDS(groups);
dsMap.put(key, ds);
}
return ds;
}
/**
* 获取MongoDB数据源<br>
* @param groups 分组列表
* @return MongoDB连接
*/
public static MongoDS getDS(Collection<String> groups) {
return getDS(groups.toArray(new String[groups.size()]));
}
/**
* 获取MongoDB数据源<br>
* @param setting 设定文件
* @param groups 分组列表
* @return MongoDB连接
*/
public static MongoDS getDS(Setting setting, String... groups) {
final String key = setting.getSettingPath() + GROUP_SEPRATER + ArrayUtil.join(groups, GROUP_SEPRATER);
MongoDS ds = dsMap.get(key);
if(null == ds) {
//没有在池中加入之
ds = new MongoDS(setting, groups);
dsMap.put(key, ds);
}
return ds;
}
/**
* 获取MongoDB数据源<br>
* @param groups 分组列表
* @return MongoDB连接
*/
public static MongoDS getDS(Setting setting, Collection<String> groups) {
return getDS(setting, groups.toArray(new String[groups.size()]));
}
//------------------------------------------------------------------------ Get DS end
/**
* 关闭全部连接
*/
public static void closeAll() {
if(CollectionUtil.isNotEmpty(dsMap)){
for(MongoDS ds : dsMap.values()) {
ds.close();
}
dsMap.clear();
}
}
//--------------------------------------------------------------------------- Constructor start
/**
* 构造MongoDB数据源<br>
* 调用者必须持有MongoDS实例,否则会被垃圾回收导致写入失败!
* @param host 主机(域名或者IP)
* @param port 端口
*/
public MongoDS(String host, int port) {
this.serverAddress = createServerAddress(host, port);
initSingle();
}
/**
* 构造MongoDB数据源<br>
* 调用者必须持有MongoDS实例,否则会被垃圾回收导致写入失败!
* @param mongoSetting MongoDB的配置文件,如果是null则读取默认配置文件或者使用MongoDB默认客户端配置
* @param host 主机(域名或者IP)
* @param port 端口
*/
public MongoDS(Setting mongoSetting, String host, int port) {
this.setting = mongoSetting;
this.serverAddress = createServerAddress(host, port);
initSingle();
}
/**
* 构造MongoDB数据源<br>
* 当提供多个数据源时,这些数据源将为一个副本集或者多个mongos<br>
* 调用者必须持有MongoDS实例,否则会被垃圾回收导致写入失败!
* 官方文档: http://docs.mongodb.org/manual/administration/replica-sets/
* @param groups 分组列表,当为null或空时使用无分组配置,一个分组使用单一模式,否则使用副本集模式
*/
public MongoDS(String... groups) {
this.groups = groups;
init();
}
/**
* 构造MongoDB数据源<br>
* 当提供多个数据源时,这些数据源将为一个副本集或者mongos<br>
* 调用者必须持有MongoDS实例,否则会被垃圾回收导致写入失败!<br>
* 官方文档: http://docs.mongodb.org/manual/administration/replica-sets/
* @param mongoSetting MongoDB的配置文件,必须有
* @param groups 分组列表,当为null或空时使用无分组配置,一个分组使用单一模式,否则使用副本集模式
*/
public MongoDS(Setting mongoSetting, String... groups) {
if(mongoSetting == null) {
throw new DbRuntimeException("Mongo setting is null!");
}
this.setting = mongoSetting;
this.groups = groups;
init();
}
//--------------------------------------------------------------------------- Constructor end
/**
* 初始化,当给定分组数大于一个时使用
*/
public void init() {
if(groups != null && groups.length > 1) {
initCloud();
}else {
initSingle();
}
}
/**
* 初始化<br>
* 设定文件中的host和端口有三种形式:
* ---------------------
* host = host:port
* ---------------------
* host = host
* port = port
* ---------------------
* #此种形式使用MongoDB默认端口
* host = host
* ---------------------
*/
synchronized public void initSingle() {
if(setting == null) {
try {
setting = new Setting(MONGO_CONFIG_PATH, true);
} catch (Exception e) {
//在single模式下,可以没有配置文件。
}
}
String group = StrUtil.EMPTY;
if(serverAddress == null) {
if(groups != null && groups.length ==1) {
group = groups[0];
}
serverAddress = createServerAddress(group);
}
try {
mongo = new MongoClient(serverAddress, buildMongoClientOptions(group));
} catch (Exception e) {
throw new DbRuntimeException(StrUtil.format("Init MongoDB pool with connection to [{}] error!", serverAddress), e);
}
log.info("Init MongoDB pool with connection to [{}]", serverAddress);
}
/**
* 初始化集群<br>
* 集群的其它客户端设定参数使用全局设定<br>
* 集群中每一个实例成员用一个group表示,例如:<br>
* [db0]
* host = 10.11.49.157:27117
* [db1]
* host = 10.11.49.157:27118
* [db2]
* host = 10.11.49.157:27119
*/
synchronized public void initCloud() {
if(groups == null || groups.length == 0) {
throw new DbRuntimeException("Please give replication set groups!");
}
if(setting == null) {
//若未指定配置文件,则使用默认配置文件
setting = new Setting(MONGO_CONFIG_PATH, true);
}
List<ServerAddress> addrList = new ArrayList<ServerAddress>();
for (String group : groups) {
addrList.add(createServerAddress(group));
}
try {
mongo = new MongoClient(addrList, buildMongoClientOptions(StrUtil.EMPTY));
} catch (Exception e) {
log.error(e, "Init MongoDB connection error!");
return;
}
log.info("Init MongoDB cloud Set pool with connection to {}", addrList);
}
/**
* 设定MongoDB配置文件
*/
public void setSetting(Setting setting) {
this.setting = setting;
}
/**
* @return 获得MongoDB客户端对象
*/
public MongoClient getMongo() {
return mongo;
}
/**
* 获得DB
* @param dbName DB
* @return DB
*/
public MongoDatabase getDb(String dbName) {
return mongo.getDatabase(dbName);
}
/**
* 获得MongoDB中指定集合对象
* @param dbName 库名
* @param collectionName 集合名
* @return DBCollection
*/
public MongoCollection<Document> getCollection(String dbName, String collectionName) {
return getDb(dbName).getCollection(collectionName);
}
@Override
public void close() {
mongo.close();
}
//--------------------------------------------------------------------------- Private method start
/**
* 创建ServerAddress对象,会读取配置文件中的相关信息
* @param group 分组,如果为null默认为无分组
* @return ServerAddress
*/
private ServerAddress createServerAddress(String group) {
if(setting == null) {
throw new DbRuntimeException(StrUtil.format("Please indicate setting file or create default [{}], and define group [{}]", MONGO_CONFIG_PATH, group));
}
if(group == null) {
group = StrUtil.EMPTY;
}
String tmpHost = setting.getByGroup("host", group);
if(StrUtil.isBlank(tmpHost)) {
throw new NotInitedException("Host name is empy of group: " + group);
}
final int defaultPort = setting.getInt("port", group, 27017);
return new ServerAddress(NetUtil.buildInetSocketAddress(tmpHost, defaultPort));
}
/**
* 创建ServerAddress对象
* @param host 主机域名或者IP(如果为空默认127.0.0.1)
* @param port 端口(如果为空默认为)
* @return ServerAddress
*/
private ServerAddress createServerAddress(String host, int port) {
return new ServerAddress(host, port);
}
/**
* 构件MongoDB连接选项<br>
* @param group 分组,当分组对应的选项不存在时会读取根选项,如果也不存在使用默认值
* @return MongoClientOptions
*/
private MongoClientOptions buildMongoClientOptions(String group) {
return buildMongoClientOptions(MongoClientOptions.builder(), group).build();
}
/**
* 构件MongoDB连接选项<br>
* @param group 分组,当分组对应的选项不存在时会读取根选项,如果也不存在使用默认值
* @return Builder
*/
private Builder buildMongoClientOptions(Builder builder, String group) {
if(setting == null) {
return builder;
}
if(group == null) {
group = StrUtil.EMPTY;
}else {
group = group + StrUtil.DOT;
}
//每个主机答应的连接数(每个主机的连接池大小),当连接池被用光时,会被阻塞住
Integer connectionsPerHost = setting.getInt(group + "connectionsPerHost");
if(StrUtil.isBlank(group) == false && connectionsPerHost == null) {
connectionsPerHost = setting.getInt("connectionsPerHost");
}
if(connectionsPerHost != null) {
builder.connectionsPerHost(connectionsPerHost);
log.debug("MongoDB connectionsPerHost: {}", connectionsPerHost);
}
//multiplier for connectionsPerHost for # of threads that can block if connectionsPerHost is 10, and threadsAllowedToBlockForConnectionMultiplier is 5, then 50 threads can block more than that and an exception will be throw --int
Integer threadsAllowedToBlockForConnectionMultiplier = setting.getInt(group + "threadsAllowedToBlockForConnectionMultiplier");
if(StrUtil.isBlank(group) == false && threadsAllowedToBlockForConnectionMultiplier == null) {
threadsAllowedToBlockForConnectionMultiplier = setting.getInt("threadsAllowedToBlockForConnectionMultiplier");
}
if(threadsAllowedToBlockForConnectionMultiplier != null) {
builder.threadsAllowedToBlockForConnectionMultiplier(threadsAllowedToBlockForConnectionMultiplier);
log.debug("MongoDB threadsAllowedToBlockForConnectionMultiplier: {}", threadsAllowedToBlockForConnectionMultiplier);
}
//被阻塞线程从连接池获取连接的最长等待时间(ms) --int
Integer connectTimeout = setting.getInt(group + "connectTimeout");
if(StrUtil.isBlank(group) == false && connectTimeout == null) {
setting.getInt("connectTimeout");
}
if(connectTimeout != null) {
builder.connectTimeout(connectTimeout);
log.debug("MongoDB connectTimeout: {}", connectTimeout);
}
//套接字超时时间;该值会被传递给Socket.setSoTimeout(int)。默以为0(无穷) --int
Integer socketTimeout = setting.getInt(group + "socketTimeout");
if(StrUtil.isBlank(group) == false && socketTimeout == null) {
setting.getInt("socketTimeout");
}
if(socketTimeout != null) {
builder.socketTimeout(socketTimeout);
log.debug("MongoDB socketTimeout: {}", socketTimeout);
}
//This controls whether or not to have socket keep alive turned on (SO_KEEPALIVE). defaults to false --boolean
Boolean socketKeepAlive = setting.getBool(group + "socketKeepAlive");
if(StrUtil.isBlank(group) == false && socketKeepAlive == null) {
socketKeepAlive = setting.getBool("socketKeepAlive");
}
if(socketKeepAlive != null) {
builder.socketKeepAlive(socketKeepAlive);
log.debug("MongoDB socketKeepAlive: {}", socketKeepAlive);
}
return builder;
}
//--------------------------------------------------------------------------- Private method end
}