在使用spring-boot-starter-data-mongodb配置多个数据源的MongoTemplate时,启动项目报错,错误日志如下:
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2020-06-10 08:23:09.734 [//] [main] ERROR o.s.b.d.LoggingFailureAnalysisReporter Line:42 -
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 1 of method gridFsTemplate in org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration required a single bean, but 2 were found:
- XXXMongoTemplate: defined by method 'XXXMongoTemplate' in class path resource [com/saicmotor/telematics/icar/common/config/MongoDBConfig.class]
- XXXXMongoTemplate: defined by method 'XXXXMongoTemplate' in class path resource [com/saicmotor/telematics/icar/common/config/MongoDBConfig.class]
- XXXXXMongoTemplate: defined by method 'XXXXXMongoTemplate' in class path resource [com/saicmotor/telematics/icar/common/config/MongoDBConfig.class]
Action:
Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed
从Action中还有查阅一些资料后得知,如果项目中用到的不止一个mongo库,或者同一个库中的不同数据源,那么就需要配置多个MongoTemplate,每个MongoTemplate对应一个mongo数据源,这个时候就需要在这么多mongo数据源中指定一个数据源作为默认数据源,如何指定一个数据源为默认数据源呢?
答案就是使用@Primary注解,将该注解标注在使用次数最多的那个MongoTemplate的bean上就可以了,比如你有三个数据源,分别是A、B、C,然后声明了三个MongoTemplate Bean,分别是AMongoTemplate、BMongoTemplate、CMongoTemplate、其中项目中使用最多的是A数据源,那最好是把@Primary标注在AMonoTemplate的Bean声明上。当然只是建议,也可以放在BMongoTemplate或者CMongoTemplate的声明上。
@Primary可以理解为默认优先选择。一般同一个类被声明了多个Bean之后,都需要指定其中一个标注@Primary。那具体@Primary有什么作用呢,为什么非要选择其中一个Bean为优先选择呢?同样以MongoTemplate为例:
//Mongo配置类
@Configuration
public class MongoConfig{
//这里可以通过@Value注入一些属性
@Value("${mongoclient.host}")
private String host;
@Value("${mongoclient.account}")
private String account;
@Value("${mongoclient.password}")
private String password;
@Value("${mongoclient.database}")
private String database;
@Value("${mongoclient.ssl}")
private String ssl;
//通过该bean与mongo数据库建立连接
@Bean
public MongoClient mongoClient() {
String[] array = host.split(",");
List<ServerAddress> list = new ArrayList<ServerAddress>();
for (int i = 0; i < array.length; i++) {
String tmp = array[i];
ServerAddress address = new ServerAddress(tmp.split(":")[0], Integer.parseInt(tmp.split(":")[1]));
list.add(address);
}
MongoClientOptions.Builder builder = new MongoClientOptions.Builder();
builder.alwaysUseMBeans(false);
builder.connectionsPerHost(800);// 修改每个Host的连接数为800
if (ssl != null && Boolean.parseBoolean(ssl)) {
builder.socketFactory(SSLSocketFactory.getDefault());
}
MongoClientOptions mongoClientOptions = builder.build();
if (!StringUtils.isEmpty(account) && !StringUtils.isEmpty(password)) {
MongoCredential credentials = MongoCredential.createScramSha1Credential(account, database,
password.toCharArray());
return new MongoClient(list, credentials, mongoClientOptions);
} else {
return new MongoClient(list, mongoClientOptions);
}
}
//声明该mongo库的两个MongoTemplate,并将其中一个作为@Primary
@Primary
@Bean(name = "primaryMongoTemplate")
@ConditionalOnClass(MongoClient.class)
public MongoTemplate primaryMongoTemplate(MongoClient mongoClient) {
MongoTemplate mongoTemplate = new MongoTemplate(new SimpleMongoDbFactory(mongoClient, "primaryMongo"));//primaryMongo为mongo集合名
return mongoTemplate;
}
@Bean(name = "secondMongoTemplate")
@ConditionalOnClass(MongoClient.class)
public MongoTemplate secondMongoTemplate(MongoClient mongoClient) {
MongoTemplate mongoTemplate = new MongoTemplate(new SimpleMongoDbFactory(mongoClient, "second"));//second为mongo集合名
return mongoTemplate;
}
}
然后可能需要在某个ServiceImpl中去使用这些MongoTemplate:
public class XXXServiceImpl implements XXXService {
//注入两个MongoTemplate
@Autowired
//由于MongoTemplate有两个Bean实例,而@Autowired是按照类去装配Bean的,
//理论上这时如果不加@Qualifier注解,spring容器不知道去装配哪个Bean实例的,所以要通过@Qualifier去按名称装配Bean实例
//注意:@Qualifier注解必须要在@AutoWired的后面
@Qualifier(value = "primaryMongoTemplate")
private MongoTemplate primaryMongoTemplate;
@Autowired
@Qualifier(value = "secondMongoTemplate")
private MongoTemplate secondMongoTemplate;
//使用primaryMongoTemplate查询
public List<XXX> mongoQuery() {
return primaryMongoTemplate.find(new Query(条件), XXX.class);
}
//使用secondMongoTemplate保存
public void saveMongo() {
secondMongoTemplate.save(XXX);
}
}
上述基本满足了程序与mongo进行数据交互,可以通过上述方式从Mongo中查询数据,保存数据,但是上述例子并没有体现出来@Primary默认优先选择的作用,其实上面例子的注解中也说了在使用@Autowired注解时理论上要加上@Qualifier注解的,但是其实也可以不加。
public class XXServiceImpl implements XXService {
@Autowired
private MongoTemplate mongoTemplate;
public List<XXXX> query () {
return mongoTemplate.find(new Query(), XXXX.class);
}
这种方式注入MongoTemplate也是可以的,此时注入的MongoTemplate就是在Config类中加了@Primary注解的MongoTemplate的Bean,查询也不会报错,但是如果加了@Primary注解的数据源中没有XXXX对应的集合,那么此次查询将查不出来结果,所以理论上都要求@Qualifier注解具体制定MongoTemplate
}
所以总结:1、多数据源MongoTemplate要加@Primary指定默认优先选择
2、@Autowired注解后要加@Qualifier(value = "primaryMongoTemplate")按Bean名称具体装配MongoTemplate
3、@Qualifier注解要在@Autowired注解之后