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