关注公众号“AI码师”领取2021最新面试资料一份,公众号内回复“源码”,获取本项目源码
【前言】
主演:老王(技术总监),小码(本猿)
老王:小码啊,我们项目中需要使用到mongodb,你集成下吧,完成了和我说下。
小码:好的,一会就给你弄好。
小码三下五除二的给集成好了,然后给老王汇报了。
小码:王哥,我已经把mongodb集成好了。
老王:好的,现在由于我们项目中会用到很多mongo数据库,你现在集成的mongo支持多数据源动态切换么?
小码:这个,这个,啥叫多数据源动态切换啊?
老王:就是在运行过程中,能够根据需要动态去连接哪个数据库,咱们项目需要支持多个特性,如果你对这个不太清楚的话,我给你一个思路,你可以考虑使用切面来实现,具体怎么弄,你自己研究下.
小码:好的,王哥。
小码想了很久,各种百度,终于找到了解决方案,花了一上午的时间,终于弄完了,又去给老王汇报了。
小码:王哥,现在项目中的mongo已经实现了多数据源了(哈哈,心里很自豪)。
老王:小伙子,很快嘛,不过现在又来一个任务,你需要把你集成的这个功能封装成一个starter,另外一个项目也需要使用这个功能,你抽时间封装下吧。
小码:好的,王哥,保证完成任务
小码下去之后,就开始研究怎么去封装成一个starter,下班之前弄好了,不过这次他没去找老王了,准备第二天再去,不然又得加班,哈哈!!!
【正文】
前面水了那么多,主要是给大家设置一种场景,让同志们知道为啥要去做这么一个功能,现在就直接进入正题了:
【springboot集成mongodb】
- 引入mongodb依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
- 配置mongodb连接信息,在application.yml中配置
# 设置了用户名和密码的连接
spring:
data:
mongodb:
uri: mongodb://用户名:密码@IP:PORT/数据库?authSource=${auth_db:用户认证数据库}
# 没有设置用户名和密码的连接配置
spring:
data:
mongodb:
uri: mongodb://IP:PORT/数据库
- 写测试代码
我们创建一个接口,然后在接口方法中去操作monog库:
接口中,直接引入MongoTemplate,就可以直接操作mongo了,这里对mongo如何使用不做过多介绍。
代码语言:javascript复制/**
* Created by AI码师 on 2019/4/19.
* 关注公众号【AI码师】领取2021最新面试资料一份(很全)
* @return
*/
@RequestMapping("/home")
@RestController
public class HomeController {
@Autowired
private MongoTemplate mongoTemplate;
@PostMapping
public String addData(@RequestParam(value = "name") String name,@RequestParam(value = "addr") String addr,@RequestParam(value = "email") String email){
Student student = new Student();
student.setAddr(addr);
student.setName(name);
student.setEmail(email);
mongoTemplate.insert(student);
return "添加成功";
}
}
请求接口:
在这里插入图片描述
响应数据:
响应添加成功,我们看下数据库,是否添加上去了:
在这里插入图片描述
数据已经添加上去了,说明已经集成成功了,但这还是第一步,我们需要做的是支持多数据源,接下来我们一起来完成逼格更高的多数据源mongo吧。
【实现多数据源】
实现思路
先介绍下实现多数据源动态切换的思路:
首先通过AOP技术,在调用方法前后动态替换mongo数据源,这个主要是替换mongo中mongodbfactory(SimpleMongoClientDatabaseFactory)值,每个factory都维护自己需要连接的库,如果在操作之前,替换该参数为自己需要操作的数据库factory,操作结束又切换成原来的,不就可以实现动态切换数据源了么。
说完了思路,我们直接上代码吧
垒代码
- 添加aop依赖
<!--引入AOP依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
- 修改数据库连接配置
# 设置了用户名和密码的连接
spring:
data:
mongodb:
uri: mongodb://用户名:密码@IP:PORT/#?authSource=${auth_db:用户认证数据库}
# 没有设置用户名和密码的连接配置
spring:
data:
mongodb:
uri: mongodb://IP:PORT/#
与上述配置,做了小小的改动,将操作的数据库名称替换成了#,用来做后续备用
- 创建切面
package com.aimashi.dynamicmongo.config;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.mongodb.core.MongoDatabaseFactorySupport;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.SimpleMongoClientDatabaseFactory;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
/**
* Created by AI码师 on 2019/4/19.
* 关注公众号【AI码师】领取2021最新面试资料一份(很全)
* @return
*/
@Component
@Aspect
public class MongoSwitch {
private final Logger logger = LoggerFactory.getLogger(MongoSwitch.class);
@Autowired private MongoDatabaseFactorySupport mongoDbFactory;
private final Map<String, MongoDatabaseFactorySupport> templateMuliteMap = new HashMap<>();
// 获取配置文件的副本集连接
@Value("${spring.data.mongodb.uri}")
private String uri;
// @Pointcut("@annotation(com.pig4cloud.pig.common.log.annotation.MongoLog)")
@Pointcut("execution(public * com.aimashi.dynamicmongo.config.MongotemplteService.*(..))")
public void routeMongoDB() {}
@Around("routeMongoDB()")
public Object routeMongoDB(ProceedingJoinPoint joinPoint) {
Object result = null;
// 获取需要访问的项目数据库
String dbName = (String) joinPoint.getArgs()[0];
Object o = joinPoint.getTarget();
Field[] fields = o.getClass().getDeclaredFields();
MultiMongoTemplate mongoTemplate = null;
try {
for (Field field : fields) {
field.setAccessible(true);
Class fieldclass = field.getType();
// 找到Template的变量
if (fieldclass == MongoTemplate.class || fieldclass == MultiMongoTemplate.class) {
// 查找项目对应的MongFactory
SimpleMongoClientDatabaseFactory simpleMongoClientDbFactory = null;
// 实例化
if (templateMuliteMap.get(dbName) == null) { // 替换数据源
simpleMongoClientDbFactory =
new SimpleMongoClientDatabaseFactory(this.uri.replace("#", dbName));
templateMuliteMap.put(dbName, simpleMongoClientDbFactory);
} else {
simpleMongoClientDbFactory =
(SimpleMongoClientDatabaseFactory) templateMuliteMap.get(dbName);
}
// 如果第一次,赋值成自定义的MongoTemplate子类
if (fieldclass == MongoTemplate.class) {
mongoTemplate = new MultiMongoTemplate(simpleMongoClientDbFactory);
} else if (fieldclass == MultiMongoTemplate.class) {
Object fieldObject = field.get(o);
mongoTemplate = (MultiMongoTemplate) fieldObject;
}
// 设置MongoFactory
mongoTemplate.setMongoDbFactory(simpleMongoClientDbFactory);
// 重新赋值
field.set(o, mongoTemplate);
break;
}
}
try {
result = joinPoint.proceed();
// 清理ThreadLocal的变量
mongoTemplate.removeMongoDbFactory();
} catch (Throwable t) {
logger.error("", t);
mongoTemplate.removeMongoDbFactory();
}
} catch (Exception e) {
logger.error("", e);
}
return result;
}
}
- 创建相关配置类
package com.aimashi.dynamicmongo.config;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.stereotype.Service;
/**
* Created by AI码师 on 2019/4/19.
* 关注公众号【AI码师】领取2021最新面试资料一份(很全)
* @return
*/
@Service
public class MongotemplteService {
private MongoTemplate mongoTemplate;
public <T> T save(String dbName, T var1) {
return mongoTemplate.save(var1);
}
}
代码语言:javascript复制package com.aimashi.dynamicmongo.config;
import com.mongodb.client.MongoDatabase;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.mongodb.core.MongoDatabaseFactorySupport;
import org.springframework.data.mongodb.core.MongoTemplate;
public class MultiMongoTemplate extends MongoTemplate {
private Logger logger= LoggerFactory.getLogger(MultiMongoTemplate.class);
//用来缓存当前MongoDbFactory
private static ThreadLocal<MongoDatabaseFactorySupport> mongoDbFactoryThreadLocal;
public MultiMongoTemplate(MongoDatabaseFactorySupport mongoDbFactory){
super(mongoDbFactory);
if(mongoDbFactoryThreadLocal==null) {
mongoDbFactoryThreadLocal = new ThreadLocal<>();
}
}
public void setMongoDbFactory(MongoDatabaseFactorySupport factory){
mongoDbFactoryThreadLocal.set(factory);
}
public void removeMongoDbFactory(){
mongoDbFactoryThreadLocal.remove();
}
@Override
public MongoDatabase getDb() {
return mongoDbFactoryThreadLocal.get().getMongoDatabase();
}
}
- 添加测试接口
/**
* Created by AI码师 on 2019/4/19.
* 关注公众号【AI码师】领取2021最新面试资料一份(很全)
* @return
*/
// dbName 为数据库名称
@PutMapping
public String addDataByDynamic(@RequestParam(value = "dbName") String dbName,@RequestParam(value = "name") String name,@RequestParam(value = "addr") String addr,@RequestParam(value = "email") String email){
Student student = new Student();
student.setAddr(addr);
student.setName(name);
student.setEmail(email);
mongotemplteService.insert(dbName,student);
return "添加成功";
}
请求接口:数据库名参数传了ams1
在这里插入图片描述
请求响应:响应成功
我们看下数据库,发现在数据库ams1下面已经有了此数据:
在这里插入图片描述
我们将数据库名参数修改为:ams2,进行请求
发现数据源已经切换成功了。
到这里,大家有没有发现自己很牛逼了啊,不过本篇文章还没算完,现在虽然已经实现了动态切换数据源的功能,但是还只能在自己项目上用,别的项目需要使用,只能直接复制过去,我们接下来需要做一个更牛逼的事情:手写一个starter来封装这个功能,别人只需要引入依赖,即可开箱即用:
【整合到starter里面】
- 创建一个maven项目:dynamicmongo-starter
将如下文件拷贝到新项目中
- 创建自动装配文件
package com.aimashi.dynamicmongo.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Created by AI码师 on 2019/4/19.
* 关注公众号【AI码师】领取2021最新面试资料一份(很全)
* @return
*/
@Configuration(proxyBeanMethods = false)
public class MongodbAutoConfiguration {
@Bean
public MongoSwitch mongoSwitch() {
return new MongoSwitch();
}
@Bean
public MongotemplteService mongotemplteService() {
return new MongotemplteService();
}
}
- 新建 resources/META_INF/spring.factories 文件
org.springframework.boot.autoconfigure.EnableAutoConfiguration=
com.aimashi.dynamicmongo.config.MongodbAutoConfiguration
到这里starter已经编写完成,是不是很简单。。
【使用starter】
starter已经编写好,我们只需要在项目中引入该依赖
代码语言:javascript复制 <dependency>
<groupId>com.aimashi</groupId>
<artifactId>dynamicmongo-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
然后在需要操作mongod方法的地方,引入:MongotemplteService即可;
注意 MongotemplteService 里面的方法大家按需扩充,目前只写了一个,大家使用的时候,只需要把mongoTemplate里面的方法名写到MongotemplteService中,然后再去调用mongoTemplate里面对应方法即可。
【总结】
很少写这么长的实践类文章,现在已经十一点半了,该休息了,后面会有更多文章和大家一起分享,希望大家能有所收获,晚安!