Implementation of spring data mongodb multi database access

Time:2021-10-14

Recently, the company’s projects need to support multi tenancy. Each tenant database address is different. The requirement is to modify the configuration file and add or delete the database configuration. It can be completed after restarting the system without additional code modification.

I searched the Internet and basically wrote the configuration in the code, for example:http://www.devzxd.top/2017/06…, you cannot dynamically increase or decrease the database configuration, and create different repositories for different databases. After continuing with Google, find an example on GitHubmulti-tenant-spring-mongodb, after looking at the code, it still doesn’t meet the requirements. No way, we can only look at the code and debug to see if we can meet this requirement.

How does spring data mongodb access the database? The core class is simplemongorepository, and the mongooperations variable is the key to access, and this mongooperations is actually mongotemplate. That means that if we can dynamically change the value of this mongotemplate, we can switch the database. I’m glad.

There are solutions, but how to implement them? The library does not provide anything like hibernateMultiTenantConnectionProviderandCurrentTenantIdentifierResolverTo help us achieve. Since spring calls functions through AOP and proxy, it seems that we can play the same way.

  1. We need to create a pointcut for access to all repository methods so that we can do things before access.
  2. Use reflection to get the target of joinPoint, then call it.AopProxyUtils.getSingletonTarget(target)Get the finalSimpleMongoRepositoryexample.
  3. Set the value of mongooperations through reflection.

The code is as follows:

@Repository
public interface WidgetDataRepository extends MongoRepository<WidgetData, String> {
}
//Note: thread unsafe!!!
@Around("execution(* com.example.demo.dao.mongo.MongoWidgetDataRepo.*(..))")
public Object doSwitch(ProceedingJoinPoint joinPoint) throws Throwable {
    //Get what we need, tenant
    String tenant = (String) RequestContextHolder.currentRequestAttributes().getAttribute("tenant", SCOPE_REQUEST);
    tenant = tenant == null ? "test" : tenant;
    
    //Get target through reflection
    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);

    //Get simplemongorepository and fill in the value
    Object singletonTarget = AopProxyUtils.getSingletonTarget(target);
    Field mongoOperationsField = singletonTarget.getClass().getDeclaredField("mongoOperations");
    mongoOperationsField.setAccessible(true);
    mongoOperationsField.set(singletonTarget, ac.getBean("mongoTemplate" + tenant));

    return joinPoint.proceed();
}

In this way, we can complete the database switching. We can use spring data mongodb to minimize the template code as much as possible.

Recommended Today

The selector returned by ngrx store createselector performs one-step debugging of fetching logic

Test source code: import { Component } from ‘@angular/core’; import { createSelector } from ‘@ngrx/store’; export interface State { counter1: number; counter2: number; } export const selectCounter1 = (state: State) => state.counter1; export const selectCounter2 = (state: State) => state.counter2; export const selectTotal = createSelector( selectCounter1, selectCounter2, (counter1, counter2) => counter1 + counter2 ); // […]