Dynamic configuration of data source based on spring boot and AOP

Keywords: Programming Druid Spring JDBC MySQL

Spring boot + AOP mode for multi data source switching

Overall design idea: spring boot + AOP mode realizes multi data source switching, inherits AbstractRoutingDataSource to achieve dynamic data source acquisition, and uses annotation to specify data source in service layer.

I. multi data source configuration

In application.properties, our configuration is as follows

#Master data source
druid.master.url=jdbc:mysql://url/masterdb?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull
druid.master.username=xxx
druid.master.password=123
druid.master.driver-class-name=com.mysql.jdbc.Driver
druid.master.max-wait=5000
druid.master.max-active=100
druid.master.test-on-borrow=true
druid.master.validation-query=SELECT 1

#From data sources
druid.slave.url=jdbc:mysql://url/slavedb?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull
druid.slave.username=xxx
druid.slave.password=123
druid.slave.driver-class-name=com.mysql.jdbc.Driver
druid.slave.max-wait=5000
druid.slave.max-active=100
druid.slave.test-on-borrow=true
druid.slave.validation-query=SELECT 1

Read configuration

<!-- master data source -->
<bean primary="true" id="masterdb" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
    <!-- Basic attributes url,user,password -->
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="${druid.master.url}"/>
    <property name="username" value="${druid.master.username}"/>
    <property name="password" value="${druid.master.password}"/>
    <!-- Configuration initialization Max -->
    <property name="maxActive" value="${druid.master.max-active}"/>
    <!-- Configure the timeout time for getting connection waiting -->
    <property name="maxWait" value="${druid.master.max-wait}"/>
    <property name="validationQuery" value="${druid.master.validation-query}"/>
    <property name="testOnBorrow" value="${druid.master.test-on-borrow}"/>

</bean>

<!-- slave data source -->
<bean primary="true" id="slavedb" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
    <!-- Basic attributes url,user,password -->
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="${druid.slave.url}"/>
    <property name="username" value="${druid.slave.username}"/>
    <property name="password" value="${druid.slave.password}"/>

    <!-- Configure initialization size, min, Max -->
    <property name="maxActive" value="${druid.slave.max-active}"/>
    <!-- Configure the timeout time for getting connection waiting -->
    <property name="maxWait" value="${druid.slave.max-wait}"/>
    <property name="validationQuery" value="${druid.slave.validation-query}"/>
    <property name="testOnBorrow" value="${druid.slave.test-on-borrow}"/>
</bean>

<!-- Dynamic data sources, based on service The annotation on the interface determines which data source to take -->
<bean id="dataSource" class="datasource.DynamicDataSource">
    <property name="targetDataSources">
        <map key-type="java.lang.String">
            <entry key="slave" value-ref="slavedb"/>
            <entry key="master" value-ref="masterdb"/>
        </map>
    </property>
    <property name="defaultTargetDataSource" ref="masterdb"/>
</bean>

<!-- Spring JdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="dataSource" />
</bean>

<!-- Spring Transaction manager -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
</bean>

<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
    <property name="transactionManager" ref="transactionManager"/>
</bean>

<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true" order="2" />

<!-- depositdbSqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="mapperLocations" value="classpath*:mapper-xxdb/*Mapper*.xml" />
</bean>

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="xxdb.mapper"/>
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>

II. Dynamic data source

spring provides us with AbstractRoutingDataSource, which is a data source with routing. After inheritance, we need to implement its determineCurrentLookupKey(), which is used to customize the routing method of the actual data source name. Since we save the information to ThreadLocal, we only need to take it out.

public class DynamicDataSource extends AbstractRoutingDataSource {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Override
    protected Object determineCurrentLookupKey() {
        String dataSource = JdbcContextHolder.getDataSource();
        logger.info("Data source is{}",dataSource);
        return dataSource;
    }

}

III. data source dynamic switching class

Dynamic data source switching is based on AOP, so we need to declare an AOP facet, and switch the data source before the facet, and remove the data source name after the facet is completed.

@Aspect
@Order(1)   //Set AOP execution order (need to be before the transaction, otherwise the transaction only occurs in the default library)
@Component
public class DataSourceAspect {

    private Logger logger = LoggerFactory.getLogger(this.getClass());
    //Tangent point
    @Pointcut("execution(* com.xxx.service.*.*(..))")
    public void aspect() { }

    @Before("aspect()")
    private void before(JoinPoint point) {
        Object target = point.getTarget();
        String method = point.getSignature().getName();
        Class<?> classz = target.getClass();// Get target class
        Class<?>[] parameterTypes = ((MethodSignature) point.getSignature())
                .getMethod().getParameterTypes();
        try {
            Method m = classz.getMethod(method, parameterTypes);
            if (m != null && m.isAnnotationPresent(MyDataSource.class)) {
                MyDataSource data = m.getAnnotation(MyDataSource.class);
                logger.info("method :{},datasource:{}",m.getName() ,data.value().getName());
                JdbcContextHolder.putDataSource(data.value().getName());// Put the data source into the current thread
            }
        } catch (Exception e) {
            logger.error("get datasource error ",e);
            //Default selection master
            JdbcContextHolder.putDataSource(DataSourceType.Master.getName());// Put the data source into the current thread
        }

    }

    @AfterReturning("aspect()")
    public void after(JoinPoint point) {
        JdbcContextHolder.clearDataSource();
    }
}

IV. data source management

public class JdbcContextHolder {

    private final static ThreadLocal<String> local = new ThreadLocal<>();

    public static void putDataSource(String name) {
        local.set(name);
    }

    public static String getDataSource() {
        return local.get();
    }

    public static void clearDataSource() {
        local.remove();
    }
}

V. data source annotation and enumeration

When we switch the data source, we usually implement it before calling the method of the specific interface, so we define a method annotation. When AOP detects that there is such annotation on the method, we switch according to the name corresponding to the value in the annotation.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyDataSource {

    DataSourceType value();

}
public enum  DataSourceType {
    // Master table
    Master("master"),
    // From table
    Slave("slave");

    private String name;

    private DataSourceType(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

Vi. notes

Because our dynamic data source is configured with a default database, if the method is to operate the default database, there is no need for annotation. If we want to operate a non default data source, we need to add @ MyDataSource("data source name") annotation on the method, so that we can use AOP to realize dynamic switching.

@Component
public class xxxServiceImpl {
    @Resource
    private XxxMapperExt xxxMapperExt;

    @MyDataSource(value= DataSourceType.Slave)
    public List<Object> getAll(){
        return xxxMapperExt.getAll();
    }

}

 

Original link:

http://tech.dianwoda.com/2018/03/28/spring-boot-aopfang-shi-shi-xian-duo-shu-ju-yuan-qie-huan/

Posted by the_Igel on Wed, 16 Oct 2019 23:06:57 -0700