1. 概述

使用Spring Data MongoDB,我们可以创建一个MongoClient来对数据库进行操作。然而,在某些应用中,我们可能需要连接多个数据库。

在这个教程中,我们将创建多个MongoDB连接,并添加一些Spring Boot测试来模拟这种场景。

2. 使用Spring Data MongoDB的多数据库应用程序

在使用MongoDB时,我们创建一个MongoTemplate来访问数据。因此,我们可以为不同的数据库创建多个模板。

但是,我们可能会遇到NoUniqueBeanDefinitionException问题,因为Spring找不到唯一的bean。

考虑到这一点,让我们看看如何构建Spring Boot配置。

2.1. 依赖设置

首先,我们在pom.xml中添加依赖。我们需要一个spring boot starter:*

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <relativePath />
</parent>

接着,我们需要Web启动器和MongoDB数据启动器的依赖:

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

同样,如果使用Gradle,我们在build.gradle中添加:

plugins {
    id 'org.springframework.boot' version '2.6.4'
}
compile 'org.springframework.boot:spring-boot-starter-data-mongodb'
compile 'org.springframework.boot:spring-boot-starter-web'

或者,我们可以使用Spring Initializer

2.2. 模型

首先,我们添加模型。我们将创建两个文档,分别用于两个不同的数据库。

例如,我们将创建一个User文档:

@Document(collection = "user")
public class User {

    @MongoId
    private ObjectId id;

    private String name;

    private String surname;
    private String email;

    private int age;

    // getters and setters
}

然后,我们也添加一个Account文档:

@Document(collection = "account")
public class Account {

    @MongoId
    private ObjectId id;

    private String userEmail;

    private String nickName;

    private String accountDomain;

    private String password;

    // getters and setters
}

2.3. 仓库

然后,我们为每个模型类创建一个仓库,包含一些Spring Data方法。

首先,我们添加一个UserRepository

@Repository
public interface UserRepository extends MongoRepository<User, String> {

    User findByEmail(String email);
}

接下来,我们添加一个AccountRepository

@Repository
public interface AccountRepository extends MongoRepository<Account, String> {

    Account findByAccountDomain(String account);
}

2.4. 连接属性

现在,我们定义用于多个数据库的属性:

mongodb.primary.host=localhost
mongodb.primary.database=db1
mongodb.primary.authenticationDatabase=admin
mongodb.primary.username=user1
mongodb.primary.password=password
mongodb.primary.port=27017

mongodb.secondary.host=localhost
mongodb.secondary.database=db2
mongodb.secondary.authenticationDatabase=admin
mongodb.secondary.username=user2
mongodb.secondary.password=password
mongodb.secondary.port=27017

值得注意的是,我们有一个特定于使用的数据库的属性,例如用于身份验证。

2.5. 主要配置

现在,我们需要我们的配置。我们将为每个数据库创建一个。

让我们看看使用UserRepository的主配置类定义:

@Configuration
@EnableMongoRepositories(basePackageClasses = UserRepository.class, mongoTemplateRef = "primaryMongoTemplate")
@EnableConfigurationProperties
public class PrimaryConfig {
    // beans
}

为了演示,让我们分解所有的bean和注解。

首先,我们将使用*MongoProperties*获取并设置属性。这样,我们可以直接将所有属性映射到一个bean上:

@Bean(name = "primaryProperties")
@ConfigurationProperties(prefix = "mongodb.primary")
@Primary
public MongoProperties primaryProperties() {
    return new MongoProperties();
}

为了允许多个用户访问,我们使用MongoDB的身份验证机制与*MongoCredential。我们通过添加认证数据库(在这种情况下是admin*)构造我们的凭证对象:

@Bean(name = "primaryMongoClient")
public MongoClient mongoClient(@Qualifier("primaryProperties") MongoProperties mongoProperties) {

    MongoCredential credential = MongoCredential
      .createCredential(mongoProperties.getUsername(), mongoProperties.getAuthenticationDatabase(), mongoProperties.getPassword());

    return MongoClients.create(MongoClientSettings.builder()
      .applyToClusterSettings(builder -> builder
        .hosts(singletonList(new ServerAddress(mongoProperties.getHost(), mongoProperties.getPort()))))
      .credential(credential)
      .build());
}

根据最新版本的建议,我们使用SimpleMongoClientDatabaseFactory而不是使用连接字符串创建MongoTemplate

@Primary
@Bean(name = "primaryMongoDBFactory")
public MongoDatabaseFactory mongoDatabaseFactory(
  @Qualifier("primaryMongoClient") MongoClient mongoClient, 
  @Qualifier("primaryProperties") MongoProperties mongoProperties) {
    return new SimpleMongoClientDatabaseFactory(mongoClient, mongoProperties.getDatabase());
}

在这里,我们需要将我们的bean标记为*@Primary*,因为我们将在后面添加更多的数据库配置。否则,我们将陷入之前讨论的独特性约束。

当我们映射多个EntityManagerJPA时,我们也需要在EnableMongoRepositories中引用Mongotemplate

@EnableMongoRepositories(basePackageClasses = UserRepository.class, mongoTemplateRef = "primaryMongoTemplate")

2.6. 二级配置

最后,为了确认,让我们看看第二个数据库配置:

@Configuration
@EnableMongoRepositories(basePackageClasses = AccountRepository.class, mongoTemplateRef = "secondaryMongoTemplate")
@EnableConfigurationProperties
public class SecondaryConfig {

    @Bean(name = "secondaryProperties")
    @ConfigurationProperties(prefix = "mongodb.secondary")
    public MongoProperties secondaryProperties() {
        return new MongoProperties();
    }

    @Bean(name = "secondaryMongoClient")
    public MongoClient mongoClient(@Qualifier("secondaryProperties") MongoProperties mongoProperties) {

        MongoCredential credential = MongoCredential
          .createCredential(mongoProperties.getUsername(), mongoProperties.getAuthenticationDatabase(), mongoProperties.getPassword());

        return MongoClients.create(MongoClientSettings.builder()
          .applyToClusterSettings(builder -> builder
            .hosts(singletonList(new ServerAddress(mongoProperties.getHost(), mongodProperties.getPort()))))
          .credential(credential)
          .build());
    }

    @Bean(name = "secondaryMongoDBFactory")
    public MongoDatabaseFactory mongoDatabaseFactory(
      @Qualifier("secondaryMongoClient") MongoClient mongoClient, 
      @Qualifier("secondaryProperties") MongoProperties mongoProperties) {
        return new SimpleMongoClientDatabaseFactory(mongoClient, mongoProperties.getDatabase());
    }

    @Bean(name = "secondaryMongoTemplate")
    public MongoTemplate mongoTemplate(@Qualifier("secondaryMongoDBFactory") MongoDatabaseFactory mongoDatabaseFactory) {
        return new MongoTemplate(mongoDatabaseFactory);
    }
}

在这种情况下,它将引用AccountRepository或同一包下的所有类。

3. 测试

我们将针对MongoDB实例测试应用程序。我们可以使用Docker中的MongoDB。

3.1. 启动MongoDB容器

让我们使用Docker Compose运行一个MongoDB容器。让我们看看我们的YAML模板:

services:
  mongo:
    hostname: localhost
    container_name: 'mongo'
    image: 'mongo:latest'
    expose:
      - 27017
    ports:
      - 27017:27017
    environment:
      - MONGO_INITDB_DATABASE=admin
      - MONGO_INITDB_ROOT_USERNAME=admin
      - MONGO_INITDB_ROOT_PASSWORD=admin
    volumes:
      - ./mongo-init.js:/docker-entrypoint-initdb.d/mongo-init.js

如果我们想要启用身份验证,我们需要使用root用户初始化。如果需要在数据库中填充更多用户,我们添加了一个bind mount到JavaScript初始化文件:

db.createUser(
    {
        user: "user1",
        pwd: "password",
        roles: [ { role: "readWrite", db: "db1" } ]
    }
)

db.createUser(
    {
        user: "user2",
        pwd: "password",
        roles: [ { role: "readWrite", db: "db2" } ]
    }
)

运行我们的容器:

docker-compose up -d

当容器启动时,它为mongo-init.js文件创建一个卷并将其复制到容器的入口点。

3.2. Spring Boot测试

现在,让我们将所有内容汇总到基本的Spring Boot测试中:

@SpringBootTest(classes = { SpringBootMultipeDbApplication.class })
@TestPropertySource("/multipledb/multidb.properties")
public class MultipleDbUnitTest {

    // set up

    @Test
    void whenFindUserByEmail_thenNameOk() {
        assertEquals("name", userRepository.findByEmail("[email protected]")
          .getName());
    }

    @Test
    void whenFindAccountByDomain_thenNickNameOk() {
        assertEquals("nickname", accountRepository.findByAccountDomain("[email protected]")
          .getNickName());
    }
}

主要目标是建立数据库连接并进行身份验证。如果没有,我们可能会发现数据库未填充或MongoDb实例未启动。

在这种情况下,我们可以查看数据库容器的日志,例如:

docker logs 30725c8635d4

值得注意的是,初始的JavaScript脚本只在容器首次运行时执行。因此,如果我们需要使用不同的脚本,我们可能需要删除卷:

docker-compose down -v

4. 总结

在这篇文章中,我们了解了如何使用Spring Data MongoDB创建多个连接。我们看到了如何添加凭证以进行身份验证。最重要的是,我们看到了如何创建配置,其中一个必须是主bean。最后,我们添加了一些针对运行中的MongoDB实例的测试案例,使用Docker和Spring Boot测试。

如往常一样,本文的所有代码可在GitHub上找到。