What is the most elegant way to switch web project data sources?

Posted by swathin2 on Mon, 03 Jun 2019 23:39:59 +0200

As business changes/requirements change, Java EE applications will be forced to connect multiple data sources for business processing.

How to dynamically switch data sources in the most elegant/concise way without affecting the original project structure?

In this paper, the dynamic switching practice after adding data sources once is taken as an example to describe the whole process of thinking and practice. If there are any mistakes in this paper, we hope to correct them.

1. Implementation of Dynamic Data Source Dependent on Spring

   

Spring provides an abstract routing data source object called AbstractRouting DataSource, which inherits from AbstractDataSource and implements the DataSource interface in JDK.

This means inheriting AbstractRouting DataSource and overwriting its determineCurrentLookupKey method class as a data source, and personalizing dynamic routing switching among multiple data sources.

(If you are careful enough, open source database connection pools now implement DataSource interfaces and personalize their own encapsulation.)

public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DbContextHolder.getDbType();
    }
}

For a web request, it can be understood as a single thread, and it is reasonable to temporarily store the current data source among threads.

public class DbContextHolder {
    private static final ThreadLocal contextHolder = new ThreadLocal<>();

    /**
     * set up data sources
     *
     * @param dbSourceEnum The name of the database enumeration to set
     */
    public static void setDbType(DBSourceEnum dbSourceEnum) {
        contextHolder.set(dbSourceEnum.getValue());
    }

    /**
     * Get the current data source
     */
    public static String getDbType() {
        return String.valueOf(contextHolder.get());
    }

    /**
     * Clear context data
     */
    public static void clearDbType() {
        contextHolder.remove();
    }
}

Of course, for later expansion and maintenance, as well as ease of use, we introduce enumeration types for data source objects.

In this way, enumeration can be easily modified by other colleagues, and some custom labeling of secondary data sources can also be carried out.

public enum DBSourceEnum {
    one("dataSource1"),
    two("dataSource2");

    private String value;

    DBSourceEnum(String value) {
        this.value = value;
    }

    public String getValue() {
        return value;
    }
}

The above dataSource1/dataSource2 is the data source object Id loaded in spring-context.

    <bean name="dataSource1" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        ......
    </bean>
    <bean name="dataSource2" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        ......
    </bean>

Next, configure DynamicDataSource inherited from AbstractRouting DataSource in context.

   <bean id="dataSource" class="com.rambo.spm.core.multidb.DynamicDataSource">
        <property name="targetDataSources">
            <map key-type="java.lang.String">
                <entry key="dataSource1" value-ref="dataSource1"/>
                <entry key="dataSource2" value-ref="dataSource2"/>
            </map>
        </property>
        <property name="defaultTargetDataSource" ref="dataSource1"/>
    </bean>

Ok, so you can use the Dynamic Data Source when configuring the subsequent dao layer.

2. The most elegant way to switch data sources

After completing the above work, in fact, the dynamic switching of data source objects can play a role, in the business layer as follows.

        DbContextHolder.setDbType(DBSourceEnum.one);
        List<Menu> menuList = menuService.selectList(null);

        DbContextHolder.setDbType(DBSourceEnum.two);
        List<User> userList = userService.selectList(null);

The disadvantage is obvious. There is a high probability of switching/disadvantageous to expansion/switching when connecting to data source 2.

When communicating with the team, we discussed the scheme of intercepting dao layer objects with powerful aop and dynamically switching data sources.

For dao layer objects, which table to access that database is determined, write custom annotations, and bind to dao layer objects.

Custom data sources are annotated as follows:

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface DataSource {

    DBSourceEnum value() default DBSourceEnum.one;
}

Write the tangent processing object, intercept the dao layer object before using it, switch the data source by hand, and set it to default if there is no data source annotation.

Therefore, for the first data source dao layer object in the original project, there is no need to make any modifications, and the tangent processing is as follows.

    @Before("cut()")
    public void doBefore(JoinPoint joinPoint) {
        DataSource dataSource = joinPoint.getTarget().getClass().getAnnotation(DataSource.class);
        DbContextHolder.setDbType(dataSource != null ? dataSource.value() : DBSourceEnum.one);
        log.info("The current data source is:" + DbContextHolder.getDbType());
    }

In most projects, the intricate abstraction and inheritance of dao layers will make it difficult for you to intercept aop facets, and there will always be ways to think and practice more.

Well, that's it. Does it feel easier to expand, program, understand and, of course, be more elegant than aop interception than hard coding in programs?

The code is hosted in: https://git.oschina.net/LanboEx/spmvc-mybatis.git Interested friends can check in the local run.

Topics: Java Spring Database Druid