业务背景
需要兼容云上云下的代码。
在云下,用的时 v3.4.6 比较老的版本,为了上云方便,云上申请的也是 v3.4 版本。
阿里云没有小版本,经验证 db.version()
也是 v3.4.6
以为一切都没有问题之后,结果遇到一个坑:
云下的验证方式,默认是 MONGODB-CR 方式,但是云上是 SCRAM-SHA-1 方式。
为什么不通
因为 MONGODB-CR 是不够安全的,出于产品角度的考虑,云上将不对其进行支持。
如何解决
修改云下版本
云上的认证方式,无法修改。那就只有调整云下的认证方式为 SCRAM-SHA-1。
但是很不幸,生产的信息一直在运行,这样无疑对数据的迁移带来很大的成本。
升级 spring-data 版本
这个问题会带来很多包不兼容的问题。
当然我们也会进行讲解。
设置 authMechanism 的方式
这种方法,改动相对较小。
原始配置
基于 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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
<constructor-arg name="mongo" ref="mongo" />
<constructor-arg name="databaseName" value="${mongo_dbname}" />
<constructor-arg name="userCredentials" ref="userCredentials" />
<property name="writeResultChecking">
<value type="org.springframework.data.mongodb.core.WriteResultChecking">EXCEPTION</value>
</property>
</bean>
<bean id="mongo" class="org.springframework.data.mongodb.core.MongoFactoryBean">
<property name="replicaSetSeeds">
<list>
<bean class="com.mongodb.ServerAddress">
<constructor-arg name="host" value="${mongo_host1}" />
<constructor-arg name="port" value="${mongo_port1}" />
</bean>
<bean class="com.mongodb.ServerAddress">
<constructor-arg name="host" value="${mongo_host2}" />
<constructor-arg name="port" value="${mongo_port2}" />
</bean>
</list>
</property>
<property name="mongoOptions" ref="mongoOptions" />
</bean>
<bean id="mongoOptions" class="com.mongodb.MongoOptions">
<property name="safe" value="true" />
</bean>
<bean id="userCredentials" class="org.springframework.data.authentication.UserCredentials">
<constructor-arg name="username" value="${mongo_username}" />
<constructor-arg name="password" value="${mongo_password}" />
</bean>
</beans>
maven 包配置
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb</artifactId>
<version>1.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongo-java-driver</artifactId>
<version>2.11.1</version>
</dependency>
基于 spring-boot 可以自动识别的方式
采用高版本的 spring-data,可以自动根据版本去决定采用哪一种验证方式。
maven 依赖
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb</artifactId>
<version>1.10.11.RELEASE</version>
</dependency>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongo-java-driver</artifactId>
<version>3.4.3</version>
</dependency>
示例代码
示例代码如下:
import com.mongodb.MongoClient;
import com.mongodb.MongoCredential;
import com.mongodb.ServerAddress;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.authentication.UserCredentials;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.SimpleMongoDbFactory;
import java.util.ArrayList;
import java.util.Arrays;
/**
* @author binbin.hou
*/
@Configuration
@ComponentScan("com.github.houbb.mongo")
public class DatasourceMongoConfig {
@Value("${mongo_username}")
private String mongoUsername;
@Value("${mongo_password}")
private String mongoPassword;
@Value("${mongo_host1}")
private String mongoHost1;
@Value("${mongo_port1}")
private int mongoPort1;
@Value("${mongo_host2}")
private String mongoHost2;
@Value("${mongo_port2}")
private int mongoPort2;
@Value("${mongo_dbname}")
private String mongoDbName;
@Bean
public UserCredentials userCredentials() {
return new UserCredentials(mongoUsername, mongoPassword);
}
public @Bean
MongoClient mongoClient() {
ServerAddress serverAddress = new ServerAddress(mongoHost1, mongoPort1);
ServerAddress serverAddress2 = new ServerAddress(mongoHost2, mongoPort2);
return new MongoClient(Arrays.asList(serverAddress, serverAddress2), new ArrayList<MongoCredential>() {
{
add(MongoCredential.createCredential(mongoUsername, mongoDbName, mongoPassword.toCharArray()));
}
});
}
@Bean
public MongoDbFactory mongoDbFactory() throws Exception {
return new SimpleMongoDbFactory(mongoClient(), mongoDbName);
}
public @Bean MongoTemplate mongoTemplate() throws Exception {
return new MongoTemplate(mongoDbFactory());
}
}
MongoCredential.createCredential(mongoUsername, mongoDbName, mongoPassword.toCharArray())
就是最核心的代码。
那么问题来了,如果我们想在 xml 中配置上述的代码,应该怎么做呢?
xml 配置实现自动识别认证方式
maven 依赖
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb</artifactId>
<version>1.10.11.RELEASE</version>
</dependency>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongo-java-driver</artifactId>
<version>3.4.3</version>
</dependency>
配置信息
参考资料:
https://docs.spring.io/spring-data/mongodb/docs/2.0.8.RELEASE/reference/html/#mongo.mongo-xml-config
<?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:context="http://www.springframework.org/schema/context"
xmlns:mongo="http://www.springframework.org/schema/data/mongo"
xsi:schemaLocation=
"http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/data/mongo http://www.springframework.org/schema/data/mongo/spring-mongo.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
<constructor-arg name="mongoDbFactory" ref="mongoDbFactory"/>
</bean>
<mongo:db-factory id="mongoDbFactory" dbname="${mongo_dbname}" mongo-ref="replicaSetMongo"/>
<mongo:mongo-client id="replicaSetMongo" replica-set="${mongo_host1}:${mongo_port1},${mongo_host2}:${mongo_port2}"
credentials="${mongo_username}:${mongo_password}@${mongo_dbname}">
<mongo:client-options
connections-per-host="50"
max-connection-life-time="300000"
min-connections-per-host="20"
max-connection-idle-time="60000"
read-preference="secondaryPreferred"/>
</mongo:mongo-client>
</beans>
使用 URI 的方式
上述方式的缺点
需要升级 spring-data 的版本,从而带来一些列版本不兼容的问题。
如何可以尽可能少的改动代码???
URI
无意间发现可以通过 uri 的方式来制定
实现方式如下:
<?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/data/mongo http://www.springframework.org/schema/data/mongo/spring-mongo.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<mongo:db-factory id="mongoDbFactory"
uri="mongodb://${mongo_username}:${mongo_password}@${mongo_host1}:${mongo_port1},${mongo_host2}:${mongo_port2}/${mongo_dbname}?authMechanism=${mongo_authMechanism:MONGODB-CR}"/>
<bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
<constructor-arg name="mongoDbFactory" ref="mongoDbFactory" />
<property name="writeResultChecking">
<value type="org.springframework.data.mongodb.core.WriteResultChecking">EXCEPTION</value>
</property>
</bean>
</beans>
authMechanism=${mongo_authMechanism:MONGODB-CR}
就是配置的地方,默认我们使用 MONGODB-CR 的方式。
mongo-java-driver 驱动版本
当然,原来的 2.11 版本太低,会报错说不支持 SCRAM-SHA-1 的方式。
后来直接升级较高版本,通过源码发现, 2.13 版本开始支持。
于是 maven 依赖调整如下:
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongo-java-driver</artifactId>
<version>2.13.0</version>
</dependency>