最近公司项目需要支持multi-tenancy,每个tenant数据库地址不同,需求就是修改配置文件,添加或者删除数据库配置,重启系统后就可以完成,不需要额外修改代码。
网上搜索了一下,基本上都是将配置写在了代码里了,例如:http://www.devzxd.top/2017/06…,不能动态增减数据库配置,并且对不同的数据库创建不同的Repository。继续Google之后,在github上找到一个例子multi-tenant-spring-mongodb,看了一下代码,还是不符合需求。没办法,只能看代码,调试来看看自己能不能实现这个需求了。
那spring-data-mongodb怎么来完成数据库的访问的呢?核心类是SimpleMongoRepository,而mongoOperations变量才是完成访问的关键,而这个mongoOperations其实就是mongoTemplate。那就意味着如果我们能够动态改变这个mongoTemplate的值就可以切换数据库了,窃喜。
解决的方法有了,但是如何实现呢?库并没有提供类似Hibernate那样的MultiTenantConnectionProvider
和CurrentTenantIdentifierResolver
来帮助我们实现。既然spring是通过AOP和Proxy来完成功能的调用的,我们似乎也可以这么玩。
- 我们要对所有的Repository方法的访问创建一个PointCut,这样我们就可以在访问之前搞事情了。
- 利用反射拿到joinPoint的target,然后调用用
AopProxyUtils.getSingletonTarget(target)
取到最终的SimpleMongoRepository
实例。 - 通过反射设置mongoOperations的值。
代码如下:
@Repository
public interface WidgetDataRepository extends MongoRepository<WidgetData, String> {
}
// 注意:线程不安全!!!
@Around("execution(* com.example.demo.dao.mongo.MongoWidgetDataRepo.*(..))")
public Object doSwitch(ProceedingJoinPoint joinPoint) throws Throwable {
// 拿到我们需要的tenant
String tenant = (String) RequestContextHolder.currentRequestAttributes().getAttribute("tenant", SCOPE_REQUEST);
tenant = tenant == null ? "test" : tenant;
// 通过反射获取到target
Field methodInvocationField = joinPoint.getClass().getDeclaredField("methodInvocation");
methodInvocationField.setAccessible(true);
ReflectiveMethodInvocation o = (ReflectiveMethodInvocation) methodInvocationField.get(joinPoint);
Field targetField = o.getClass().getDeclaredField("target");
targetField.setAccessible(true);
Object target = targetField.get(o);
// 获得SimpleMongoRepository,并往里面填入值
Object singletonTarget = AopProxyUtils.getSingletonTarget(target);
Field mongoOperationsField = singletonTarget.getClass().getDeclaredField("mongoOperations");
mongoOperationsField.setAccessible(true);
mongoOperationsField.set(singletonTarget, ac.getBean("mongoTemplate" + tenant));
return joinPoint.proceed();
}
这样我们就可以完成数据库的切换了,可以利用spring-data-mongodb,尽可能的少些模板代码。