springboot/mybatis之多數據源配置

注:數據源切換不可在事務內部、同一事務中數據源確定、不可切換!

    亦可說數據源切換必須在事務處理之前!

 

一、功能介紹

    在實際的開發中,同一個項目中使用多個數據源是很常見的場景。最近在項目中正好使用到多數據源、搭建完成後記錄一下!可供參考。

二、配置文件

    application.yml中添加多數據源配置

spring:
  application:
    name: businesscooperation-server-dev
  main:
    allow-bean-definition-overriding: true
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource

# 多數據源 配置
datasource:
  defaultName: master
  driverClassName: com.mysql.cj.jdbc.Driver
  # 多數據源配置數據源名稱
  multipleDataSources: master,supplier
  master:
    url: jdbc:mysql://ip:3306/mboms_mall?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&serverTimezone=Asia/Shanghai
    username: xxx
    password: xxx
  supplier:
      url: jdbc:mysql://ip:3306/mbproduct_mall?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&serverTimezone=Asia/Shanghai
      username: xxx
      password: xxx

三、java處理

    1、自定義數據源配置DBProperties.java

package com.hbis.mysql.config;

import com.alibaba.druid.pool.DruidDataSource;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 自定義數據源配置
 *
 * @author henry
 * @date 2020/6/19 14:34
 */
@Data
@Component
public class DBProperties {

    @Resource
    private Environment env;

    @Value("#{'${datasource.multipleDataSources}'.split(',')}")
    private List<String> multipleDataSources;
    @Value("${datasource.driverClassName}")
    private String driverClassName;
    @Value("${datasource.defaultName}")
    private String defaultName;
    /**
     * 數據源存儲
     */
    private Map<Object, Object> dataSources = new HashMap<>();

    private DruidDataSource initDataSource(String url, String username, String password) {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driverClassName);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }

    /**
     * 初始化自定義數據源集
     */
    void init() {
        multipleDataSources.forEach(r -> dataSources.put(r, initDataSource(env.getProperty("datasource." + r + ".url"),
                env.getProperty("datasource." + r + ".username"),
                env.getProperty("datasource." + r + ".password"))));
    }

    /**
     * 獲得默認的數據源 Get the default data source
     *
     * @return DruidDataSource
     */
    DruidDataSource getDefaultDataSource() {
        return (DruidDataSource) this.dataSources.get(defaultName);
    }
}

    2、數據源配置,將自定義的所有的數據源傳給數據源路由器

package com.hbis.mysql.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;

import javax.annotation.Resource;
import javax.sql.DataSource;


/**
 * 數據源配置,將自定義的所有的數據源傳給數據源路由器
 *
 * @author henry
 * @date 2020/6/19 14:33
 */
@Configuration
public class DataSourceConfig {

    @Resource
    private DBProperties dbProperties;

    /**
     * 配置數據源 Configure data sources
     *
     * @return DataSource
     */
    @Bean(name = "dataSource")
    public DataSource dataSource() {
        //初始化自定義數據源集
        dbProperties.init();
        // 採用AbstractRoutingDataSource的對象包裝多數據源
        DynamicDataSource dataSource = new DynamicDataSource();
        dataSource.setTargetDataSources(dbProperties.getDataSources());
        // 設置默認的數據源,當拿不到指定數據源或者未指定數據源時使用該配置
        dataSource.setDefaultTargetDataSource(dbProperties.getDefaultDataSource());
        return dataSource;
    }

    /**
     * 配置事務管理 Configuration transaction management
     *
     * @return PlatformTransactionManager
     */
    @Bean
    public PlatformTransactionManager txManager() {
        return new DataSourceTransactionManager(dataSource());
    }
}

    3、動態數據源

package com.hbis.mysql.config;

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

/**
 * 動態數據源 Dynamic data source
 *
 * @author henry
 * @date 2020/6/19 14:36
 */
public class DynamicDataSource extends AbstractRoutingDataSource {

    //數據源路由器,獲取最終被執行的數據源
    @Override
    protected Object determineCurrentLookupKey() {
        //從本地線程中獲取最終被執行的數據源名稱
        return DynamicDataSourceHolder.getDataSource();
    }
}

    4、動態數據源持有者,負責利用ThreadLocal存取本線程使用的數據源的名稱

package com.hbis.mysql.config;

/**
 * 動態數據源持有者,負責利用ThreadLocal存取本線程使用的數據源的名稱
 *
 * @author henry
 * @date 2020/7/1 10:56
 */
public class DynamicDataSourceHolder {
    /**
     * 本地線程共享對象
     */
    private static final ThreadLocal<String> THREAD_LOCAL = new ThreadLocal<>();

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

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

    /**
     * 清除本線程指定的數據源使用默認數據源
     */
    public static void clear() {
        THREAD_LOCAL.remove();
    }
}

    5、基於aop攔截 註解/java類


package com.hbis.mysql.aop;

import com.hbis.annotation.DataSource;
import com.hbis.mysql.config.DynamicDataSourceHolder;
import lombok.extern.slf4j.Slf4j;
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.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * 數據源切換控制切面
 *
 * @auther: henry
 */
@Component
@Aspect
@Order(0) //保證先被執行
@Slf4j
public class DataSourceAspect {
    /**
     * 註解攔截切面表達式
     *
     * @annotation 用於攔截所有被該註解標註的方法
     * @within 用於攔截被所有該註解標註的類
     */
    @Pointcut("@annotation(com.hbis.annotation.DataSource) || @within(com.hbis.annotation.DataSource) ")
    public void pointcut() {
    }

    @Before("pointcut()")
    public void annotationMethodBefore(JoinPoint joinPoint) {
        Class<?> clazz = joinPoint.getTarget().getClass();
        DataSource annotation = clazz.getAnnotation(DataSource.class);
        //先判斷類上是否有DataSource註解,如果沒有在判斷方法上是否有註解
        if (annotation == null) {//類上沒有
            //獲取方法上的註解
            Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
            annotation = method.getAnnotation(DataSource.class);
            //如果還是爲null則退出,這次方法調用將使用默認的數據源
            if (annotation == null) {
                return;
            }
        }
        //獲取註解上得值
        String dataSourceName = annotation.value();
        log.info("---------------------------切換到數據源:" + dataSourceName + "----------------------------------");
        //因爲有默認數據源的存在,所以不用擔心註解上的值無對應的數據源,當找不到指定數據源時,會使用默認的數據源
        DynamicDataSourceHolder.putDataSource(dataSourceName);
    }

    //執行完切面後,將線程共享中的數據源名稱清空 讓程序使用默認數據源
    @After("pointcut()")
    public void annotationMethodAfter(JoinPoint joinPoint) {
        DynamicDataSourceHolder.clear();
    }

    //---------------------------------基於AOP,攔截某種\某個類-------------------------------------------------------------
    @Pointcut("execution( * com.hbis.framework.service.impl.*.*(..))")
    public void pointcut2() {
    }

    @Before("pointcut2()")
    public void dataSourcePointCut(JoinPoint joinPoint) {
        Class<?> aClass = joinPoint.getTarget().getClass();
        DataSource annotation = aClass.getAnnotation(DataSource.class);
        //取出類上的註解,並將ThreadLocal 中的數據源設置爲指定的數據源, 當然 也可以按照業務需要不適用註解,直接固定某一數據源
        if (annotation != null) {
            String dataSource = annotation.value();
            DynamicDataSourceHolder.putDataSource(dataSource);
        }
    }

    @After("pointcut2()")
    public void annotationMethodAfter1(JoinPoint joinPoint) {
        DynamicDataSourceHolder.clear();
    }
}

    6、自定義註解

package com.hbis.annotation;

import com.hbis.constants.DBConstants;

import java.lang.annotation.*;

/**
 * 用以切換數據源的註解
 *
 * @author henry
 * @date 2020/6/19 14:14
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface DataSource {
    String value() default DBConstants.DATASOURCE_MASTER;
}

    7、數據庫使用常量類

package com.hbis.constants;

public class DBConstants {
    /**
     * 數據源分組
     */
    public static final String DATASOURCE_SUPPLIER = "supplier";

    /**
     * 數據源分組
     */
    public static final String DATASOURCE_MASTER = "master";
}

    8、使用示列 基於註解!兩種方式二選一即可!

/*
 * Copyright (c) 2018-2022 henry, .
 */
package com.hbis.businesscooperationmanager.service.impl;

import com.hbis.annotation.DataSource;
import com.hbis.businesscooperationmanager.dao.SupplierInfoDao;
import com.hbis.businesscooperationmanager.model.dto.BoSupplierContractVO;
import com.hbis.businesscooperationmanager.model.entity.SupplierInfo;
import com.hbis.businesscooperationmanager.service.SupplierInfoService;
import com.hbis.constants.DBConstants;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Map;

/**
 * <p>
 * 供應商信息管理表 服務實現類
 * </p>
 *
 * @author henry
 */
@Service
@DataSource(DBConstants.DATASOURCE_SUPPLIER)
public class SupplierInfoServiceImpl implements SupplierInfoService {

    @Override
    @DataSource(DBConstants.DATASOURCE_SUPPLIER)
    public Map<String, SupplierInfo> querySupplierInfoByIds(List<String> ids) {
        return baseDao.querySupplierInfoByIds(ids);
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章