Spring boot mybatis 動態(tài)多數據源實現

在項目中在某些時候可能會需要同時連接使用多個不同的數據庫,這就是我們今天要說的多數據源問題??梢允侵鲝牡膱鼍埃鲙靾?zhí)行增刪改的業(yè)務邏輯,從庫進行大量復雜查詢、報表之類的,從而不影響主要業(yè)務。也可以是業(yè)務邏輯設計到多個主數據庫的問題,我這里是不太推薦的,如果可以的話最好拆分成微服務進行調用。多個數據源中處理起事務也是非常麻煩的,而且也沒有必要的,相比之下使用一些補償機制達到最終用一致性也許是更好的選擇。這個也沒有絕對性的,因需求而異。

這個實現方式網上早已有許多成熟的例子,我也是借鑒了一些,記錄一下,讓自己熟悉熟悉。如果涉及到一些版權問題。請及時聯系,非常抱歉。

主要實現方式是注解+AOP

下面總結下步驟:

  1. 創(chuàng)建一個動態(tài)數據源的對象
 /**
 * Created by baixiangzhu on 2017/4/19.
 */
public class DynamicDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {

        return DynamicDataSourceContextHolder.getDataSourceType();
      }
}
  1. 注冊數據源。通過spring的Environment對象,讀取配置文件中配置的多個數據源配置。創(chuàng)建動態(tài)數據源對象,注冊到spring容器中。
/**
 * Created by baixiangzhu on 2017/4/19.
 */
@Slf4j
public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar,EnvironmentAware{

    private ConversionService conversionService = new DefaultConversionService();
    private PropertyValues dataSourcePropertyValues;

    // 如配置文件中未指定數據源類型,使用該默認值
    private static final Object DATASOURCE_TYPE_DEFAULT = "org.apache.tomcat.jdbc.pool.DataSource";


    // 數據源
    private DataSource defaultDataSource;
    private Map<String, DataSource> customDataSources = Maps.newHashMap();

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        Map<Object, Object> targetDataSources = Maps.newHashMap();
        // 將主數據源添加到更多數據源中
        targetDataSources.put("dataSource", defaultDataSource);
        DynamicDataSourceContextHolder.dataSourceIds.add("dataSource");
        // 添加更多數據源
        targetDataSources.putAll(customDataSources);

        //記錄注冊的數據源key
        customDataSources.keySet().stream().forEach( e -> DynamicDataSourceContextHolder.dataSourceIds.add(e));


        // 創(chuàng)建DynamicDataSource
        GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
        beanDefinition.setBeanClass(DynamicDataSource.class);
        beanDefinition.setSynthetic(true);
        MutablePropertyValues mpv = beanDefinition.getPropertyValues();
        mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource);
        mpv.addPropertyValue("targetDataSources", targetDataSources);
        registry.registerBeanDefinition("dataSource", beanDefinition);

        log.info("Dynamic DataSource Registry");
    }


    @SuppressWarnings("unchecked")
    public DataSource buildDataSource(Map<String, Object> dsMap) {
        try {
            Object type = dsMap.get("type");
            if (type == null)
                type = DATASOURCE_TYPE_DEFAULT;// 默認DataSource

            Class<? extends DataSource> dataSourceType;
            dataSourceType = (Class<? extends DataSource>) Class.forName((String) type);

            String driverClassName = dsMap.get("driver-class-name").toString();
            String url = dsMap.get("url").toString();
            String username = dsMap.get("username").toString();
            String password = dsMap.get("password").toString();

            DataSourceBuilder factory = DataSourceBuilder.create().driverClassName(driverClassName).url(url)
                    .username(username).password(password).type(dataSourceType);
            return factory.build();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 加載多數據源配置
     */
    @Override
    public void setEnvironment(Environment env) {
        initDataSource(env);
    }

    /**
     * 初始化主數據源
     */
    private void initDataSource(Environment env) {
        // 讀取主數據源
        RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(env, "datasource.");
        String dsPrefixs = propertyResolver.getProperty("names");

        //獲取所有數據源名稱
        String[] dataSourceNames = dsPrefixs.split(",");

        //綁定主數據源
        Arrays.stream(dataSourceNames).filter( e -> isMaster(e)).forEach( e-> bindMasterDataSource(env));

        //綁定從數據源
        Arrays.stream(dataSourceNames).filter( e -> !isMaster(e)).forEach(e-> bindSlaveDataSource(e,env));

    }

    /**
     * 綁定數據源
     * @param dsPrefix
     * @param env
     */
    private void bindSlaveDataSource(String dsPrefix, Environment env) {

        RelaxedPropertyResolver otherPropertyResolver = new RelaxedPropertyResolver(env, dsPrefix + ".datasource.");
        Map<String, Object> dsMap=this.convertData(otherPropertyResolver);
        DataSource ds = buildDataSource(dsMap);
        dataBinder(ds, env);
        customDataSources.put(dsPrefix, ds);

    }

    /**
     * 綁定默認數據源
     */
    private void bindMasterDataSource(Environment env) {

        //綁定默認數據源
        RelaxedPropertyResolver defaulPropertyResolver = new RelaxedPropertyResolver(env, "spring.datasource.");
        Map<String, Object> dsMap=this.convertData(defaulPropertyResolver);
        defaultDataSource = buildDataSource(dsMap);
        dataBinder(defaultDataSource, env);

    }

    private boolean isMaster(String dataSourceName) {

        return "master".equals(dataSourceName);
    }

    private Map<String,Object>  convertData(RelaxedPropertyResolver pr){
        Map<String, Object> dsMap = Maps.newHashMap();
        dsMap.put("type", pr.getProperty("type"));
        dsMap.put("driver-class-name", pr.getProperty("driver-class-name"));
        dsMap.put("url", pr.getProperty("url"));
        dsMap.put("username", pr.getProperty("username"));
        dsMap.put("password", pr.getProperty("password"));
        return dsMap;
    }


    private void dataBinder(DataSource dataSource, Environment env){
        RelaxedDataBinder dataBinder = new RelaxedDataBinder(dataSource);
        dataBinder.setConversionService(conversionService);
        dataBinder.setIgnoreNestedProperties(false);//false
        dataBinder.setIgnoreInvalidFields(false);//false
        dataBinder.setIgnoreUnknownFields(true);//true
        if(dataSourcePropertyValues == null){
            Map<String, Object> rpr = new RelaxedPropertyResolver(env, "offlinetrade_master.datasource").getSubProperties(".");
            Map<String, Object> values =Maps.newHashMap(rpr);
            // 排除已經設置的屬性
            values.remove("type");
            values.remove("driver-class-name");
            values.remove("url");
            values.remove("username");
            values.remove("password");
            dataSourcePropertyValues = new MutablePropertyValues(values);
        }
        dataBinder.bind(dataSourcePropertyValues);
    }


}

注:我這里的數據庫配置,主數據源(即默認數據源)在config配置中心的application.properties文件中,從數據源在本地的bootstrap.yml 文件中,從數據源可以有多個,我這里只配置了一個。

數據源配置如下:

  • 主數據源:


    Paste_Image.png
  • 從數據源

Paste_Image.png

3.創(chuàng)建一個注解,主要作用的標記dao使用的數據源??梢杂迷诜椒ㄉ虾皖惿?,但是不能用在mapper的接口上。

/**
 * Created by baixiangzhu on 2017/4/19.
 */
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface UseDataSource {

    String value() default "";
}

4.創(chuàng)建一個維護數據源的容器,主要是記錄數據源的名字。就是注解中需要配置的名稱.

/**
 * Created by baixiangzhu on 2017/4/19.
 */
public class DynamicDataSourceContextHolder {

    private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
    public static List<String> dataSourceIds = Lists.newArrayList();

    public static void setDataSourceType(String dataSourceType) {
        contextHolder.set(dataSourceType);
    }

    public static String getDataSourceType() {
        return contextHolder.get();
    }

    public static void clearDataSourceType() {
        contextHolder.remove();
    }

    public static boolean containsDataSource(String dataSourceId){
        return dataSourceIds.contains(dataSourceId);
    }

}

5.寫一個AOP類,去攔截標有數據源的方法或者類,根據注解的數據源動態(tài)替換

/**
 * Created by baixiangzhu on 2017/4/19.
 */
@Slf4j
@Aspect
@Order(-1)// 保證該AOP在@Transactional之前執(zhí)行
@Component
public class DynamicDataSourceAspect {

    @Before("@annotation(ds)")
    public void changeDataSource(JoinPoint point, UseDataSource ds) throws Throwable {
        String dsId = ds.value();
        if (!DynamicDataSourceContextHolder.containsDataSource(dsId)) {
            log.error("數據源[{}]不存在,使用默認數據源 > {}", ds.value(), point.getSignature());
        } else {
            log.debug("Use DataSource : {} > {}", ds.value(), point.getSignature());
            DynamicDataSourceContextHolder.setDataSourceType(ds.value());
        }
    }

    @After("@annotation(ds)")
    public void restoreDataSource(JoinPoint point, UseDataSource ds) {
        log.debug("Revert DataSource : {} > {}", ds.value(), point.getSignature());
        DynamicDataSourceContextHolder.clearDataSourceType();
    }

}

至此,重要的實現已經完成。現在來看看如何使用:

  • 需要在項目的啟動類中引入動態(tài)注入數據源的類
Paste_Image.png
  • 在需要使用數據的方法或者類,添加數據源注解
Paste_Image.png

這下小伙伴們就可以愉快的使用了哈。如果有任何疑問,歡迎留言

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發(fā)現,斷路器,智...
    卡卡羅2017閱讀 136,724評論 19 139
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,366評論 25 708
  • Spring Boot 參考指南 介紹 轉載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 47,290評論 6 342
  • (圖片來源網絡)請相信,我時刻準備推翻自己過往的言論。 “語詞的囚徒” 這是今天早上“羅胖曰”中的一個詞。我當時看...
    ylf立方米閱讀 788評論 1 0
  • 1 JAVA_HOME 指JDK的安裝根目錄,如D:\opensource\jdk1.8.0 PATH 指Jav...
    丘兜瑪德閱讀 80評論 0 0

友情鏈接更多精彩內容