spring boot 動態(tài)數(shù)據(jù)源切換+通用mapper配置

因為項目必須要用到多數(shù)據(jù)源耳鸯,所以最近研究了下動態(tài)切換數(shù)據(jù)源,在spring2.0以后增加了AbstractRoutingDataSource 來實現(xiàn)對數(shù)據(jù)源的操作湿蛔。

對數(shù)據(jù)源進行切換 繼承擴展AbstractRoutingDataSource這個抽象類 ,根據(jù)提供的鍵值切換對應(yīng)的數(shù)據(jù)源,下面我們來看下這個類


public abstract class AbstractRoutingDataSourceextends AbstractDataSourceimplements InitializingBean {

@Nullable

private Map targetDataSources; //存放所有的數(shù)據(jù)源的map 根據(jù)key 值可獲取對應(yīng)的數(shù)據(jù)源

    @Nullable

private Object defaultTargetDataSource;  //默認的數(shù)據(jù)源

    private boolean lenientFallback =true;

    private DataSourceLookup dataSourceLookup =new JndiDataSourceLookup();

    @Nullable

private Map resolvedDataSources;

    @Nullable

private DataSource resolvedDefaultDataSource;

    public AbstractRoutingDataSource() {

}

public void setTargetDataSources(Map targetDataSources) {

this.targetDataSources = targetDataSources;

    }

public void setDefaultTargetDataSource(Object defaultTargetDataSource) {

this.defaultTargetDataSource = defaultTargetDataSource;

    }

public void setLenientFallback(boolean lenientFallback) {

this.lenientFallback = lenientFallback;

    }

public void setDataSourceLookup(@Nullable DataSourceLookup dataSourceLookup) {

this.dataSourceLookup = (DataSourceLookup)(dataSourceLookup !=null?dataSourceLookup:new JndiDataSourceLookup());

    }

// 初始化bean的時候執(zhí)行,可以針對某個具體的bean進行配置县爬。afterPropertiesSet 必須實現(xiàn) InitializingBean接口阳啥。實現(xiàn) InitializingBean接口必須實現(xiàn)afterPropertiesSet方法。方法體是將數(shù)據(jù)源分別進行復制到resolvedDataSources和resolvedDefaultDataSource中

public void afterPropertiesSet() {

if(this.targetDataSources ==null) {

throw new IllegalArgumentException("Property 'targetDataSources' is required");

        }else {

this.resolvedDataSources =new HashMap(this.targetDataSources.size());

            this.targetDataSources.forEach((key, value) -> {

Object lookupKey =this.resolveSpecifiedLookupKey(key);

                DataSource dataSource =this.resolveSpecifiedDataSource(value);

                this.resolvedDataSources.put(lookupKey, dataSource);

            });

            if(this.defaultTargetDataSource !=null) {

this.resolvedDefaultDataSource =this.resolveSpecifiedDataSource(this.defaultTargetDataSource);

            }

}

}

protected Object resolveSpecifiedLookupKey(Object lookupKey) {

return lookupKey;

    }

protected DataSource resolveSpecifiedDataSource(Object dataSource)throws IllegalArgumentException {

if(dataSourceinstanceof DataSource) {

return (DataSource)dataSource;

        }else if(dataSourceinstanceof String) {

return this.dataSourceLookup.getDataSource((String)dataSource);

        }else {

throw new IllegalArgumentException("Illegal data source value - only [javax.sql.DataSource] and String supported: " + dataSource);

        }

}

// 先調(diào)用determineTargetDataSource()方法返回DataSource在進行g(shù)etConnection()捌省。

public Connection getConnection()throws SQLException {

return this.determineTargetDataSource().getConnection();

    }

public Connection getConnection(String username, String password)throws SQLException {

return this.determineTargetDataSource().getConnection(username, password);

    }

public T unwrap(Class iface)throws SQLException {

return iface.isInstance(this)?this:this.determineTargetDataSource().unwrap(iface);

    }

public boolean isWrapperFor(Class iface)throws SQLException {

return iface.isInstance(this) ||this.determineTargetDataSource().isWrapperFor(iface);

    }

protected DataSource determineTargetDataSource() {

Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");

** Object lookupKey =this.determineCurrentLookupKey();//這里就是根據(jù) determineCurrentLookupKey獲取相應(yīng)的key 下面再根據(jù)key值進行獲取相應(yīng)的數(shù)據(jù)源.**

        DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey);

        if(dataSource ==null && (this.lenientFallback || lookupKey ==null)) {

dataSource =this.resolvedDefaultDataSource;

        }

if(dataSource ==null) {

throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey +"]");

        }else {

return dataSource;

        }

}

//重寫此方法就可以進行

@Nullable

protected abstract Object determineCurrentLookupKey();

}

定義DynamicDataSource 重寫determineCurrentLookupKey方法

image

數(shù)據(jù)源配置:


spring:
 #數(shù)據(jù)源配置
  datasource:
    common:
      dbconfig:
        minIdle: 5
        maxActive: 20
        # 配置獲取連接等待超時的時間
        maxWait: 60000
        # 配置間隔多久才進行一次檢測苫纤,檢測需要關(guān)閉的空閑連接,單位是毫秒
        timeBetweenEvictionRunsMillis: 60000
        # ? 配置一個連接在池中最小生存的時間纲缓,單位是毫秒
        minEvictableIdleTimeMillis: 300000
        validationQuery: SELECT 1 FROM DUAL
        testWhileIdle: true
        testOnBorrow: false
        testOnReturn: false
        # 打開PSCache卷拘,并且指定每個連接上PSCache的大小
        poolPreparedStatements: true
        maxPoolPreparedStatementPerConnectionSize: 20
        # 配置監(jiān)控統(tǒng)計攔截的filters,去掉后監(jiān)控界面sql無法統(tǒng)計祝高,'wall'用于防火墻
        spring.datasource.filters: stat,wall,log4j
        # 通過connectProperties屬性來打開mergeSql功能栗弟;慢SQL記錄
        connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
    names: base,bloodsugar

base:

      type: com.alibaba.druid.pool.DruidDataSource

driver-class-name: com.mysql.jdbc.Driver

initialize: true#指定初始化數(shù)據(jù)源,是否用data.sql來初始化工闺,默認: true

      name: cmmi

url: jdbc:mysql://192.168.2.13:3306/api?useUnicode=true&characterEncoding=utf-8&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC&zeroDateTimeBehavior=convertToNull

username: root

password: eeesys

minIdle: 5

maxActive: 20

# 配置獲取連接等待超時的時間

      maxWait: 60000

# 配置間隔多久才進行一次檢測乍赫,檢測需要關(guān)閉的空閑連接,單位是毫秒

      timeBetweenEvictionRunsMillis: 60000

# 配置一個連接在池中最小生存的時間陆蟆,單位是毫秒

      minEvictableIdleTimeMillis: 300000

validationQuery: SELECT 1 FROM DUAL

testWhileIdle: true

testOnBorrow: false

testOnReturn: false

# 打開PSCache雷厂,并且指定每個連接上PSCache的大小

      poolPreparedStatements: true

maxPoolPreparedStatementPerConnectionSize: 20

# 配置監(jiān)控統(tǒng)計攔截的filters,去掉后監(jiān)控界面sql無法統(tǒng)計叠殷,'wall'用于防火墻

      spring.datasource.filters: stat,wall,log4j

# 通過connectProperties屬性來打開mergeSql功能改鲫;慢SQL記錄

      connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000



下面創(chuàng)建注冊類,實現(xiàn)數(shù)據(jù)源的注冊和初始化


package com.txby.common.mybatis.DataSource;

import java.sql.SQLException;

import java.util.HashMap;

import java.util.Map;

import javax.sql.DataSource;

import com.alibaba.druid.pool.DruidDataSource;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.beans.MutablePropertyValues;

import org.springframework.beans.PropertyValues;

import org.springframework.beans.factory.support.BeanDefinitionRegistry;

import org.springframework.beans.factory.support.GenericBeanDefinition;

import org.springframework.boot.bind.RelaxedDataBinder;

import org.springframework.boot.bind.RelaxedPropertyResolver;

import org.springframework.context.EnvironmentAware;

import org.springframework.context.annotation.Configuration;

import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;

import org.springframework.core.env.Environment;

import org.springframework.core.type.AnnotationMetadata;

/**
 * 動態(tài)數(shù)據(jù)源注冊
 * @author liqun
 * @data 2018-05-16
 */
public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware {

    private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceRegister.class);
    private static final String prefix = "spring.datasource";
    // 數(shù)據(jù)源配置信息
    private PropertyValues dataSourcePropertyValues;
    // 默認數(shù)據(jù)源
    private DataSource defaultDataSource;
    // 動態(tài)數(shù)據(jù)源
    private Map<String, DataSource> dynamicDataSources = new HashMap<>();



    /**
     *  加載多數(shù)據(jù)源配置
     * @param env
     */
    @Override
    public void setEnvironment(Environment env) {
        //獲取數(shù)據(jù)源配置 的節(jié)點
        Binder binder = Binder.get(env);
        Map parentMap = binder.bind(prefix, Map.class).get();
        Map common = (Map<String, Object>)((Map<String, Object>) parentMap.get("common")).get("dbconfig");
        //數(shù)據(jù)源存放的屬性
        String dsPrefixs = parentMap.get("names").toString();
        //遍歷生成相應(yīng)的數(shù)據(jù)源并存儲
        for (String dsPrefix : dsPrefixs.split(",")) {
            Map<String,Object> map = (Map<String, Object>) parentMap.get(dsPrefix);
            DataSource ds = null;
            if(map.get("minIdle") == null ){
                ds = initDataSource(map,common );
            }else {
                ds = initDataSource(map,null);
            }
            // 設(shè)置默認數(shù)據(jù)源
            if ("base".equals(dsPrefix)) {
                defaultDataSource = ds;
            } else {
                dynamicDataSources.put(dsPrefix, ds);
            }
            //初始化數(shù)據(jù)源
            dataBinder(ds, env,dsPrefix);
        }
    }

    /**
     * 初始化數(shù)據(jù)源
     * @param map
     * @return
     */
    public DataSource initDataSource(Map<String, Object> map ,Map<String, Object> commonMap ) {
        String driverClassName = map.get("driver-class-name").toString();
        String url = map.get("url").toString();
        String username = map.get("username").toString();
        String password = map.get("password").toString();
        Integer minIdle = 0 ;
        Integer maxActive =0;
        Long maxWait;
        Long minEvictableIdleTimeMillis;
        if(map.get("minIdle") == null ){
            minIdle = Integer.parseInt(commonMap.get("minIdle").toString());
            maxActive = Integer.parseInt(commonMap.get("maxActive").toString());
            maxWait = Long.parseLong(commonMap.get("maxWait").toString());
            minEvictableIdleTimeMillis =  Long.parseLong(commonMap.get("minEvictableIdleTimeMillis").toString());
        }else{
            minIdle = Integer.parseInt(map.get("minIdle").toString());
            maxActive = Integer.parseInt(map.get("maxActive").toString());
            maxWait = Long.parseLong(map.get("maxWait").toString());
            minEvictableIdleTimeMillis =  Long.parseLong(map.get("minEvictableIdleTimeMillis").toString());
        }

        DruidDataSource datasource = new DruidDataSource();
        datasource.setUrl(url);
        datasource.setDriverClassName(driverClassName);
        datasource.setUsername(username);
        datasource.setPassword(password);
        datasource.setMinIdle(minIdle);
        datasource.setMaxWait(maxWait);
        datasource.setMaxActive(maxActive);
        datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
        try {
            //開啟Druid的監(jiān)控統(tǒng)計功能 stat表示sql合并,wall表示防御SQL注入攻擊
            datasource.setFilters("stat,wall");
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return datasource;
    }

    /**
     * 加載數(shù)據(jù)源配置信息
     * @param dataSource
     * @param env
     */
    private void dataBinder(DataSource dataSource, Environment env,String defix) {
        Binder binder = Binder.get(env);
        binder.bind( defix,Bindable.ofInstance(dataSource));
    }

    /**
     * 注冊數(shù)據(jù)源been
     * @param importingClassMetadata
     * @param registry
     */
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
        // 將主數(shù)據(jù)源添加到更多數(shù)據(jù)源中
        targetDataSources.put("dataSource", defaultDataSource);
        // 添加更多數(shù)據(jù)源
        targetDataSources.putAll(dynamicDataSources);
        // 創(chuàng)建動態(tài)數(shù)據(jù)源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);

    }

}

啟動類中加 @Import({DynamicDataSourceRegister.class}) 把注冊類注入spring 容器中

為了方便使用我們運用注解的形式


/**

* 指定使用哪個數(shù)據(jù)源的注解

* Created by liqun on 2018/5/16.

*/

@Target({ElementType.METHOD,ElementType.TYPE})

@Retention(RetentionPolicy.RUNTIME)

public @interface TargetDataSource{

Stringname();

}

攔截注解,設(shè)置相應(yīng)的數(shù)據(jù)源林束,使用完成后清楚設(shè)置的數(shù)據(jù)源


/**

* 利用aop原理實現(xiàn)切換數(shù)據(jù)源

* Created by liqun on 2018/5/16.

*/

@Aspect

@Component

public class TargetDataSourceAspect {

/**

* 根據(jù)@TargetDataSource的name值設(shè)置不同的DataSource

    * @param joinPoint

    * @param targetDataSource

    */

    @Before("@annotation(targetDataSource)")

public void changeDataSource(JoinPoint joinPoint,TargetDataSource targetDataSource){

DynamicDataSource.setDataSourceKey(targetDataSource.name());

    }

/**

* 方法執(zhí)行完之后清楚當前數(shù)據(jù)源像棘,讓其使用默認數(shù)據(jù)源

    * @param joinPoint

    * @param targetDataSource

    */

    @After("@annotation(targetDataSource)")

public void restoreDataSource(JoinPoint joinPoint,TargetDataSource targetDataSource){

DynamicDataSource.clearDataSourceKey();

    }

}

到此就可以在Service中引用了


@TargetDataSource(name ="test")

public UsergetUser(int id) {

return userDao.selectByPrimaryKey(id);

}

下面我們配置下通用mapper

pom.xml


  tk.mybatis

  mapper

  3.3.7

配置通用的mapper


import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import tk.mybatis.spring.mapper.MapperScannerConfigurer;

import java.util.Properties;

@Configuration

public class MyBatisMapperScannerConfig {

@Bean

    public MapperScannerConfigurermapperScannerConfigurer() {

MapperScannerConfigurer mapperScannerConfigurer =new MapperScannerConfigurer();

        mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactory");

        mapperScannerConfigurer.setBasePackage("com.txby.web.dao.base");//掃描該路徑下的dao

        Properties properties =new Properties();

        properties.setProperty("mappers", "com.txby.common.mybatis.BaseDao");//通用dao

        properties.setProperty("notEmpty", "false");

        properties.setProperty("IDENTITY", "MYSQL");

        mapperScannerConfigurer.setProperties(properties);

        return mapperScannerConfigurer;

    }

}

通用mapper 的dao


import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import tk.mybatis.spring.mapper.MapperScannerConfigurer;

import java.util.Properties;

@Configuration

public class MyBatisMapperScannerConfig {

@Bean

    public MapperScannerConfigurermapperScannerConfigurer() {

MapperScannerConfigurer mapperScannerConfigurer =new MapperScannerConfigurer();

        mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactory");

        mapperScannerConfigurer.setBasePackage("com.txby.web.dao.base");//掃描該路徑下的dao

        Properties properties =new Properties();

        properties.setProperty("mappers", "com.txby.common.mybatis.BaseDao");//通用dao

        properties.setProperty("notEmpty", "false");

        properties.setProperty("IDENTITY", "MYSQL");

        mapperScannerConfigurer.setProperties(properties);

        return mapperScannerConfigurer;

    }

}

UserDao繼承通用dao 就可以像JPA一樣調(diào)用封裝好的方法了


@Mapper

public interface UserDaoextends BaseDao {

}

'''

下面service的調(diào)用

'''

@Service

public class UserService {

@Autowired

    private UserDaouserDao;

    @TargetDataSource(name ="test")

public UsergetUser(int id) {

return userDao.selectByPrimaryKey(id);

    }

public ListgetUsers() {

return userDao.selectAll();

    }

}

'''
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市壶冒,隨后出現(xiàn)的幾起案子缕题,更是在濱河造成了極大的恐慌,老刑警劉巖胖腾,帶你破解...
    沈念sama閱讀 222,252評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件烟零,死亡現(xiàn)場離奇詭異,居然都是意外死亡胸嘁,警方通過查閱死者的電腦和手機瓶摆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來性宏,“玉大人群井,你說我怎么就攤上這事『潦ぃ” “怎么了书斜?”我有些...
    開封第一講書人閱讀 168,814評論 0 361
  • 文/不壞的土叔 我叫張陵诬辈,是天一觀的道長。 經(jīng)常有香客問我荐吉,道長焙糟,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,869評論 1 299
  • 正文 為了忘掉前任样屠,我火速辦了婚禮穿撮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘痪欲。我一直安慰自己悦穿,他們只是感情好,可當我...
    茶點故事閱讀 68,888評論 6 398
  • 文/花漫 我一把揭開白布业踢。 她就那樣靜靜地躺著栗柒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪知举。 梳的紋絲不亂的頭發(fā)上瞬沦,一...
    開封第一講書人閱讀 52,475評論 1 312
  • 那天,我揣著相機與錄音雇锡,去河邊找鬼逛钻。 笑死,一個胖子當著我的面吹牛锰提,可吹牛的內(nèi)容都是我干的绣的。 我是一名探鬼主播,決...
    沈念sama閱讀 41,010評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼欲账,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了芭概?” 一聲冷哼從身側(cè)響起赛不,我...
    開封第一講書人閱讀 39,924評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎罢洲,沒想到半個月后踢故,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,469評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡惹苗,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,552評論 3 342
  • 正文 我和宋清朗相戀三年殿较,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片桩蓉。...
    茶點故事閱讀 40,680評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡淋纲,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出院究,到底是詐尸還是另有隱情洽瞬,我是刑警寧澤本涕,帶...
    沈念sama閱讀 36,362評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站伙窃,受9級特大地震影響菩颖,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜为障,卻給世界環(huán)境...
    茶點故事閱讀 42,037評論 3 335
  • 文/蒙蒙 一晦闰、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧鳍怨,春花似錦呻右、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至确徙,卻和暖如春醒串,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背鄙皇。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評論 1 274
  • 我被黑心中介騙來泰國打工芜赌, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人伴逸。 一個月前我還...
    沈念sama閱讀 49,099評論 3 378
  • 正文 我出身青樓缠沈,卻偏偏與公主長得像,于是被迫代替她去往敵國和親错蝴。 傳聞我的和親對象是個殘疾皇子洲愤,可洞房花燭夜當晚...
    茶點故事閱讀 45,691評論 2 361

推薦閱讀更多精彩內(nèi)容