From 9e2fa7d2b61293c246333ba07a1308628dbb1b1f Mon Sep 17 00:00:00 2001 From: VampireAchao Date: Sun, 13 Mar 2022 21:59:27 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A4=AA=E6=A3=92=E4=BA=86=EF=BC=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hutool-db/pom.xml | 11 +- .../cn/hutool/db/nosql/mongo/MongoDS.java | 44 +- .../cn/hutool/db/nosql/mongo/MongoDS4.java | 404 ++++++++++++++++++ .../hutool/db/nosql/mongo/MongoFactory4.java | 120 ++++++ .../java/cn/hutool/db/nosql/MongoDBTest.java | 4 +- 5 files changed, 553 insertions(+), 30 deletions(-) create mode 100644 hutool-db/src/main/java/cn/hutool/db/nosql/mongo/MongoDS4.java create mode 100644 hutool-db/src/main/java/cn/hutool/db/nosql/mongo/MongoFactory4.java diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index c80ea176d..f14a0cc1b 100644 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -23,7 +23,8 @@ 10.0.14 1.2.8 2.4.13 - 4.5.0 + 3.12.10 + 4.5.0 3.36.0.3 2.5.2 @@ -99,10 +100,16 @@ org.mongodb - mongodb-driver-sync + mongo-java-driver ${mongo.version} true + + org.mongodb + mongodb-driver-sync + ${mongo4.version} + true + redis.clients diff --git a/hutool-db/src/main/java/cn/hutool/db/nosql/mongo/MongoDS.java b/hutool-db/src/main/java/cn/hutool/db/nosql/mongo/MongoDS.java index 1c7a4c70f..8158f0d33 100644 --- a/hutool-db/src/main/java/cn/hutool/db/nosql/mongo/MongoDS.java +++ b/hutool-db/src/main/java/cn/hutool/db/nosql/mongo/MongoDS.java @@ -6,28 +6,24 @@ import cn.hutool.core.util.StrUtil; import cn.hutool.db.DbRuntimeException; import cn.hutool.log.Log; import cn.hutool.setting.Setting; -import com.mongodb.MongoClientSettings; +import com.mongodb.MongoClient; +import com.mongodb.MongoClientOptions; +import com.mongodb.MongoClientOptions.Builder; import com.mongodb.MongoCredential; -import com.mongodb.MongoDriverInformation; import com.mongodb.ServerAddress; -import com.mongodb.client.MongoClient; import com.mongodb.client.MongoCollection; import com.mongodb.client.MongoDatabase; -import com.mongodb.client.internal.MongoClientImpl; -import com.mongodb.connection.ConnectionPoolSettings; -import com.mongodb.connection.SocketSettings; import org.bson.Document; import java.io.Closeable; import java.util.ArrayList; -import java.util.Collections; import java.util.List; -import java.util.concurrent.TimeUnit; /** * MongoDB工具类 * * @author xiaoleilu + * */ public class MongoDS implements Closeable { private final static Log log = Log.get(); @@ -150,11 +146,11 @@ public class MongoDS implements Closeable { final MongoCredential credentail = createCredentail(group); try { - MongoClientSettings.Builder clusterSettingsBuilder = MongoClientSettings.builder().applyToClusterSettings(b -> b.hosts(Collections.singletonList(serverAddress))); - if (null != credentail) { - clusterSettingsBuilder.credential(credentail); + if (null == credentail) { + mongo = new MongoClient(serverAddress, buildMongoClientOptions(group)); + } else { + mongo = new MongoClient(serverAddress, credentail, buildMongoClientOptions(group)); } - mongo = new MongoClientImpl(clusterSettingsBuilder.build(), MongoDriverInformation.builder().build()); } catch (Exception e) { throw new DbRuntimeException(StrUtil.format("Init MongoDB pool with connection to [{}] error!", serverAddress), e); } @@ -196,11 +192,11 @@ public class MongoDS implements Closeable { final MongoCredential credentail = createCredentail(StrUtil.EMPTY); try { - MongoClientSettings.Builder clusterSettingsBuilder = MongoClientSettings.builder().applyToClusterSettings(b -> b.hosts(addrList)); - if (null != credentail) { - clusterSettingsBuilder.credential(credentail); + if (null == credentail) { + mongo = new MongoClient(addrList, buildMongoClientOptions(StrUtil.EMPTY)); + } else { + mongo = new MongoClient(addrList, credentail, buildMongoClientOptions(StrUtil.EMPTY)); } - mongo = new MongoClientImpl(clusterSettingsBuilder.build(), MongoDriverInformation.builder().build()); } catch (Exception e) { log.error(e, "Init MongoDB connection error!"); return; @@ -252,7 +248,6 @@ public class MongoDS implements Closeable { } // --------------------------------------------------------------------------- Private method start - /** * 创建ServerAddress对象,会读取配置文件中的相关信息 * @@ -327,8 +322,8 @@ public class MongoDS implements Closeable { * @param group 分组,当分组对应的选项不存在时会读取根选项,如果也不存在使用默认值 * @return MongoClientOptions */ - private MongoClientSettings buildMongoClientOptions(String group) { - return buildMongoClientOptions(MongoClientSettings.builder(), group).build(); + private MongoClientOptions buildMongoClientOptions(String group) { + return buildMongoClientOptions(MongoClientOptions.builder(), group).build(); } /** @@ -337,7 +332,7 @@ public class MongoDS implements Closeable { * @param group 分组,当分组对应的选项不存在时会读取根选项,如果也不存在使用默认值 * @return Builder */ - private MongoClientSettings.Builder buildMongoClientOptions(MongoClientSettings.Builder builder, String group) { + private Builder buildMongoClientOptions(Builder builder, String group) { if (setting == null) { return builder; } @@ -353,9 +348,8 @@ public class MongoDS implements Closeable { if (StrUtil.isBlank(group) == false && connectionsPerHost == null) { connectionsPerHost = setting.getInt("connectionsPerHost"); } - ConnectionPoolSettings.Builder connectionPoolSettingsBuilder = ConnectionPoolSettings.builder(); if (connectionsPerHost != null) { - connectionPoolSettingsBuilder.maxConnecting(connectionsPerHost); + builder.connectionsPerHost(connectionsPerHost); log.debug("MongoDB connectionsPerHost: {}", connectionsPerHost); } @@ -365,10 +359,9 @@ public class MongoDS implements Closeable { setting.getInt("connectTimeout"); } if (connectTimeout != null) { - connectionPoolSettingsBuilder.maxWaitTime(connectTimeout, TimeUnit.MILLISECONDS); + builder.connectTimeout(connectTimeout); log.debug("MongoDB connectTimeout: {}", connectTimeout); } - builder.applyToConnectionPoolSettings(b -> b.applySettings(connectionPoolSettingsBuilder.build())); // 套接字超时时间;该值会被传递给Socket.setSoTimeout(int)。默以为0(无穷) --int Integer socketTimeout = setting.getInt(group + "socketTimeout"); @@ -376,8 +369,7 @@ public class MongoDS implements Closeable { setting.getInt("socketTimeout"); } if (socketTimeout != null) { - SocketSettings socketSettings = SocketSettings.builder().connectTimeout(socketTimeout, TimeUnit.MILLISECONDS).build(); - builder.applyToSocketSettings(b -> b.applySettings(socketSettings)); + builder.socketTimeout(socketTimeout); log.debug("MongoDB socketTimeout: {}", socketTimeout); } diff --git a/hutool-db/src/main/java/cn/hutool/db/nosql/mongo/MongoDS4.java b/hutool-db/src/main/java/cn/hutool/db/nosql/mongo/MongoDS4.java new file mode 100644 index 000000000..0b2ff0f62 --- /dev/null +++ b/hutool-db/src/main/java/cn/hutool/db/nosql/mongo/MongoDS4.java @@ -0,0 +1,404 @@ +package cn.hutool.db.nosql.mongo; + +import cn.hutool.core.exceptions.NotInitedException; +import cn.hutool.core.net.NetUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.db.DbRuntimeException; +import cn.hutool.log.Log; +import cn.hutool.setting.Setting; +import com.mongodb.MongoClientSettings; +import com.mongodb.MongoCredential; +import com.mongodb.MongoDriverInformation; +import com.mongodb.ServerAddress; +import com.mongodb.client.MongoClient; +import com.mongodb.client.MongoCollection; +import com.mongodb.client.MongoDatabase; +import com.mongodb.client.internal.MongoClientImpl; +import com.mongodb.connection.ConnectionPoolSettings; +import com.mongodb.connection.SocketSettings; +import org.bson.Document; + +import java.io.Closeable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * MongoDB4工具类 + * + * @author VampireAchao + */ +public class MongoDS4 implements Closeable { + + private final static Log log = Log.get(); + + /** + * 默认配置文件 + */ + public final static String MONGO_CONFIG_PATH = "config/mongo.setting"; + + // MongoDB配置文件 + private Setting setting; + // MongoDB实例连接列表 + private String[] groups; + // MongoDB单点连接信息 + private ServerAddress serverAddress; + // MongoDB客户端对象 + private MongoClient mongo; + + // --------------------------------------------------------------------------- Constructor start + + /** + * 构造MongoDB数据源
+ * 调用者必须持有MongoDS实例,否则会被垃圾回收导致写入失败! + * + * @param host 主机(域名或者IP) + * @param port 端口 + */ + public MongoDS4(String host, int port) { + this.serverAddress = createServerAddress(host, port); + initSingle(); + } + + /** + * 构造MongoDB数据源
+ * 调用者必须持有MongoDS实例,否则会被垃圾回收导致写入失败! + * + * @param mongoSetting MongoDB的配置文件,如果是null则读取默认配置文件或者使用MongoDB默认客户端配置 + * @param host 主机(域名或者IP) + * @param port 端口 + */ + public MongoDS4(Setting mongoSetting, String host, int port) { + this.setting = mongoSetting; + this.serverAddress = createServerAddress(host, port); + initSingle(); + } + + /** + * 构造MongoDB数据源
+ * 当提供多个数据源时,这些数据源将为一个副本集或者多个mongos
+ * 调用者必须持有MongoDS实例,否则会被垃圾回收导致写入失败! 官方文档: http://docs.mongodb.org/manual/administration/replica-sets/ + * + * @param groups 分组列表,当为null或空时使用无分组配置,一个分组使用单一模式,否则使用副本集模式 + */ + public MongoDS4(String... groups) { + this.groups = groups; + init(); + } + + /** + * 构造MongoDB数据源
+ * 当提供多个数据源时,这些数据源将为一个副本集或者mongos
+ * 调用者必须持有MongoDS实例,否则会被垃圾回收导致写入失败!
+ * 官方文档: http://docs.mongodb.org/manual/administration/replica-sets/ + * + * @param mongoSetting MongoDB的配置文件,必须有 + * @param groups 分组列表,当为null或空时使用无分组配置,一个分组使用单一模式,否则使用副本集模式 + */ + public MongoDS4(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(); + } + } + + /** + * 初始化
+ * 设定文件中的host和端口有三种形式: + * + *
+	 * host = host:port
+	 * 
+ * + *
+	 * host = host
+	 * port = port
+	 * 
+ * + *
+	 * 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 (null == this.serverAddress) { + //存在唯一分组 + if (groups != null && groups.length == 1) { + group = groups[0]; + } + serverAddress = createServerAddress(group); + } + + final MongoCredential credentail = createCredentail(group); + try { + MongoClientSettings.Builder clusterSettingsBuilder = MongoClientSettings.builder().applyToClusterSettings(b -> b.hosts(Collections.singletonList(serverAddress))); + if (null != credentail) { + clusterSettingsBuilder.credential(credentail); + } + mongo = new MongoClientImpl(clusterSettingsBuilder.build(), MongoDriverInformation.builder().build()); + } 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); + } + + /** + * 初始化集群
+ * 集群的其它客户端设定参数使用全局设定
+ * 集群中每一个实例成员用一个group表示,例如: + * + *
+	 * user = test1
+	 * pass = 123456
+	 * database = test
+	 * [db0]
+	 * host = 192.168.1.1:27117
+	 * [db1]
+	 * host = 192.168.1.1:27118
+	 * [db2]
+	 * host = 192.168.1.1: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); + } + + final List addrList = new ArrayList<>(); + for (String group : groups) { + addrList.add(createServerAddress(group)); + } + + final MongoCredential credentail = createCredentail(StrUtil.EMPTY); + try { + MongoClientSettings.Builder clusterSettingsBuilder = MongoClientSettings.builder().applyToClusterSettings(b -> b.hosts(addrList)); + if (null != credentail) { + clusterSettingsBuilder.credential(credentail); + } + mongo = new MongoClientImpl(clusterSettingsBuilder.build(), MongoDriverInformation.builder().build()); + } catch (Exception e) { + log.error(e, "Init MongoDB connection error!"); + return; + } + + log.info("Init MongoDB cloud Set pool with connection to {}", addrList); + } + + /** + * 设定MongoDB配置文件 + * + * @param setting 配置文件 + */ + 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 getCollection(String dbName, String collectionName) { + return getDb(dbName).getCollection(collectionName); + } + + @Override + public void close() { + mongo.close(); + } + + // --------------------------------------------------------------------------- Private method start + + /** + * 创建ServerAddress对象,会读取配置文件中的相关信息 + * + * @param group 分组,如果为{@code null}或者""默认为无分组 + * @return ServerAddress + */ + private ServerAddress createServerAddress(String group) { + final Setting setting = checkSetting(); + + if (group == null) { + group = StrUtil.EMPTY; + } + + final 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); + } + + /** + * 创建{@link MongoCredential},用于服务端验证
+ * 此方法会首先读取指定分组下的属性,用户没有定义则读取空分组下的属性 + * + * @param group 分组 + * @return {@link MongoCredential},如果用户未指定用户名密码返回null + * @since 4.1.20 + */ + private MongoCredential createCredentail(String group) { + final Setting setting = this.setting; + if (null == setting) { + return null; + } + final String user = setting.getStr("user", group, setting.getStr("user")); + final String pass = setting.getStr("pass", group, setting.getStr("pass")); + final String database = setting.getStr("database", group, setting.getStr("database")); + return createCredentail(user, database, pass); + } + + /** + * 创建{@link MongoCredential},用于服务端验证 + * + * @param userName 用户名 + * @param database 数据库名 + * @param password 密码 + * @return {@link MongoCredential} + * @since 4.1.20 + */ + private MongoCredential createCredentail(String userName, String database, String password) { + if (StrUtil.hasEmpty(userName, database, database)) { + return null; + } + return MongoCredential.createCredential(userName, database, password.toCharArray()); + } + + /** + * 构件MongoDB连接选项
+ * + * @param group 分组,当分组对应的选项不存在时会读取根选项,如果也不存在使用默认值 + * @return MongoClientOptions + */ + private MongoClientSettings buildMongoClientOptions(String group) { + return buildMongoClientOptions(MongoClientSettings.builder(), group).build(); + } + + /** + * 构件MongoDB连接选项
+ * + * @param group 分组,当分组对应的选项不存在时会读取根选项,如果也不存在使用默认值 + * @return Builder + */ + private MongoClientSettings.Builder buildMongoClientOptions(MongoClientSettings.Builder builder, String group) { + if (setting == null) { + return builder; + } + + if (StrUtil.isEmpty(group)) { + 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"); + } + ConnectionPoolSettings.Builder connectionPoolSettingsBuilder = ConnectionPoolSettings.builder(); + if (connectionsPerHost != null) { + connectionPoolSettingsBuilder.maxSize(connectionsPerHost); + log.debug("MongoDB connectionsPerHost: {}", connectionsPerHost); + } + + // 被阻塞线程从连接池获取连接的最长等待时间(ms) --int + Integer connectTimeout = setting.getInt(group + "connectTimeout"); + if (StrUtil.isBlank(group) == false && connectTimeout == null) { + setting.getInt("connectTimeout"); + } + if (connectTimeout != null) { + connectionPoolSettingsBuilder.maxWaitTime(connectTimeout, TimeUnit.MILLISECONDS); + log.debug("MongoDB connectTimeout: {}", connectTimeout); + } + builder.applyToConnectionPoolSettings(b -> b.applySettings(connectionPoolSettingsBuilder.build())); + + // 套接字超时时间;该值会被传递给Socket.setSoTimeout(int)。默以为0(无穷) --int + Integer socketTimeout = setting.getInt(group + "socketTimeout"); + if (StrUtil.isBlank(group) == false && socketTimeout == null) { + setting.getInt("socketTimeout"); + } + if (socketTimeout != null) { + SocketSettings socketSettings = SocketSettings.builder().connectTimeout(socketTimeout, TimeUnit.MILLISECONDS).build(); + builder.applyToSocketSettings(b -> b.applySettings(socketSettings)); + log.debug("MongoDB socketTimeout: {}", socketTimeout); + } + + return builder; + } + + /** + * 检查Setting配置文件 + * + * @return Setting配置文件 + */ + private Setting checkSetting() { + if (null == this.setting) { + throw new DbRuntimeException("Please indicate setting file or create default [{}]", MONGO_CONFIG_PATH); + } + return this.setting; + } + // --------------------------------------------------------------------------- Private method end + +} diff --git a/hutool-db/src/main/java/cn/hutool/db/nosql/mongo/MongoFactory4.java b/hutool-db/src/main/java/cn/hutool/db/nosql/mongo/MongoFactory4.java new file mode 100644 index 000000000..a099118a4 --- /dev/null +++ b/hutool-db/src/main/java/cn/hutool/db/nosql/mongo/MongoFactory4.java @@ -0,0 +1,120 @@ +package cn.hutool.db.nosql.mongo; + +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.RuntimeUtil; +import cn.hutool.setting.Setting; + +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * MongoDB4工厂类,用于创建 + * @author VampireAchao + */ +public class MongoFactory4 { + + /** 各分组做组合key的时候分隔符 */ + private final static String GROUP_SEPRATER = ","; + + /** 数据源池 */ + private static final Map DS_MAP = new ConcurrentHashMap<>(); + + // JVM关闭前关闭MongoDB连接 + static { + RuntimeUtil.addShutdownHook(MongoFactory4::closeAll); + } + + // ------------------------------------------------------------------------ Get DS start + /** + * 获取MongoDB数据源
+ * + * @param host 主机 + * @param port 端口 + * @return MongoDB连接 + */ + public static MongoDS4 getDS(String host, int port) { + final String key = host + ":" + port; + MongoDS4 ds = DS_MAP.get(key); + if (null == ds) { + // 没有在池中加入之 + ds = new MongoDS4(host, port); + DS_MAP.put(key, ds); + } + + return ds; + } + + /** + * 获取MongoDB数据源
+ * 多个分组名对应的连接组成集群 + * + * @param groups 分组列表 + * @return MongoDB连接 + */ + public static MongoDS4 getDS(String... groups) { + final String key = ArrayUtil.join(groups, GROUP_SEPRATER); + MongoDS4 ds = DS_MAP.get(key); + if (null == ds) { + // 没有在池中加入之 + ds = new MongoDS4(groups); + DS_MAP.put(key, ds); + } + + return ds; + } + + /** + * 获取MongoDB数据源
+ * + * @param groups 分组列表 + * @return MongoDB连接 + */ + public static MongoDS4 getDS(Collection groups) { + return getDS(groups.toArray(new String[0])); + } + + /** + * 获取MongoDB数据源
+ * + * @param setting 设定文件 + * @param groups 分组列表 + * @return MongoDB连接 + */ + public static MongoDS4 getDS(Setting setting, String... groups) { + final String key = setting.getSettingPath() + GROUP_SEPRATER + ArrayUtil.join(groups, GROUP_SEPRATER); + MongoDS4 ds = DS_MAP.get(key); + if (null == ds) { + // 没有在池中加入之 + ds = new MongoDS4(setting, groups); + DS_MAP.put(key, ds); + } + + return ds; + } + + /** + * 获取MongoDB数据源
+ * + * @param setting 配置文件 + * @param groups 分组列表 + * @return MongoDB连接 + */ + public static MongoDS4 getDS(Setting setting, Collection groups) { + return getDS(setting, groups.toArray(new String[0])); + } + // ------------------------------------------------------------------------ Get DS ends + + /** + * 关闭全部连接 + */ + public static void closeAll() { + if(MapUtil.isNotEmpty(DS_MAP)){ + for(MongoDS4 ds : DS_MAP.values()) { + ds.close(); + } + DS_MAP.clear(); + } + } +} diff --git a/hutool-db/src/test/java/cn/hutool/db/nosql/MongoDBTest.java b/hutool-db/src/test/java/cn/hutool/db/nosql/MongoDBTest.java index 278431b09..67ffadd9f 100644 --- a/hutool-db/src/test/java/cn/hutool/db/nosql/MongoDBTest.java +++ b/hutool-db/src/test/java/cn/hutool/db/nosql/MongoDBTest.java @@ -1,6 +1,6 @@ package cn.hutool.db.nosql; -import cn.hutool.db.nosql.mongo.MongoFactory; +import cn.hutool.db.nosql.mongo.MongoFactory4; import com.mongodb.client.MongoDatabase; import org.junit.Assert; import org.junit.Ignore; @@ -14,7 +14,7 @@ public class MongoDBTest { @Test @Ignore public void redisDSTest() { - MongoDatabase db = MongoFactory.getDS("master").getDb("test"); + MongoDatabase db = MongoFactory4.getDS("master").getDb("test"); Assert.assertEquals("test", db.getName()); } }