前面章节分别介绍了怎样基于 Java 和 XML 去创建 Mongo 的实例。下面将介绍怎样获取 MongoDB 数据库对象 MongoDatabase。
虽然 com.mongodb.client.MongoClient 是 MongoDB 驱动程序 API 的入口,但连接到特定的 MongoDB 数据库实例需要额外的信息,例如数据库名称以及可选的用户名和密码。
有了这些信息,你就可以获得 com.mongodb.client.MongoDatabase 对象并访问特定 MongoDB 数据库实例的所有功能。
Spring 提供了 org.springframework.data.mongodb.core.MongoDatabaseFactory 接口,用于创建与 MongoDB 数据库的连接。它的定义如下:
public interface MongoDatabaseFactory {
    // 从底层工厂获取一个 MongoDatabase
    MongoDatabase getDatabase() throws DataAccessException;
    // 使用给定的 dbName 从底层工厂获取一个 MongoDatabase
    MongoDatabase getDatabase(String dbName) throws DataAccessException;
    //...
}下面将介绍如何使用基于 Java 或基于 XML 的方式来配置容器 MongoDatabaseFactory 接口的实例。反过来,你还可以使用 MongoDatabaseFactory 实例来配置 MongoTemplate。
与其使用 IoC 容器来创建 MongoTemplate 的实例,你可以在标准 Java 代码中使用它们,如下所示:
package com.hxstrive.springdata.mongodb.demo;
import com.mongodb.client.MongoClients;
import lombok.Builder;
import lombok.Data;
import lombok.ToString;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.SimpleMongoClientDatabaseFactory;
import org.springframework.data.mongodb.core.query.Query;
import static org.springframework.data.mongodb.core.query.Criteria.where;
/**
 * 在标准 Java 中使用 MongoTemplate
 * @author hxstrive.com 2022/12/22
 */
public class MongoApp {
    public static void main(String[] args) {
        MongoOperations mongoOps = new MongoTemplate(
                new SimpleMongoClientDatabaseFactory(MongoClients.create(), "test"));
        // 插入一条记录
        mongoOps.insert(User.builder().id(100).name("Tom").build());
        // 查询 name 为 Tom 的用户信息
        User user = mongoOps.findOne(new Query(where("name").is("Tom")), User.class);
        System.out.println(user);
        // 删除集合
        mongoOps.dropCollection("user");
    }
    @ToString
    @Data
    @Builder
    static class User {
        private int id;
        private String name;
    }
}在上面代码中,我们使用了 SimpleMongoClientDbFactory 工厂类,它是 MongoDatabaseFactory 的实现,如下:
// 抽象类,MongoDatabaseFactory 的抽象实现
public abstract class MongoDatabaseFactorySupport<C> implements MongoDatabaseFactory {}
// SimpleMongoClientDatabaseFactory 的实现
public class SimpleMongoClientDatabaseFactory extends MongoDatabaseFactorySupport<MongoClient>
        implements DisposableBean {}注意:选择 com.mongodb.client.MongoClient 作为入口点时,请使用 SimpleMongoClientDbFactory 工厂类。
你可以通过基于 Java 的 @Configuration 配置类在容器中注册一个 MongoDatabaseFactory 实例,代码如下:
package com.hxstrive.springdata.mongodb.config;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.MongoDatabaseFactory;
import org.springframework.data.mongodb.core.MongoClientFactoryBean;
import org.springframework.data.mongodb.core.SimpleMongoClientDatabaseFactory;
/**
 * 配置类
 * @author hxstrive.com 2022/12/21
 */
@Configuration
public class BaseJavaConfig {
    /**
     * 注册一个 MongoDatabaseFactory
     * @return
     */
    @Bean
    public MongoDatabaseFactory mongoDatabaseFactory() {
        return new SimpleMongoClientDatabaseFactory(MongoClients.create(), "test");
    }
}创建一个客户端,验证我们注册的 MongoDatabaseFactory 是否可用,代码如下:
package com.hxstrive.springdata.mongodb;
import lombok.Builder;
import lombok.Data;
import lombok.ToString;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.mongodb.MongoDatabaseFactory;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Query;
import static org.springframework.data.mongodb.core.query.Criteria.where;
/**
 * 使用注册的 MongoDatabaseFactory
 * @author hxstrive.com 2022/12/22
 */
@SpringBootTest
public class MongoDatabaseFactoryTest {
    @Autowired
    private MongoDatabaseFactory mongoDatabaseFactory;
    @Test
    void contextLoads() {
        MongoOperations mongoOps = new MongoTemplate(mongoDatabaseFactory);
        // 插入一条记录
        mongoOps.insert(User.builder().id(100).name("Helen").build());
        // 查询 name 为 Tom 的用户信息
        User user = mongoOps.findOne(new Query(where("name").is("Helen")), User.class);
        System.out.println(user);
        // 删除集合
        mongoOps.dropCollection("user");
    }
    @ToString
    @Data
    @Builder
    static class User {
        private int id;
        private String name;
    }
}注意:MongoDB 服务器第三代改变了连接到数据库时的认证模式。因此,一些可用于身份验证的配置选项已不再有效。你应该使用 MongoClient 特有的选项,通过 MongoCredential 设置证书,以提供数据库认证数据。如下例所示:
package com.hxstrive.springdata.mongodb.config;
import com.mongodb.ConnectionString;
import com.mongodb.MongoClientSettings;
import com.mongodb.MongoCredential;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.config.AbstractMongoClientConfiguration;
/**
 * MongoDB 授权信息配置
 * @author hxstrive.com 2022/12/22
 */
@Configuration
public class MongoAuthConfig extends AbstractMongoClientConfiguration {
    private static final Logger LOG = LoggerFactory.getLogger(MongoAuthConfig.class);
    @Override
    public String getDatabaseName() {
        LOG.info("getDatabaseName()");
        return "test";
    }
    @Override
    protected void configureClientSettings(MongoClientSettings.Builder builder) {
        LOG.info("configureClientSettings(MongoClientSettings.Builder builder)");
        // 设置用户名、数据库、密码信息
        builder.credential(MongoCredential.createCredential(
                "hxstrive", "test", "aaaaaa".toCharArray()))
                .applyConnectionString(new ConnectionString("mongodb://127.0.0.1:27017"));
    }
}编写客户端,测试上面的授权信息是否可用。代码如下:
package com.hxstrive.springdata.mongodb;
import lombok.Builder;
import lombok.Data;
import lombok.ToString;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.mongodb.MongoDatabaseFactory;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Query;
import static org.springframework.data.mongodb.core.query.Criteria.where;
/**
 * 测试通过继承 AbstractMongoClientConfiguration 为 Mongo 提供授权信息
 * @author hxstrive.com 2022/12/22
 */
@SpringBootTest
public class MongoAuthConfigTest {
    @Autowired
    private MongoDatabaseFactory mongoDbFactory;
    @Test
    void contextLoads() {
        MongoOperations mongoOps = new MongoTemplate(mongoDbFactory);
        // 插入一条记录
        mongoOps.insert(User.builder().id(100).name("Helen").build());
        // 查询 name 为 Tom 的用户信息
        User user = mongoOps.findOne(new Query(where("name").is("Helen")), User.class);
        System.out.println(user);
        // 删除集合
        mongoOps.dropCollection("user");
    }
    @ToString
    @Data
    @Builder
    static class User {
        private int id;
        private String name;
    }
}注意,在执行客户端代码之前,你需要开启 MongoDB 的权限校验和创建用户。
重点:上面的配置信息是怎样帮我们设置到 Mongo 连接中的呢?部分源码片段如下:
// MongoAuthConfigTest -> AbstractMongoClientConfiguration -> MongoConfigurationSupport
@Configuration(proxyBeanMethods = false)
public abstract class AbstractMongoClientConfiguration extends MongoConfigurationSupport {
    // 创建 Mongo
    public MongoClient mongoClient() {
        // mongoClientSettings() 这个方法在父类中是一个抽象方法,由我们去实现
        return createMongoClient(mongoClientSettings());
    }
    // 启示可以直接使用 MongoTemplate,他帮我们创建好了
    @Bean
    public MongoTemplate mongoTemplate(MongoDatabaseFactory databaseFactory, MappingMongoConverter converter) {
        return new MongoTemplate(databaseFactory, converter);
    }
    // 我们需要使用到的工厂
    @Bean
    public MongoDatabaseFactory mongoDbFactory() {
        // getDatabaseName() 方法在父类中是一个抽象方法,由我们去实现
        return new SimpleMongoClientDatabaseFactory(mongoClient(), getDatabaseName());
    }
    //...
    protected MongoClient createMongoClient(MongoClientSettings settings) {
        return MongoClients.create(settings, SpringDataMongoDB.driverInformation());
    }
}
public abstract class MongoConfigurationSupport {
    // 我们自己实现
    protected abstract String getDatabaseName();
    //...
    
    // 被子类 AbstractMongoClientConfiguration 的 mongoClient() 方法调用
    protected MongoClientSettings mongoClientSettings() {
        MongoClientSettings.Builder builder = MongoClientSettings.builder();
        builder.uuidRepresentation(UuidRepresentation.JAVA_LEGACY);
        configureClientSettings(builder);
        return builder.build();
    }
    
    // 我们自己实现
    protected void configureClientSettings(MongoClientSettings.Builder builder) {
        // customization hook
    }
}如果我们的 MongoDB 配置信息在外部属性文件中,可以通过 @PropertySource 注解导入,并且还能通过 builder.applyToConnectionPoolSettings() 去配置 Mongo 连接池等信息,如下:
@Configuration
@PropertySource("classpath:/com/myapp/mongodb/config/mongo.properties")
public class ApplicationContextEventTestsAppConfig extends AbstractMongoClientConfiguration {
    @Autowired
    private Environment env;
    @Override
    public String getDatabaseName() {
        return "database";
    }
    @Override
    protected void configureClientSettings(Builder builder) {
        builder.applyToClusterSettings(settings -> {
            settings.hosts(
                singletonList(
                    new ServerAddress(
                        env.getProperty("mongo.host"),
                        env.getProperty("mongo.port", Integer.class)
                    )
                )
            );
        });
        builder.applyToConnectionPoolSettings(settings -> {
            settings.maxConnectionLifeTime(
                env.getProperty("mongo.pool-max-life-time", Integer.class),
                TimeUnit.MILLISECONDS
            )
            .minSize(env.getProperty("mongo.pool-min-size", Integer.class))
            .maxSize(env.getProperty("mongo.pool-max-size", Integer.class))
            .maintenanceFrequency(10, TimeUnit.MILLISECONDS)
            .maintenanceInitialDelay(11, TimeUnit.MILLISECONDS)
            .maxConnectionIdleTime(30, TimeUnit.SECONDS)
            .maxWaitTime(15, TimeUnit.MILLISECONDS);
        });
    }
}上面介绍了基于 Java 来注册 MongoDatabaseFactory 工厂,当然,我们也可以通过 XML 文件进行注册,如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:mongo="http://www.springframework.org/schema/data/mongo"
        xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/data/mongo
            https://www.springframework.org/schema/data/mongo/spring-mongo.xsd">
    <!-- 配置 MongoDatabaseFactory -->
    <!-- ${mongo.username}:${mongo.password}@${mongo.dbname} -->
    <mongo:mongo-client id="mongoClient" host="127.0.0.1" port="27017" credential="hxstrive:aaaaaa@test">
        <!-- 在 com.mongodb.client.MongoClient 实例上配置额外的选项 -->
        <mongo:client-settings
                connection-pool-max-connection-life-time="120"
                connection-pool-min-size="5"
                connection-pool-max-size="20"
                connection-pool-maintenance-frequency="10"
                connection-pool-maintenance-initial-delay="11"
                connection-pool-max-connection-idle-time="30"
                connection-pool-max-wait-time="15" />
    </mongo:mongo-client>
    <mongo:db-factory id="mongoDatabaseFactory" dbname="test" mongo-client-ref="mongoClient"/>
    <!-- 配置自己的 MongoTempalte -->
    <bean id="anotherMongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
        <constructor-arg name="mongoDbFactory" ref="mongoDatabaseFactory"/>
    </bean>
</beans>此时,我们就可以这样去使用它们,如下:
@Autowired private MongoDatabaseFactory mongoDatabaseFactory; @Autowired private MongoTemplate anotherMongoTemplate; @Autowired private MongoClient mongoClient;
注意:在基于 XML 的配置中,使用的用户名和密码凭证,如果包含保留字符(如:, %, @, 或空格),必须进行 URL 编码。例如:
原始密码:
m0ng0@dmin:mo_res:bw6},Qsdxx@admin@database
编码后的密码:
m0ng0%40dmin:mo_res%3Abw6%7D%2CQsdxx%40admin@database
注意:用户名中的 @ 被编码成 %40,密码中的 “:” 被编码成 “%3A”,“}” 被编码成 “%2C”,“,” 被编码成 “%7D”,更多关于编码细节,你可以参见 RFC 3986 文档的 2.2节。
