日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 运维知识 > windows >内容正文

windows

自定义springboot-starter 动态数据源

發(fā)布時(shí)間:2023/11/26 windows 47 coder
生活随笔 收集整理的這篇文章主要介紹了 自定义springboot-starter 动态数据源 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

自定義springboot-starter 動(dòng)態(tài)數(shù)據(jù)源

如果使用的是spring或springboot框架,spring提供了一個(gè)實(shí)現(xiàn)動(dòng)態(tài)數(shù)據(jù)源的一個(gè)抽象類AbstractRoutingDataSource

當(dāng)我們實(shí)現(xiàn)這個(gè)類后需要實(shí)現(xiàn)一個(gè)方法

@Override
protected Object determineCurrentLookupKey() {

}

spring獲取連接代碼最終會(huì)走到AbstractRoutingDataSource類中的determineTargetDataSource方法

protected DataSource determineTargetDataSource() {
    Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
    //主要這一段
    Object lookupKey = determineCurrentLookupKey();
    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 + "]");
    }
    return dataSource;
}

通過(guò)this.resolvedDataSources.get(lookupKey);來(lái)獲取一個(gè)dataSource之后才能獲取連接

resolvedDataSources是AbstractRoutingDataSource類中的一個(gè)map類型的變量,里面的數(shù)據(jù)是在afterPropertiesSet方法時(shí)從targetDataSources獲取的

@Override
public void afterPropertiesSet() {
  ....
    this.resolvedDataSources = CollectionUtils.newHashMap(this.targetDataSources.size());
  ....
}

也就是說(shuō)通過(guò)設(shè)置map的k-v,再通過(guò)determineCurrentLookupKey方法返回對(duì)應(yīng)的key,就可以進(jìn)行數(shù)據(jù)源的切換

首先創(chuàng)建一個(gè)配置類用來(lái)保存每個(gè)數(shù)據(jù)源的信息

@Component(value = "dynamicDataSourceConfig")
@ConfigurationProperties(prefix = "dynamic")
public class DynamicDataSourceConfig{

    private Map<String, DataSourceProperties> dataSources;

    public Map<String, DataSourceProperties> getDataSources() {
        return dataSources;
    }

    public void setDataSources(Map<String, DataSourceProperties> dataSources) {
        this.dataSources = dataSources;
    }

}

之后創(chuàng)建一個(gè)ThreadLocal持有類來(lái)保存每個(gè)線程需要的數(shù)據(jù)源

public class DynamicContextHolder {

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

    public static void choose(String dbName) {
        CONTEXT_HOLDER.set(dbName);
    }

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

    public static void clear() {
        CONTEXT_HOLDER.remove();
    }
}

最后創(chuàng)建一個(gè)繼承AbstractRoutingDataSource的子類,功能就完成了

@Component
public class RoutingDataSource extends AbstractRoutingDataSource {


    public RoutingDataSource(DynamicDataSourceConfig dynamicDataSourceConfig) {
        HashMap<Object, Object> routingDataSourceMap = parseConfig(dynamicDataSourceConfig);
        this.setTargetDataSources(routingDataSourceMap);
    }

    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicContextHolder.get();
    }

    @Override
    public void setDefaultTargetDataSource(Object defaultTargetDataSource) {
        super.setDefaultTargetDataSource(defaultTargetDataSource);
    }

    private HashMap<Object, Object> parseConfig(DynamicDataSourceConfig config) {
        Map<String, DataSourceProperties> map = config.getDataSources();
    
        HashMap<Object, Object> routingDataSourceMap = new HashMap<>();
        for (Map.Entry<String, DataSourceProperties> entry : map.entrySet()) {
            String dataSourceKey = entry.getKey();
            DataSourceProperties sourceProperties = entry.getValue();

            String driverClassName = sourceProperties.getDriverClassName();
            String url = sourceProperties.getUrl();
            String username = sourceProperties.getUsername();
            String password = sourceProperties.getPassword();

            Class<Driver> driverClass;
            try {
                driverClass = (Class<Driver>) Class.forName(driverClassName);
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
            try {
                SimpleDriverDataSource source = new SimpleDriverDataSource(
                        driverClass.newInstance(),
                        url,
                        username,
                        password);
                routingDataSourceMap.put(dataSourceKey, source);
            } catch (InstantiationException | IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        }
        return routingDataSourceMap;
    }
}

首先是將數(shù)據(jù)庫(kù)連接配置刪除掉,修改為如下代碼

dynamic:
  default-data-source-key: master
  data-sources:
    master:
      url: jdbc:mysql://127.0.0.1/master_data_source?characterEncoding=UTF8&serverTimezone=Asia/Shanghai
      username: root
      password: root
      driver-class-name: com.mysql.cj.jdbc.Driver
    slave:
      url: jdbc:mysql://127.0.0.1/slave_data_source?characterEncoding=UTF8&serverTimezone=Asia/Shanghai
      username: root
      password: root
      driver-class-name: com.mysql.cj.jdbc.Driver

測(cè)試代碼

@GetMapping("master")
public List<User> master() {
    DynamicContextHolder.choose("master");
    System.out.println(userService.list());
    return null;
}

@GetMapping("slave")
public List<User> slave() {
    DynamicContextHolder.choose("slave");
    System.out.println(userService.list());
    return null;
}

結(jié)果

[User{id=1, name=master_user_1}, User{id=2, name=master_user_2}]
[User{id=1, name=slave_user_1}, User{id=2, name=slave_user_2}]

功能就算完成了,只需要在方法調(diào)用前指定使用的數(shù)據(jù)源即可,也可以給加個(gè)aop,更方便點(diǎn)

然后我就想給他整一個(gè)starter,starter相比代碼中直接寫(xiě)會(huì)有兩個(gè)問(wèn)題,一個(gè)是spring如何將外部的類添加到容器中,另一個(gè)是容器掃描的順序

第一個(gè):在starter項(xiàng)目中resources文件夾下添加META-INF/spring.factories文件

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
work.jame.dynamic.config.DynamicDataSourceConfig

這個(gè)文件會(huì)被springboot掃描,之后去加載里面指定的類,我們有一個(gè)類能被springboot加載到后其他的也都不是問(wèn)題了

@Order(Ordered.HIGHEST_PRECEDENCE)
@Component(value = "dynamicDataSourceConfig")
//這個(gè)注解必須要加,否則先去加載spring默認(rèn)的數(shù)據(jù)源了
@AutoConfigureBefore(DataSourceAutoConfiguration.class)
//加載其他類的類
@Import(AutoHandler.class)
@ConfigurationProperties(prefix = "dynamic")
public class DynamicDataSourceConfig {

    private Map<String, DataSourceProperties> dataSources;

    private String defaultDataSourceKey = "master";


}
@Configuration
public class AutoHandler {

    @Autowired
    private DynamicDataSourceConfig config;

    @Bean
    public DataSource abstractRoutingDataSource() {
        return new RoutingDataSource(config);
    }

    //使用aop掃描方法注解,方便使用
    @Bean
    public Advisor dynamicDataSourceAnnotationAdvisor() {
        DynamicDataSourceAnnotationInterceptor interceptor = new 
            DynamicDataSourceAnnotationInterceptor(config.getDefaultDataSourceKey());
        DynamicDataSourceAnnotationAdvisor advisor = new DynamicDataSourceAnnotationAdvisor(interceptor);
        advisor.setOrder(Ordered.HIGHEST_PRECEDENCE);
        return advisor;
    }
}

代碼都在git上了,功能不難

https://gitee.com/sunankang/dynamic-data-source/tree/master

下面說(shuō)說(shuō)寫(xiě)這個(gè)過(guò)程中遇到的問(wèn)題和解決辦法吧

第一個(gè)就是我把代碼挪到starter后,啟動(dòng)項(xiàng)目啟動(dòng)一直報(bào)

我一看,經(jīng)典的導(dǎo)入了連數(shù)據(jù)庫(kù)的配置,但是沒(méi)配spring-datasource下url的錯(cuò)誤,但是我就是不要配url,賬號(hào)密碼,用自定義的動(dòng)態(tài)配置,我沒(méi)有把代碼抽出去之前還是可以用的,抽出去之后就不行了,很納悶,看容器中也有自定義的動(dòng)態(tài)數(shù)據(jù)源的類,后來(lái)我一想,之前見(jiàn)到過(guò)一個(gè)動(dòng)態(tài)數(shù)據(jù)源的開(kāi)源框架,和mybatis-plus是一個(gè)組織的,然后就去下載了,抄襲借鑒下別人是怎么做的

打開(kāi)之后根本不知道從哪開(kāi)始看,類太多了,模塊也很多,然后我git切換到了一個(gè)比較早的版本,類還比較少

這個(gè)注解意思是在指定的類之前進(jìn)行注入,然后就去看了下這個(gè)DataSourceAutoCoonfiguration類

這個(gè)注解是當(dāng)容器中沒(méi)有DataSource類型的bean時(shí),才執(zhí)行下面的這個(gè)方法,這個(gè)方法上的@Import導(dǎo)入了DataSourceConfiguration.Hikari.class,springboot默認(rèn)的數(shù)據(jù)池就是Hikari,到這里其實(shí)已經(jīng)大概明白了,當(dāng)前項(xiàng)目加載bean和外部掃出的bean執(zhí)行順序應(yīng)該是不一樣的,所以在項(xiàng)目中直接寫(xiě)沒(méi)問(wèn)題,抽出個(gè)starter就報(bào)錯(cuò)了

總結(jié)

以上是生活随笔為你收集整理的自定义springboot-starter 动态数据源的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。