在Spring Boot中MongoDB配置了默认连接池,但没有直接提供连接池参数的配置,下面开始探究MongoDB连接池参数的配置(以Spring Boot2.2.8为例)

一、整合MongoDB

1、pom依赖:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>

完整pom文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>


    <groupId>com.phy</groupId>
    <artifactId>data-capture</artifactId>
    <version>1.0</version>
    <name>data-capture</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
        <spring-boot.version>2.2.8.RELEASE</spring-boot.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-mongodb</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

2、yml配置

server:
  port: 8040

spring:
  #mongodb配置
  data:
    mongodb:
      uri: mongodb://localhost:27017/datacapture

二、MongoDB连接池参数配置探究

1、MongoAutoConfiguration源码:

查看MongoAutoConfiguration自动配置源码:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.boot.autoconfigure.mongo;

import com.mongodb.MongoClient;
import com.mongodb.MongoClientOptions;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;

@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnClass({MongoClient.class})
@EnableConfigurationProperties({MongoProperties.class})
@ConditionalOnMissingBean(
    type = {"org.springframework.data.mongodb.MongoDbFactory"}
)
public class MongoAutoConfiguration {
    public MongoAutoConfiguration() {
    }

    @Bean
    @ConditionalOnMissingBean(
        type = {"com.mongodb.MongoClient", "com.mongodb.client.MongoClient"}
    )
    public MongoClient mongo(MongoProperties properties, ObjectProvider<MongoClientOptions> options, Environment environment) {
        return (new MongoClientFactory(properties, environment)).createMongoClient((MongoClientOptions)options.getIfAvailable());
    }
}

观察上面源码,再次简单查看MongoProperties、MongoClientOptions源码,很容易发现连接池的配置参数就在MongoClientOptions中:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.mongodb;

import com.mongodb.annotations.Beta;
import com.mongodb.annotations.Immutable;
import com.mongodb.annotations.NotThreadSafe;
import com.mongodb.assertions.Assertions;
import com.mongodb.connection.ConnectionPoolSettings;
import com.mongodb.connection.ServerSettings;
import com.mongodb.connection.SocketSettings;
import com.mongodb.connection.SslSettings;
import com.mongodb.event.ClusterListener;
import com.mongodb.event.CommandListener;
import com.mongodb.event.ConnectionPoolListener;
import com.mongodb.event.ServerListener;
import com.mongodb.event.ServerMonitorListener;
import com.mongodb.lang.Nullable;
import com.mongodb.selector.ServerSelector;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.net.SocketFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import org.bson.codecs.configuration.CodecRegistry;

@Immutable
public class MongoClientOptions {
    private static final SocketFactory DEFAULT_SSL_SOCKET_FACTORY = SSLSocketFactory.getDefault();
    private static final SocketFactory DEFAULT_SOCKET_FACTORY = SocketFactory.getDefault();
    private final String description;
    private final String applicationName;
    private final List<MongoCompressor> compressorList;
    private final ReadPreference readPreference;
    private final WriteConcern writeConcern;
    private final boolean retryWrites;
    private final boolean retryReads;
    private final ReadConcern readConcern;
    private final CodecRegistry codecRegistry;
    private final ServerSelector serverSelector;
    private final int minConnectionsPerHost;
    private final int maxConnectionsPerHost;
    private final int threadsAllowedToBlockForConnectionMultiplier;
    private final int serverSelectionTimeout;
    private final int maxWaitTime;
    private final int maxConnectionIdleTime;
    private final int maxConnectionLifeTime;
    private final int connectTimeout;
    private final int socketTimeout;
    private final boolean socketKeepAlive;
    private final boolean sslEnabled;
    private final boolean sslInvalidHostNameAllowed;
    private final SSLContext sslContext;
    private final boolean alwaysUseMBeans;
    private final int heartbeatFrequency;
    private final int minHeartbeatFrequency;
    private final int heartbeatConnectTimeout;
    private final int heartbeatSocketTimeout;
    private final int localThreshold;
    private final String requiredReplicaSetName;
    private final DBDecoderFactory dbDecoderFactory;
    private final DBEncoderFactory dbEncoderFactory;
    private final SocketFactory socketFactory;
    private final boolean cursorFinalizerEnabled;
    private final ConnectionPoolSettings connectionPoolSettings;
    private final SocketSettings socketSettings;
    private final ServerSettings serverSettings;
    private final SocketSettings heartbeatSocketSettings;
    private final SslSettings sslSettings;
    private final List<ClusterListener> clusterListeners;
    private final List<CommandListener> commandListeners;
    private final AutoEncryptionSettings autoEncryptionSettings;
    ......
}

但是MongoClientOptions并没有提供这些属性的setter方法,而是通过静态内部类MongoClientOptions.Builder来构造这些参数的。而且MongoClientOptions.Builder中的属性设置方法是建造者模式下的链式结构,方法名不符合JavaBean属性setter命名规范,因此这里要自己再新建一个类。

三、MongoDB连接池参数配置具体步骤

1、MongoClientProperties

新建属性类MongoClientProperties.java
复制静态内部类MongoClientOptions.Builder代码属性,稍加修改后如下:

package com.phy.data.capture.config;

import com.mongodb.*;
import com.mongodb.assertions.Assertions;
import lombok.Getter;
import lombok.Setter;
import org.bson.codecs.configuration.CodecRegistry;

/**
 * MongoClientProperties
 *
 * @desc: TODO 类的设计目的、功能及注意事项
 * @version:
 * @createTime: 2020/11/22 14:12
 * @author: liuhr
 */
@Getter
@Setter
public class MongoClientProperties {

    private String description;
    private String applicationName;
    private WriteConcern writeConcern;
    private boolean retryWrites;
    private boolean retryReads;
    private ReadConcern readConcern;
    private CodecRegistry codecRegistry;
    private int minConnectionsPerHost;
    private int maxConnectionsPerHost;
    private int threadsAllowedToBlockForConnectionMultiplier;
    private int serverSelectionTimeout;
    private int maxWaitTime;
    private int maxConnectionIdleTime;
    private int maxConnectionLifeTime;
    private int connectTimeout;
    private int socketTimeout;
    private boolean socketKeepAlive;
    private boolean sslEnabled;
    private boolean sslInvalidHostNameAllowed;
    private boolean alwaysUseMBeans;
    private int heartbeatFrequency;
    private int minHeartbeatFrequency;
    private int heartbeatConnectTimeout;
    private int heartbeatSocketTimeout;
    private int localThreshold;
    private String requiredReplicaSetName;
    private DBDecoderFactory dbDecoderFactory;
    private DBEncoderFactory dbEncoderFactory;
    private boolean cursorFinalizerEnabled;

    public MongoClientProperties() {
        this.writeConcern = WriteConcern.ACKNOWLEDGED;
        this.retryWrites = true;
        this.retryReads = true;
        this.readConcern = ReadConcern.DEFAULT;
        this.codecRegistry = MongoClient.getDefaultCodecRegistry();
        this.maxConnectionsPerHost = 100;
        this.threadsAllowedToBlockForConnectionMultiplier = 5;
        this.serverSelectionTimeout = 30000;
        this.maxWaitTime = 120000;
        this.connectTimeout = 10000;
        this.socketTimeout = 0;
        this.socketKeepAlive = true;
        this.sslEnabled = false;
        this.sslInvalidHostNameAllowed = false;
        this.alwaysUseMBeans = false;
        this.heartbeatFrequency = 10000;
        this.minHeartbeatFrequency = 500;
        this.heartbeatConnectTimeout = 20000;
        this.heartbeatSocketTimeout = 20000;
        this.localThreshold = 15;
        this.dbDecoderFactory = DefaultDBDecoder.FACTORY;
        this.dbEncoderFactory = DefaultDBEncoder.FACTORY;
        this.cursorFinalizerEnabled = true;
        this.setHeartbeatFrequency(Integer.parseInt(System.getProperty("com.mongodb.updaterIntervalMS", "10000")));
        this.setMinHeartbeatFrequency(Integer.parseInt(System.getProperty("com.mongodb.updaterIntervalNoMasterMS", "500")));
        this.setHeartbeatConnectTimeout(Integer.parseInt(System.getProperty("com.mongodb.updaterConnectTimeoutMS", "20000")));
        this.setHeartbeatSocketTimeout(Integer.parseInt(System.getProperty("com.mongodb.updaterSocketTimeoutMS", "20000")));
        this.setLocalThreshold(Integer.parseInt(System.getProperty("com.mongodb.slaveAcceptableLatencyMS", "15")));
    }

    public void setHeartbeatFrequency(int heartbeatFrequency) {
        Assertions.isTrueArgument("heartbeatFrequency must be > 0", heartbeatFrequency > 0);
        this.heartbeatFrequency = heartbeatFrequency;
    }

    public void setMinHeartbeatFrequency(int minHeartbeatFrequency) {
        Assertions.isTrueArgument("minHeartbeatFrequency must be > 0", minHeartbeatFrequency > 0);
        this.minHeartbeatFrequency = minHeartbeatFrequency;
    }

    public void setLocalThreshold(int localThreshold) {
        Assertions.isTrueArgument("localThreshold must be >= 0", localThreshold >= 0);
        this.localThreshold = localThreshold;
    }

    public MongoClientOptions.Builder builder() {
        return MongoClientOptions.builder()
                .description(description)
                .applicationName(applicationName)
                .minConnectionsPerHost(minConnectionsPerHost)
                .connectionsPerHost(maxConnectionsPerHost)
                .threadsAllowedToBlockForConnectionMultiplier(threadsAllowedToBlockForConnectionMultiplier)
                .serverSelectionTimeout(serverSelectionTimeout)
                .maxWaitTime(maxWaitTime)
                .maxConnectionIdleTime(maxConnectionIdleTime)
                .maxConnectionLifeTime(maxConnectionLifeTime)
                .connectTimeout(connectTimeout)
                .socketTimeout(socketTimeout)
                .socketKeepAlive(socketKeepAlive)
                .writeConcern(writeConcern)
                .retryWrites(retryWrites)
                .readConcern(readConcern)
                .codecRegistry(codecRegistry)
                .sslEnabled(sslEnabled)
                .sslInvalidHostNameAllowed(sslInvalidHostNameAllowed)
                .alwaysUseMBeans(alwaysUseMBeans)
                .heartbeatFrequency(heartbeatFrequency)
                .minHeartbeatFrequency(minHeartbeatFrequency)
                .heartbeatConnectTimeout(heartbeatConnectTimeout)
                .heartbeatSocketTimeout(heartbeatSocketTimeout)
                .localThreshold(localThreshold)
                .requiredReplicaSetName(requiredReplicaSetName)
                .dbDecoderFactory(dbDecoderFactory)
                .dbEncoderFactory(dbEncoderFactory)
                .cursorFinalizerEnabled(cursorFinalizerEnabled);
    }
}

2、MongoConfig

新建MongoDB的配置类MongoConfig.java

package com.phy.data.capture.config;

import com.mongodb.MongoClientOptions;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * MongoConfig
 *
 * @desc: TODO 类的设计目的、功能及注意事项
 * @version:
 * @createTime: 2020/11/22 13:59
 * @author: liuhr
 */
@Configuration
public class MongoConfig {

    @Bean
    @ConfigurationProperties(prefix = "spring.data.mongodb.properties")
    public MongoClientProperties mongoClientProperties(){
        return new MongoClientProperties();
    }

    @Bean
    public MongoClientOptions mongoClientOptions(MongoClientProperties mongoClientProperties){
        MongoClientOptions.Builder builder = mongoClientProperties.builder();
        return builder.build();
    }

}

3、yml配置

修改yml配置文件,下面仅以连接池属性maxWaitTime属性为例,将maxWaitTime从默认值120000ms改为180000ms,其他属性类似配置:

server:
  port: 8040

spring:
  #mongodb配置
  data:
    mongodb:
      uri: mongodb://localhost:27017/datacapture
      properties:
        maxWaitTime: 180000

四、验证连接池参数配置

下面对配置参数进行验证:
在MongoAutoConfiguration 35行 打断点后以调试模式启动:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.boot.autoconfigure.mongo;

import com.mongodb.MongoClient;
import com.mongodb.MongoClientOptions;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;

@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnClass({MongoClient.class})
@EnableConfigurationProperties({MongoProperties.class})
@ConditionalOnMissingBean(
    type = {"org.springframework.data.mongodb.MongoDbFactory"}
)
public class MongoAutoConfiguration {
    public MongoAutoConfiguration() {
    }

    @Bean
    @ConditionalOnMissingBean(
        type = {"com.mongodb.MongoClient", "com.mongodb.client.MongoClient"}
    )
    public MongoClient mongo(MongoProperties properties, ObjectProvider<MongoClientOptions> options, Environment environment) {
	//此处打断点查看连接池参数
        return (new MongoClientFactory(properties, environment)).createMongoClient((MongoClientOptions)options.getIfAvailable());
    }
}

进入断点后查看options.getIfAvailable()属性值:
image.png
可以看到maxWaitTime已经设置为180000了,至此,MongoDB连接池参数的配置已经完成

Q.E.D.

知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议