easymulti-datasource-spring-boot-starter

Update: Fixed backtracking of multiple data source switches in the same thread

Several open source projects of the author have been used in the project, and have been published to the Central repository of Maven, problems will be solved in time, welcome to use, if you have any questions, please go to Github to raise issues.

Resolve backtracking of multiple data source switches in the same thread

In some scenarios, it may be necessary to switch data sources multiple times to process the same request, that is, to switch data sources multiple times on the same thread.

For example, servicea. a calls Serviceb. b, and serviceB. b calls servicec. c. Servicea. a uses the slave library, serviceb. b uses the master library, and Servicec. c uses the slave library, so this call link requires three dynamic data source switches.

The data source switch is done using AOP before the method is executed, fetching the data source key from the annotation and keeping it in ThreadLocal.

When the method completes or fails, we need to remove the switch record from ThreadLocal. Otherwise, we may get the wrong data source from other places that do not display the declared switch data source. We also need to ensure that ThreadLocal’s remove method is called, which can cause problems if the data source is switched multiple times.

When servicea. a is called, the data source is switched to the slave library. In the middle of the method execution, the data source is switched to the master library because the serviceB. b method section overwrites the data source switchover record of the Servicea. a method section.

When the serviceB. b method is executed, the serviceB. b method section calls ThreadLocal’s remove method to remove the data source switchover record on the ServiceB.b method section. Since ThreadLocal stores NULL, all database operations following the Servicea. a method are performed on the primary library if the default data source is configured.

This phenomenon can be called dynamic data source failover failure caused by method call backtracking.

The method of dynamically switching data sources using sections is as follows:

public class EasyMutiDataSourceAspect {
/** * Switch data source **@paramPoint tangent point *@return
     * @throws Throwable
     */
    @Around("dataSourcePointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        EasyMutiDataSource ds = method.getAnnotation(EasyMutiDataSource.class);
        if (ds == null) {
            DataSourceContextHolder.setDataSource(null);
        } else {
            DataSourceContextHolder.setDataSource(ds.value());
        }
        try {
            return point.proceed();
        } finally{ DataSourceContextHolder.clearDataSource(); }}}Copy the code

To solve this problem, I came up with the idea of using the stack data structure to store switching records for dynamic data sources. When the servicea. a method is called to switch data sources, the key of the data source is pushed to the top of the stack. When the servicea. a method is called to switch data sources, the key of the data source is also pushed to the top of the stack. The code is as follows:

public final class DataSourceContextHolder {
   
   /** * sets the data source **@param multipleDataSource
     */
    public static void setDataSource(EasyMutiDataSource.MultipleDataSource multipleDataSource) {
        // The stack used to store switch records
        DataSourceSwitchStack switchStack = multipleDataSourceThreadLocal.get();
        if (switchStack == null) {
            switchStack = new DataSourceSwitchStack();
            multipleDataSourceThreadLocal.set(switchStack);
        }
        // Push the current switched data source to the top of the stack, overwriting the last switched data sourceswitchStack.push(multipleDataSource); }}Copy the code

When the serviceb. b method is complete, the method segment needs to call clearDataSource to remove the key of the switched data source from ThreadLocal. In this case, we can remove an element from the top of the stack and determine whether the stack is empty. After the pop operation removes the key from the data source that the serviceb.b method cuts to, the top of the stack is the data source that was used before the serviceb.b method was called.

public final class DataSourceContextHolder {
    
   /** * clear the data source */
    public static void clearDataSource(a) {
        DataSourceSwitchStack switchStack = multipleDataSourceThreadLocal.get();
        if (switchStack == null) {
            return;
        }
        // Rollback data source switchover
        switchStack.pop();
        // If the stack is empty, all switches have been restored and can be removed
        if (switchStack.size() == 0) { multipleDataSourceThreadLocal.remove(); }}}Copy the code

The stack that holds data source switching records is removed from ThreadLocal only after all pointcuts have called the clearDataSource method. After each pointcut is executed, the clearDataSource method is called to remove its own switching record from the stack, and the switching record of the previous pointcut is stored at the top of the stack, that is, the rollback data source switching. This can solve the backtracking problem of multiple data source switches under the same thread, so that the data source switch is normal.

The stack for storing switch records in easyMulti-datasource is as follows.

class DataSourceSwitchStack {

    private EasyMutiDataSource.MultipleDataSource[] stack;
    private int topIndex;
    private int leng = 2;

    public DataSourceSwitchStack(a) {
        stack = new EasyMutiDataSource.MultipleDataSource[leng];
        topIndex = -1;
    }

    public void push(EasyMutiDataSource.MultipleDataSource source) {
        if (topIndex + 1 == leng) {
            leng *= 2;
            stack = Arrays.copyOf(stack, leng);
        }
        this.stack[++topIndex] = source;
    }

    public EasyMutiDataSource.MultipleDataSource peek(a) {
        return stack[topIndex];
    }

    public EasyMutiDataSource.MultipleDataSource pop(a) {
        return stack[topIndex--];
    }

    public int size(a) {
        return topIndex + 1; }}Copy the code