日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長(zhǎng)提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請(qǐng)做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢(xún)客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會(huì)員:747

一、概述

      在項(xiàng)目的開(kāi)發(fā)過(guò)程中,遇到了需要從數(shù)據(jù)庫(kù)中動(dòng)態(tài)查詢(xún)新的數(shù)據(jù)源信息并切換到該數(shù)據(jù)源做相應(yīng)的查詢(xún)操作,這樣就產(chǎn)生了動(dòng)態(tài)切換數(shù)據(jù)源的場(chǎng)景。為了能夠靈活地指定具體的數(shù)據(jù)庫(kù),本文基于注解和AOP的方法實(shí)現(xiàn)多數(shù)據(jù)源自動(dòng)切換。在使用過(guò)程中,只需要添加注解就可以使用,簡(jiǎn)單方便。(代碼獲取方式:見(jiàn)文章底部(開(kāi)箱即用))

二、構(gòu)建核心代碼

2.1、AbstractRoutingDataSource構(gòu)建

ackage com.wonders.dynamic;
import org.springframework.beans.factory.InitializingBean;import org.springframework.jdbc.datasource.AbstractDataSource;import org.springframework.jdbc.datasource.lookup.DataSourceLookup;import org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup;import org.springframework.lang.Nullable;import org.springframework.util.Assert;import org.springframework.util.CollectionUtils;
import JAVAx.sql.DataSource;import java.sql.Connection;import java.sql.SQLException;import java.util.Map;
/** * @Description: TODO:抽象類(lèi)AbstractRoutingDataSource,實(shí)現(xiàn)動(dòng)態(tài)數(shù)據(jù)源切換 * @Author: yyalin * @CreateDate: 2023/7/16 14:40 * @Version: V1.0 */public abstract class AbstractRoutingDataSource extends AbstractDataSource        implements InitializingBean {    //目標(biāo)數(shù)據(jù)源map集合,存儲(chǔ)將要切換的多數(shù)據(jù)源bean信息    @Nullable    private Map<Object, Object> targetDataSources;    //未指定數(shù)據(jù)源時(shí)的默認(rèn)數(shù)據(jù)源對(duì)象    @Nullable    private Object defaultTargetDataSource;    private boolean lenientFallback = true;    //數(shù)據(jù)源查找接口,通過(guò)該接口的getDataSource(String dataSourceName)獲取數(shù)據(jù)源信息    private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();    //解析targetDataSources之后的DataSource的map集合    @Nullable    private Map<Object, DataSource> resolvedDataSources;    @Nullable    private DataSource resolvedDefaultDataSource;
    //將targetDataSources的內(nèi)容轉(zhuǎn)化一下放到resolvedDataSources中,將defaultTargetDataSource轉(zhuǎn)為DataSource賦值給resolvedDefaultDataSource    public void afterPropertiesSet() {        //如果目標(biāo)數(shù)據(jù)源為空,會(huì)拋出異常,在系統(tǒng)配置時(shí)應(yīng)至少傳入一個(gè)數(shù)據(jù)源        if (this.targetDataSources == null) {            throw new IllegalArgumentException("Property 'targetDataSources' is required");        } else {            //初始化resolvedDataSources的大小            this.resolvedDataSources = CollectionUtils.newHashMap(this.targetDataSources.size());            //遍歷目標(biāo)數(shù)據(jù)源信息map集合,對(duì)其中的key,value進(jìn)行解析            this.targetDataSources.forEach((key, value) -> {                //resolveSpecifiedLookupKey方法沒(méi)有做任何處理,只是將key繼續(xù)返回                Object lookupKey = this.resolveSpecifiedLookupKey(key);                //將目標(biāo)數(shù)據(jù)源map集合中的value值(德魯伊數(shù)據(jù)源信息)轉(zhuǎn)為DataSource類(lèi)型                DataSource dataSource = this.resolveSpecifiedDataSource(value);                //將解析之后的key,value放入resolvedDataSources集合中                this.resolvedDataSources.put(lookupKey, dataSource);            });            if (this.defaultTargetDataSource != null) {                //將默認(rèn)目標(biāo)數(shù)據(jù)源信息解析并賦值給resolvedDefaultDataSource                this.resolvedDefaultDataSource = this.resolveSpecifiedDataSource(this.defaultTargetDataSource);            }
        }    }
    protected Object resolveSpecifiedLookupKey(Object lookupKey) {        return lookupKey;    }
    protected DataSource resolveSpecifiedDataSource(Object dataSource) throws IllegalArgumentException {        if (dataSource instanceof DataSource) {            return (DataSource)dataSource;        } else if (dataSource instanceof String) {            return this.dataSourceLookup.getDataSource((String)dataSource);        } else {            throw new IllegalArgumentException("Illegal data source value - only [javax.sql.DataSource] and String supported: " + dataSource);        }    }
    //因?yàn)锳bstractRoutingDataSource繼承AbstractDataSource,而AbstractDataSource實(shí)現(xiàn)了DataSource接口,所有存在獲取數(shù)據(jù)源連接的方法    public Connection getConnection() throws SQLException {        return this.determ.NETargetDataSource().getConnection();    }
    public Connection getConnection(String username, String password) throws SQLException {        return this.determineTargetDataSource().getConnection(username, password);    }
    protected DataSource determineTargetDataSource() {        Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");        //調(diào)用實(shí)現(xiàn)類(lèi)中重寫(xiě)的determineCurrentLookupKey方法拿到當(dāng)前線程要使用的數(shù)據(jù)源的名稱(chēng)        Object lookupKey = this.determineCurrentLookupKey();        //去解析之后的數(shù)據(jù)源信息集合中查詢(xún)?cè)摂?shù)據(jù)源是否存在,如果沒(méi)有拿到則使用默認(rèn)數(shù)據(jù)源resolvedDefaultDataSource        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();}

2.2、DynamicDataSource類(lèi)

** * @Description: TODO:動(dòng)態(tài)數(shù)據(jù)源 * @Author: yyalin * @CreateDate: 2023/7/16 14:46 * @Version: V1.0 *//** * * 調(diào)用AddDefineDataSource組件的addDefineDynamicDataSource()方法,獲取原來(lái)targetdatasources的map, * 并將新的數(shù)據(jù)源信息添加到map中,并替換targetdatasources中的map * 切換數(shù)據(jù)源時(shí)可以使用@DataSource(value = "數(shù)據(jù)源名稱(chēng)"),或者DynamicDataSourceContextHolder.setContextKey("數(shù)據(jù)源名稱(chēng)") */@Data@AllArgsConstructor@NoArgsConstructorpublic class DynamicDataSource extends AbstractRoutingDataSource {    //備份所有數(shù)據(jù)源信息,    private Map<Object, Object> defineTargetDataSources;
    /**     * 決定當(dāng)前線程使用哪個(gè)數(shù)據(jù)源     */    @Override    protected Object determineCurrentLookupKey() {        return DynamicDataSourceHolder.getDynamicDataSourceKey();    }
}

2.3、DynamicDataSourceHolder

** * @Description: TODO:數(shù)據(jù)源切換處理 * DynamicDataSourceHolder類(lèi)主要是設(shè)置當(dāng)前線程的數(shù)據(jù)源名稱(chēng), * 移除數(shù)據(jù)源名稱(chēng),以及獲取當(dāng)前數(shù)據(jù)源的名稱(chēng),便于動(dòng)態(tài)切換 * @Author: yyalin * @CreateDate: 2023/7/16 14:51 * @Version: V1.0 */@Slf4jpublic class DynamicDataSourceHolder {    /**     * 保存動(dòng)態(tài)數(shù)據(jù)源名稱(chēng)     */    private static final ThreadLocal<String> DYNAMIC_DATASOURCE_KEY = new ThreadLocal<>();
    /**     * 設(shè)置/切換數(shù)據(jù)源,決定當(dāng)前線程使用哪個(gè)數(shù)據(jù)源     */    public static void setDynamicDataSourceKey(String key){        log.info("數(shù)據(jù)源切換為:{}",key);        DYNAMIC_DATASOURCE_KEY.set(key);    }
    /**     * 獲取動(dòng)態(tài)數(shù)據(jù)源名稱(chēng),默認(rèn)使用mater數(shù)據(jù)源     */    public static String getDynamicDataSourceKey(){        String key = DYNAMIC_DATASOURCE_KEY.get();        return key == null ? DbsConstant.MySQL_db_01 : key;    }
    /**     * 移除當(dāng)前數(shù)據(jù)源     */    public static void removeDynamicDataSourceKey(){        log.info("移除數(shù)據(jù)源:{}",DYNAMIC_DATASOURCE_KEY.get());        DYNAMIC_DATASOURCE_KEY.remove();    }
}

2.4、數(shù)據(jù)源工具類(lèi)

** * @Description: TODO:數(shù)據(jù)源工具類(lèi) * @Author: yyalin * @CreateDate: 2023/7/16 15:00 * @Version: V1.0 */@Slf4j@Componentpublic class DataSourceUtils {    @Resource    DynamicDataSource dynamicDataSource;
    /**     * @Description: 根據(jù)傳遞的數(shù)據(jù)源信息測(cè)試數(shù)據(jù)庫(kù)連接     * @Author zhangyu     */    public DruidDataSource createDataSourceConnection(DataSourceInfo dataSourceInfo) {        DruidDataSource druidDataSource = new DruidDataSource();        druidDataSource.setUrl(dataSourceInfo.getUrl());        druidDataSource.setUsername(dataSourceInfo.getUserName());        druidDataSource.setPassword(dataSourceInfo.getPassword());        druidDataSource.setDriverClassName(dataSourceInfo.getDriverClassName());        druidDataSource.setBreakAfterAcquireFAIlure(true);        druidDataSource.setConnectionErrorRetryAttempts(0);        try {            druidDataSource.getConnection(2000);            log.info("數(shù)據(jù)源連接成功");            return druidDataSource;        } catch (SQLException throwables) {            log.error("數(shù)據(jù)源 {} 連接失敗,用戶(hù)名:{},密碼 {}",dataSourceInfo.getUrl(),dataSourceInfo.getUserName(),dataSourceInfo.getPassword());            return null;        }    }
    /**     * @Description: 將新增的數(shù)據(jù)源加入到備份數(shù)據(jù)源map中     * @Author zhangyu     */    public void addDefineDynamicDataSource(DruidDataSource druidDataSource, String dataSourceName){        Map<Object, Object> defineTargetDataSources = dynamicDataSource.getDefineTargetDataSources();        defineTargetDataSources.put(dataSourceName, druidDataSource);        dynamicDataSource.setTargetDataSources(defineTargetDataSources);        dynamicDataSource.afterPropertiesSet();    }

2.5、DynamicDataSourceConfig

** * @Description: TODO:數(shù)據(jù)源信息配置類(lèi),讀取數(shù)據(jù)源配置信息并注冊(cè)成bean。 * @Author: yyalin * @CreateDate: 2023/7/16 14:54 * @Version: V1.0 */@Configuration@MApperScan("com.wonders.mapper")@Slf4jpublic class DynamicDataSourceConfig {    @Bean(name = DbsConstant.mysql_db_01)    @ConfigurationProperties("spring.datasource.mysqldb01")    public DataSource masterDataSource() {        log.info("數(shù)據(jù)源切換為:{}",DbsConstant.mysql_db_01);        DruidDataSource dataSource = DruidDataSourceBuilder.create().build();        return dataSource;    }
    @Bean(name = DbsConstant.mysql_db_02)    @ConfigurationProperties("spring.datasource.mysqldb02")    public DataSource slaveDataSource() {        log.info("數(shù)據(jù)源切換為:{}",DbsConstant.mysql_db_02);        DruidDataSource dataSource = DruidDataSourceBuilder.create().build();        return dataSource;    }

    @Bean(name = DbsConstant.oracle_db_01)    @ConfigurationProperties("spring.datasource.oracledb01")    public DataSource oracleDataSource() {        log.info("數(shù)據(jù)源切換為oracle:{}",DbsConstant.oracle_db_01);        DruidDataSource dataSource = DruidDataSourceBuilder.create().build();        return dataSource;    }    @Bean    @Primary    public DynamicDataSource dynamicDataSource(){        Map<Object, Object> dataSourceMap = new HashMap<>(3);        dataSourceMap.put(DbsConstant.mysql_db_01,masterDataSource());        dataSourceMap.put(DbsConstant.mysql_db_02,slaveDataSource());        dataSourceMap.put(DbsConstant.oracle_db_01,oracleDataSource());        //設(shè)置動(dòng)態(tài)數(shù)據(jù)源        DynamicDataSource dynamicDataSource = new DynamicDataSource();        dynamicDataSource.setDefaultTargetDataSource(masterDataSource());        dynamicDataSource.setTargetDataSources(dataSourceMap);        //將數(shù)據(jù)源信息備份在defineTargetDataSources中        dynamicDataSource.setDefineTargetDataSources(dataSourceMap);        return dynamicDataSource;    }
}

 

三、測(cè)試代碼

/** * @Description: TODO * @Author: yyalin * @CreateDate: 2023/7/16 15:02 * @Version: V1.0 */@Slf4j@Api(tags="動(dòng)態(tài)切換多數(shù)據(jù)源測(cè)試")@RestControllerpublic class TestController {    @Resource    DataSourceUtils dataSourceUtils;    @Autowired    private StudentMapper studentMapper;    @ApiOperation(value="動(dòng)態(tài)切換多數(shù)據(jù)源測(cè)試", notes="test")    @GetMapping("/test")    public Map<String, Object> dynamicDataSourceTest(String id){        Map<String, Object> map = new HashMap<>();        //1、默認(rèn)庫(kù)中查詢(xún)數(shù)據(jù)        Student student=studentMapper.selectById(id);        map.put("1、默認(rèn)庫(kù)中查詢(xún)到的數(shù)據(jù)",student);        //2、指定庫(kù)中查詢(xún)的數(shù)據(jù)        DynamicDataSourceHolder.setDynamicDataSourceKey(DbsConstant.mysql_db_02);        Student student02=studentMapper.selectById(id);        map.put("2、指定庫(kù)中查詢(xún)的數(shù)據(jù)",student02);        //3、從數(shù)據(jù)庫(kù)獲取連接信息,然后獲取數(shù)據(jù)        //模擬從數(shù)據(jù)庫(kù)中獲取的連接        DataSourceInfo dataSourceInfo = new DataSourceInfo(                "jdbc:mysql://127.0.0.1:3308/test02?useUnicode=true&characterEncoding=utf-8&useSSL=false",                 "root",                "root",                "mysqldb03",                "com.mysql.cj.jdbc.Driver");        map.put("dataSource",dataSourceInfo);        log.info("數(shù)據(jù)源信息:{}",dataSourceInfo);        //測(cè)試數(shù)據(jù)源連接        DruidDataSource druidDataSource = dataSourceUtils.createDataSourceConnection(dataSourceInfo);        if (Objects.nonNull(druidDataSource)){            //將新的數(shù)據(jù)源連接添加到目標(biāo)數(shù)據(jù)源map中            dataSourceUtils.addDefineDynamicDataSource(druidDataSource,dataSourceInfo.getDatasourceKey());            //設(shè)置當(dāng)前線程數(shù)據(jù)源名稱(chēng)-----代碼形式            DynamicDataSourceHolder.setDynamicDataSourceKey(dataSourceInfo.getDatasourceKey());            //在新的數(shù)據(jù)源中查詢(xún)用戶(hù)信息            Student student03=studentMapper.selectById(id);            map.put("3、動(dòng)態(tài)數(shù)據(jù)源查詢(xún)的數(shù)據(jù)",student03);            //關(guān)閉數(shù)據(jù)源連接            druidDataSource.close();        }        //4、指定oracle庫(kù)中查詢(xún)的數(shù)據(jù)        DynamicDataSourceHolder.setDynamicDataSourceKey(DbsConstant.oracle_db_01);        Student student04=studentMapper.selectById(id);        map.put("4、指定oracle庫(kù)中查詢(xún)的數(shù)據(jù)",student04);        return map;    }}

測(cè)試結(jié)果如下:

       從結(jié)果中可以明顯的看出,通過(guò)切換不同的數(shù)據(jù)源,可以從不同的庫(kù)中獲取不同的數(shù)據(jù),包括:常見(jiàn)庫(kù)Mysql、oracle、sqlserver等數(shù)據(jù)庫(kù)相互切換。也可以從數(shù)據(jù)庫(kù)的某張表中獲取連接信息,實(shí)現(xiàn)動(dòng)態(tài)切換數(shù)據(jù)庫(kù)。

四、使用注解方式切換數(shù)據(jù)源

      從上述TestController 中代碼不難看出,若要想切換數(shù)據(jù)源需要在mapper調(diào)用之前調(diào)用:

  • DynamicDataSourceHolder.setDynamicDataSourceKey(DbsConstant.mysql_db_02);

不夠簡(jiǎn)潔優(yōu)雅,所以下面推薦使用注解的方式來(lái)動(dòng)態(tài)進(jìn)行數(shù)據(jù)源的切換。

4.1、創(chuàng)建注解類(lèi)DataSource

/** * @Description: TODO:自定義多數(shù)據(jù)源切換注解 * 優(yōu)先級(jí):先方法,后類(lèi),如果方法覆蓋了類(lèi)上的數(shù)據(jù)源類(lèi)型,以方法的為準(zhǔn),否則以類(lèi)上的為準(zhǔn) * @Author: yyalin * @CreateDate: 2023/7/17 14:00 * @Version: V1.0 */@Target({ ElementType.METHOD, ElementType.TYPE })@Retention(RetentionPolicy.RUNTIME)@Documented@Inheritedpublic @interface DataSource {    //切換數(shù)據(jù)源名稱(chēng),默認(rèn)mysql_db_01    public String value() default DbsConstant.mysql_db_01;}

4.2、創(chuàng)建切面DataSourceAspect類(lèi)

** * @Description: TODO:創(chuàng)建切面DataSourceAspect類(lèi) * @Author: yyalin * @CreateDate: 2023/7/17 14:03 * @Version: V1.0 */@Aspect@Componentpublic class DataSourceAspect {    // 設(shè)置DataSource注解的切點(diǎn)表達(dá)式    @Pointcut("@annotation(com.wonders.dynamic.DataSource)")    public void dynamicDataSourcePointCut(){}
    //環(huán)繞通知    @Around("dynamicDataSourcePointCut()")    public Object around(ProceedingJoinPoint joinPoint) throws Throwable{        String key = getDefineAnnotation(joinPoint).value();        DynamicDataSourceHolder.setDynamicDataSourceKey(key);        try {            return joinPoint.proceed();        } finally {            DynamicDataSourceHolder.removeDynamicDataSourceKey();        }    }    /**     * 功能描述:先判斷方法的注解,后判斷類(lèi)的注解,以方法的注解為準(zhǔn)     * @MethodName: getDefineAnnotation     * @MethodParam: [joinPoint]     * @Return: com.wonders.dynamic.DataSource     * @Author: yyalin     * @CreateDate: 2023/7/17 14:09     */    private DataSource getDefineAnnotation(ProceedingJoinPoint joinPoint){        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();        DataSource dataSourceAnnotation = methodSignature.getMethod().getAnnotation(DataSource.class);        if (Objects.nonNull(methodSignature)) {            return dataSourceAnnotation;        } else {            Class<?> dsClass = joinPoint.getTarget().getClass();            return dsClass.getAnnotation(DataSource.class);        }    }}

4.3、進(jìn)行數(shù)據(jù)源切換

/@Mapper 與 啟動(dòng)類(lèi)的@MapperScan({"com.example.demo.mapper"}) 二選一即可@Repositorypublic interface StudentMapper extends BaseMapper<Student> {    /**     * 功能描述:在mysql_db_01中查詢(xún)數(shù)據(jù)     * @MethodName: findStudentById     * @MethodParam: [id]     * @Return: com.wonders.entity.Student     * @Author: yyalin     * @CreateDate: 2023/7/17 14:20     */    @DataSource(value = DbsConstant.oracle_db_01)    Student findStudentById(String id);}

或在service層

Servicepublic class StudentServiceImpl implements StudentService{    @Autowired    private StudentMapper studentMapper;    //注解加在實(shí)現(xiàn)層才能生效    @DataSource(value = DbsConstant.mysql_db_01)    @Override    public Student findStudentById(String id) {        return studentMapper.selectById(id);    }}

4.3、測(cè)試效果

ApiOperation(value="使用注解方式動(dòng)態(tài)切換多數(shù)據(jù)源", notes="test02")    @GetMapping("/test02")    public Student test02(String id){        Student student=studentMapper.findStudentById(id);        return student;    }

--結(jié)果如下:

分享到:
標(biāo)簽:SpringBoot
用戶(hù)無(wú)頭像

網(wǎng)友整理

注冊(cè)時(shí)間:

網(wǎng)站:5 個(gè)   小程序:0 個(gè)  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會(huì)員

趕快注冊(cè)賬號(hào),推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過(guò)答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫(kù),初中,高中,大學(xué)四六

運(yùn)動(dòng)步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動(dòng)步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績(jī)?cè)u(píng)定2018-06-03

通用課目體育訓(xùn)練成績(jī)?cè)u(píng)定