Spring Data MongoDB 教程

MongoDatabaseFactory 接口

前面章节分别介绍了怎样基于 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;
    //...
}

简单使用 MongoDatabaseFactory

下面将介绍如何使用基于 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注册 MongoDatabaseFactory

你可以通过基于 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);
        });
    }

}

基于 XML 注册 MongoDatabaseFactory

上面介绍了基于 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节。

说说我的看法
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
公众号