java項目配置多數據源

有些web應用需要連接多個數據源,本文講解一下如何使用多個數據源,大題思路是這樣的,系統啓動的時候創建多個數據源,然後具體執行sql的時候去切換數據源執行對應的sql。如何切換數據源呢?spring提供了一個AbstractRoutingDataSource抽象類,只要繼承這個類就可以了,這個類需要設置多個數據源,每個數據源有一個key對應,繼承這個類必須實現determineCurrentLookupKey()方法,這個方法返回一個Object值,這個值應該是數據源的key,執行sql的時候會調用這個方法 獲取key,然後根據這個key獲取到的數據源執行sql。下面看具體的例子。
前面說了determineCurrentLookupKey()方法的返回值決定,選擇什麼數據庫,方法執行的時候如何動態設置返回值呢?爲了不影響其他線程的使用,使用線程本地變量是最好的,這樣只會影響當前線程。看如下類

public class DataSourceRouter {
    //默認數據源的key
    public final static String DEFAULT = "default";
    //數據源1的key
    public final static String KEY_ONE = "key_one";
    private final static ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> DEFAULT);

    /**
     * 獲取線程本地變量
     *
     * @return
     */
    static String getCurrentDataSourceKey() {
        return threadLocal.get();
    }

    /**
     * @param key      數據源對應的key
     * @param supplier sql執行的方法
     * @param <T>
     * @return sql執行的結果
     */
    public static <T> T doWithKey(String key, Supplier<T> supplier) {
        threadLocal.set(key);
        T t = supplier.get();
        threadLocal.set(DEFAULT);
        return t;
    }
}

這裏通過doWithKey執行sql,通過determineCurrentLookupKey動態獲取當前數據源。
下面來實現AbstractRoutingDataSource

public class DynamicDataSource extends AbstractRoutingDataSource {

    @Autowired
    ApplicationContext applicationContext;

    /**
     * 是否在創建數據源時,立即初始化連接
     */
    private boolean initConnectionsOnCreate = false;

    /**
     * 返回配置的數據源的key,這樣下面執行的sql就是使用該數據源的
     *
     * @return
     */
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceRouter.getCurrentDataSourceKey();
    }

    public void init() {
        DruidDataSource defaultTargetDataSource = applicationContext.getBean("defaultDataSource", DruidDataSource.class);
        DruidDataSource dataSource1 = applicationContext.getBean("dataSourceTemplate", DruidDataSource.class);
        dataSource1.setUrl("jdbc:mysql://localhost:3306/social?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC");
        dataSource1.setUsername("root");
        dataSource1.setPassword("998973");
        if (initConnectionsOnCreate) {
            try {
                defaultTargetDataSource.init();
                dataSource1.init();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
        Map<Object, Object> map = new HashMap<>();
        map.put(DataSourceRouter.DEFAULT, defaultTargetDataSource);
        map.put(DataSourceRouter.KEY_ONE, dataSource1);
        setTargetDataSources(map);
        //默認先執行配置文件中的targetDataSources屬性的數據源,這裏設置的數據源不會生效,必須調用afterPropertiesSet
        afterPropertiesSet();
    }

    public void setInitConnectionsOnCreate(boolean initConnectionsOnCreate) {
        this.initConnectionsOnCreate = initConnectionsOnCreate;
    }
}

這裏說明了,配置文件中配置的targetDataSources是必配的,對象創建的時候會讀取這個配置然後調用afterPropertiesSet加載數據源,因爲我們這裏的數據源可能通過數據庫等其他途徑獲取,所以沒有寫在配置文件中,這裏需要手動調用一下afterPropertiesSet重新設置一下數據源。
配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!-- 數據源模板,動態增加數據源時需要用到,scope是prototype,非單例對象 -->
    <bean id="dataSourceTemplate" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="filters" value="stat"/>
        <property name="maxActive" value="20"/>
        <property name="initialSize" value="1"/>
        <property name="maxWait" value="60000"/>
        <property name="minIdle" value="1"/>
        <property name="timeBetweenEvictionRunsMillis" value="60000"/>
        <property name="minEvictableIdleTimeMillis" value="300000"/>
        <property name="testWhileIdle" value="true"/>
        <property name="testOnBorrow" value="false"/>
        <property name="testOnReturn" value="false"/>
        <property name="poolPreparedStatements" value="true"/>
        <property name="maxOpenPreparedStatements" value="20"/>
        <property name="asyncInit" value="true"/>
    </bean>

    <bean id="defaultDataSource" parent="dataSourceTemplate">
        <property name="url"
                  value="jdbc:mysql://localhost:3306/easytour?useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=UTC"/>
        <property name="username" value="root"/>
        <property name="password" value="998973"/>
    </bean>

    <!--統一的dataSource-->
    <bean id="dynamicDataSource" class="com.zhan.design.config.datasource.DynamicDataSource" init-method="init">
        <!-- 設置true時,隨便一個key,找不到就走默認數據源,很可能帶來不好的效果 -->
        <property name="lenientFallback" value="false"/>
        <property name="targetDataSources">
            <map></map>
        </property>
        <!--設置默認的dataSource-->
        <property name="defaultTargetDataSource" ref="defaultDataSource">
        </property>
        <!--是否在創建數據源時,立即初始化連接-->
        <property name="initConnectionsOnCreate" value="true"/>
    </bean>

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dynamicDataSource"/>
        <!--掃描mybatis配置文件,在哪裏可以做細配置-->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <!--掃描映射文件所在目錄-->
        <property name="mapperLocations" value="classpath:com/zhan/design/mapper/**/*.xml"/>
    </bean>

    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!--掃描接口的基礎包,會把該包下面的所有接口註冊爲spring的bean-->
        <property name="basePackage" value="com.zhan.design.dao"/>
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    </bean>

    <!--配置spring的事務-->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dynamicDataSource"/>
    </bean>

    <!--開啓事務註解-->
    <tx:annotation-driven transaction-manager="txManager"/>
</beans>

這樣就完成了
下面看下具體使用的例子

    @GetMapping("/test")
    public Map<String, String> test() {
        //通過調用doWithKey就完成對數據源的切換了
        String label1 = DataSourceRouter.doWithKey(DataSourceRouter.DEFAULT, () -> dictionaryService.getByTypeAndKey("1", "1"));
        String label2 = DataSourceRouter.doWithKey(DataSourceRouter.KEY_ONE, () -> dictionaryService.getByTypeAndKey("1", "1"));
        Map<String, String> map = new HashMap<>();
        map.put("default_label", label1);
        map.put("one_label", label2);
        return map;
    }

至此就完成了。

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