spring-data-mongodb多資料庫訪問實現

神經病198發表於2018-01-23

最近公司專案需要支援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那樣的MultiTenantConnectionProviderCurrentTenantIdentifierResolver來幫助我們實現。既然spring是通過AOP和Proxy來完成功能的呼叫的,我們似乎也可以這麼玩。

  1. 我們要對所有的Repository方法的訪問建立一個PointCut,這樣我們就可以在訪問之前搞事情了。
  2. 利用反射拿到joinPoint的target,然後呼叫用AopProxyUtils.getSingletonTarget(target)取到最終的SimpleMongoRepository例項。
  3. 通過反射設定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,儘可能的少些模板程式碼。

相關文章