系统集成mybatisplus+hikari+使用注解实现多数据源切换方式

业务与读写分离
背景:项目是比较大的业务与数据操作都冗杂在一起的系统,现在需要进行数据读写的业务抽离,原来没有集成mybatis等框架,全部使用DatabaseUtil类来获取jdbc连接,当然这个连接是有数据库连接池在启动的时候 就好了的,每次拿connection,但相较于mybatis等优秀框架还是差别很大,故集成。

使用方式:现有系统集成mybatisplus+hikari+使用注解实现多数据源切换方式。
主要更改内容:
DataBaseEnum:现有三个数据源枚举名称。
DataBaseSource:创建@DataBaseSource注解,标注在方法或者类上,动态切换数据源。
DataBaseSourceAspect:切换数据源过程,找到切点,判断使用什么数据库,切换数据源。
DataSourceContextHolder:用ThreadLocal(线程内部的存储类,提供线程内存储变量的功能)保存数据源的信息到线程中,方便使用获取数据源信息。
DynamicDataSource:继承AbstractRoutingDataSource(根据用户规则选择当前数据源),在每次数据库查询操作前执行determineCurrentLookupKey()决定使用哪个数据源。
HikariPoolConfig:初始化数据库配置,创建数据源,加入配置,加入到动态数据源内。
MybatisConfig:mybatisplus的配置类,创建session工厂。主要是为了解决,mybatisplus空字段不返回问题,setCallSettersOnNulls设为true。
SpringUtils:非spring管理的类获取bean对象的公共方法。
DatabaseInfoHandleUtils:原数据库操作查询后的数据经过了一定的代码处理,这个类主要为了处理mybatis查询到的数据,代码处理后与原先数据保持不变。(按照自己原有逻辑写就可以)
com.xxxx.xxx.dao主要写从业务里抽离出来的sql查询方法。
其他修改点就是 将原来所有sql抽取出来,放入dao里面。

话不多说,贴代码:
DataBaseEnum

package com.xxx.xxx.framework.datasource;

/**
 *
 * 项目里面用了三个数据库
 * @author liutt
 * @date 2020/09/15
 */
public enum DataBaseEnum {

    /**
     * 对应xxx.mysqlUrls
     */
    STATISTICS,
    /**
     * 对应xxx.statistics.mysqlUrls
     */
    ETERNAL,
    /**
     * xxxx.mysqlUrls
     */
    TESTCASE;

}

DataBaseSource 注解类

package com.xxx.xxx.framework.datasource;

import java.lang.annotation.*;

/**
 *
 * DataBaseSource annotation
 *
 * @author liutt
 * @date 2020/09/10
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
@Documented
public @interface DataBaseSource {
    DataBaseEnum value() default DataBaseEnum.STATISTICS;
}

DataSourceContextHolder

package com.xxx.xxx.framework.datasource;


/**
 * 设置路由的目的是为了方便查找对应的数据源
 * 可以用ThreadLocal保存数据源的信息到每个线程中
 * 方便使用获取
 *
 * Gets and sets the context environment.
 * It is mainly responsible for changing the name of the context data source
 *
 * @author liutt
 * @date 2020/09/10
 */
class DataSourceContextHolder {

    private DataSourceContextHolder() {
    }

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

    static void setDataSource(String dbType) {
        CONTEXT_HOLDER.set(dbType);
    }

    static String getDataSource() {
        return CONTEXT_HOLDER.get();
    }

    static void clearDataSource() {
        CONTEXT_HOLDER.remove();
    }
}

DataBaseSourceAspect 切面类

package com.xxx.xxx.framework.datasource;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;


/**
 * AOP switch datasource procedure
 *
 * @author liutt
 * @date 2020/09/10
 */

@Aspect
@Component
public class DataBaseSourceAspect {
    private static final Logger logger = LoggerFactory.getLogger(DataBaseSourceAspect.class);

    @Pointcut("@annotation(com.xxx.xxx.framework.datasource.DataBaseSource)")
    public void dbPointCut() {

    }

    @Before("dbPointCut()")
    public void beforeSwitchDataSource(JoinPoint joinPoint) {
        //获取当前访问的class
        Class<?> className = joinPoint.getTarget().getClass();
        String dataSource;
        DataBaseSource annotation = null;
        try {
            if (className.isAnnotationPresent(DataBaseSource.class)) {
                annotation = className.getAnnotation(DataBaseSource.class);
            } else {
                Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
                if (method.isAnnotationPresent(DataBaseSource.class)) {
                    annotation = method.getAnnotation(DataBaseSource.class);

                }
            }
            dataSource = annotation == null ? DataBaseEnum.STATISTICS.name() : annotation.value().name();
            DataSourceContextHolder.setDataSource(dataSource);
        } catch (Exception e) {
            logger.info(e.getMessage(), e);
        }
    }

    @After("dbPointCut()")
    public void afterSwitchDataSource(JoinPoint joinPoint) {
        DataSourceContextHolder.clearDataSource();
    }
}

HikariPoolConfig
备注:ConfigHelper是为了获取配置文件中的参数,只需要得到你的参数值就可以。

package com.xxx.xxx.framework.datasource;

import com.xxx.xxx.framework.config.ConfigHelper;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;


/**
 * 初始化数据库配置 创建数据源 加入配置
 * 加入到动态数据源内
 *
 * @author liutt
 * @date 2020/09/10
 */

@Configuration
public class HikariPoolConfig {

    private static final Logger logger = LoggerFactory.getLogger(HikariPoolConfig.class);

    static final String STATISTICS_DB = "test01";
    static final String ETERNAL_DB = "test02";
    static final String TEST_CASE_DB = "test03";

    @Bean(name = STATISTICS_DB)
    public static DataSource defaultDataSource() {

        String url = ConfigHelper.getStatisticsSqlUrl();
        String user = ConfigHelper.getMysqlUser();
        String passWord = ConfigHelper.getMysqlPasswd();

        return new HikariDataSource(setHikariConfigInfo(url, user, passWord));
    }

    @Bean(name = ETERNAL_DB)
    public static DataSource eternalDataSource() {

        String url = ConfigHelper.getEternalStatisticSqlUrl();
        String user = ConfigHelper.getMysqlUser();
        String passWord = ConfigHelper.getMysqlPasswd();

        return new HikariDataSource(setHikariConfigInfo(url, user, passWord));
    }

    @Bean(name = TEST_CASE_DB)
    public static DataSource testCaseDataSource() {

        String url = ConfigHelper.getStatisticsTestCaseSqlUrl();
        String user = ConfigHelper.getMysqlUser();
        String passWord = ConfigHelper.getMysqlPasswd();

        return new HikariDataSource(setHikariConfigInfo(url, user, passWord));
    }

    @Primary
    @Bean(name = "dynamicDataSource")
    public static DynamicDataSource dataSource(@Qualifier(STATISTICS_DB) DataSource statisticsDataSource,
                                        @Qualifier(ETERNAL_DB) DataSource eternalDataSource,
                                        @Qualifier(TEST_CASE_DB) DataSource testCaseDataSource) {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DataBaseEnum.STATISTICS.name(), statisticsDataSource);
        targetDataSources.put(DataBaseEnum.ETERNAL.name(), eternalDataSource);
        targetDataSources.put(DataBaseEnum.TESTCASE.name(), testCaseDataSource);
        return new DynamicDataSource(statisticsDataSource, targetDataSources);
    }

    public static HikariConfig setHikariConfigInfo(String url, String user, String passWord) {
        HikariConfig config = new HikariConfig();
        config.setMaximumPoolSize(50);
        config.setMinimumPoolSize(4);
        config.setDataSourceClassName("com.mysql.jdbc.jdbc2.optional.MysqlDataSource");
        config.addDataSourceProperty("url", url);
        config.addDataSourceProperty("encoding", ConfigHelper.getMysqlEncoding());
        config.addDataSourceProperty("noAccessToProcedureBodies", "true");
        config.addDataSourceProperty("user", user);
        config.addDataSourceProperty("password", passWord);

        return config;
    }
}

DynamicDataSource

package com.xxx.xxx.framework.datasource;


import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

import javax.sql.DataSource;
import java.util.Map;

/**
 * AbstractRoutingDataSource
 * [Select the current datasource according to user rules]
 *
 * @author liutt
 * @date 2020/09/10
 */
public class DynamicDataSource extends AbstractRoutingDataSource {

    public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
        super.setDefaultTargetDataSource(defaultTargetDataSource);
        super.setTargetDataSources(targetDataSources);
        super.afterPropertiesSet();
    }

    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDataSource();
    }
}

MybatisConfig

package com.xxx.xxx.framework.datasource;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

/**
 * @author liutt
 * @date 2020/09/22
 */

@Configuration
public class MybatisConfig  {

    @Bean("sqlSessionFactory")
    @Primary
    public SqlSessionFactory sqlSessionFactoryBean(@Qualifier("dynamicDataSource") DynamicDataSource dynamicDataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
        configuration.setCallSettersOnNulls(true);
        bean.setConfiguration(configuration);
        bean.setDataSource(dynamicDataSource);
        return bean.getObject();
    }
    @Bean
    @Primary
    public DataSourceTransactionManager dataSourceTransactionManager(@Qualifier("dynamicDataSource")DynamicDataSource dynamicDataSource) {
        return new DataSourceTransactionManager(dynamicDataSource);
    }
}

SpringUtils

package com.xxx.xxx.utils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;


/**
 * Non spring managed classes call bean objects
 *
 * @author liutt
 * @date 2020/09/11
 */
@Component
public class SpringUtils implements ApplicationContextAware {

    private static final Logger logger = LoggerFactory.getLogger(SpringUtils.class);

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringUtils.applicationContext = applicationContext;
    }

    public static <T> T getBean(Class<T> requiredType) {
        return applicationContext.getBean(requiredType);
    }
}


版权声明:本文为ldddd_原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。