天天看點

多資料源配置-使用spring配置多個資料源實作讀寫分離

我們在很多的項目中經常會有用到多個資料源。比如資料庫讀寫分離,讀操作都去從庫裡讀,寫操作都往主庫裡寫。那麼這裡主庫和從庫就是兩個不同的資料源。再比如要做兩個資料庫之間的資料轉換,從一個資料庫讀取資料寫到另一個資料庫中,等等這些情況都需要系統使用兩個或多個資料源。那麼該如何配置多個資料源呢?我這裡使用的是spring管理,資料庫連接配接池使用的是阿裡的druid。具體步驟如下:

1.修改spring的配置檔案, 配置多個不同的資料源。這裡不同的資料源就是由spring維護多個DruidDataSource的執行個體,每個執行個體都指定不同的資料庫連結資訊。配置如下:

<!-- 配置多資料源 ,druid -->
    <bean id="masterDataSource" class="com.alibaba.druid.pool.DruidDataSource"
        destroy-method="close">
 
        <property name="url" value="${jdbc.master.url}" />
        <property name="username" value="${jdbc.master.username}" />
        <property name="password" value="${jdbc.master.password}" />
        <property name="driverClassName" value="${jdbc.master.driver}" />
 
        <!-- 最大連接配接池數量 -->
        <property name="maxActive" value="${master.maxActive}" />
        <!-- 擷取連接配接時最大等待時間,機關毫秒。 配置了maxWait之後,預設啟用公平鎖,并發效率會有所下降,如果需要可以通過配置useUnfairLock屬性為true使用非公平鎖。 -->
        <property name="maxWait" value="60000" />
 
        <!-- 建議配置為true,不影響性能,并且保證安全性。 申請連接配接的時候檢測,如果空閑時間大于timeBetweenEvictionRunsMillis,執行validationQuery檢測連接配接是否有效。 -->
        <property name="testWhileIdle" value="true"></property>
 
        <property name="filters" value="stat"></property>
    </bean>
 
    <!-- 配置多資料源 ,druid -->
    <bean id="slaveDataSource" class="com.alibaba.druid.pool.DruidDataSource"
        destroy-method="close">
 
        <property name="url" value="${jdbc.slave1.url}" />
        <property name="username" value="${jdbc.slave1.username}" />
        <property name="password" value="${jdbc.slave1.password}" />
        <property name="driverClassName" value="${jdbc.slave1.driver}" />
 
        <!-- 最大連接配接池數量 -->
        <property name="maxActive" value="${slave1.maxActive}" />
        <!-- 擷取連接配接時最大等待時間,機關毫秒。 配置了maxWait之後,預設啟用公平鎖,并發效率會有所下降,如果需要可以通過配置useUnfairLock屬性為true使用非公平鎖。 -->
        <property name="maxWait" value="60000" />
 
        <!-- 建議配置為true,不影響性能,并且保證安全性。 申請連接配接的時候檢測,如果空閑時間大于timeBetweenEvictionRunsMillis,執行validationQuery檢測連接配接是否有效。 -->
        <property name="testWhileIdle" value="true"></property>
 
        <property name="filters" value="stat"></property>
    </bean>           

我這裡主要配置了兩個資料源,一個是主庫的資料源,名字叫masterDataSource,還有一個是從庫的資料源,名字叫slaveDataSource

2.選擇要使用的資料源。我們有了資料源之後我們要使用哪一個呢,是以這個時候我們需要來定義一個類,指定選擇哪一個資料源,這裡也很簡單,隻要繼承spring給我們提供的AbstractRoutingDataSource類就可以了,這是一個抽象類,需要重寫determineCurrentLookupKey方法,這個方法就是用來選擇哪一個資料源的。

我們先給兩個資料源分别打個标記,比如,讀庫的源就叫slave,寫庫的叫master,在實作這個方法的時候,我們傳回一個字元串'slave' 表示要找讀庫,傳回一個master表示要找主庫。既然要動态的傳回,我們就需要有一個入口能夠設定選擇的标記。在多線程環境下,為了保證資料安全,可以使用ThreadLocal這個類。代碼如下

public class DynamicDataSource extends AbstractRoutingDataSource {
 
    @Override
    protected Object determineCurrentLookupKey() {
        return DbContextHolder.getDataSource();
    }
 
}           

DbContextHolder的代碼如下

public class DbContextHolder {
 
    public static final String master = "master";
 
    public static final String slave = "slave";
 
    private static ThreadLocal<String> ds = new ThreadLocal<>();
 
    public static void setDataSource (String dataSource) {
        ds.set(dataSource);
    }
 
    public static String getDataSource() {
        return ds.get();
    }
 
}           

3.繼續配置檔案,不管我們配置了幾個資料源,同一時刻生效的隻有一個,于是我們要通過DynamicDataSource這個類來告訴spring我們要使用哪個資料源

<bean id="dataSource" class="com.hy.base.db.DynamicDataSource">
        <property name="targetDataSources">
            <map key-type="java.lang.String">
                <!-- write -->
                <entry key="master" value-ref="masterDataSource" />
                <!-- read -->
                <entry key="slave" value-ref="slaveDataSource" />
            </map>
        </property>
        <property name="defaultTargetDataSource" ref="masterDataSource" />
    </bean>           

這裡要注意,key就是我們上面說到的标記,需要和DbContextHolder裡的常量配置的相同。

到這一步,我們就可以說配置完成了,代碼中就可以指定資料源,來進行資料庫操作。當然我們為了更加友善,我們可以使用注解的方式來選擇使用哪一個資料源,首先定義注解類

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DataSource {
    String value();
}           

我們希望呢,在方法上打上這個注解,當這個方法執行前,我們就調用setDataSource方法來設定注解中指定的值,這樣也就不需要手動設定了。下面是定義切面

public class DataSourceAspect {
 
    public void before(JoinPoint point)
    {
//        System.out.println("--------------------------");
        Object target = point.getTarget();
        String method = point.getSignature().getName();
 
        Class<?>[] classz = target.getClass().getInterfaces();
 
        Class<?>[] parameterTypes = ((MethodSignature) point.getSignature())
                .getMethod().getParameterTypes();
        try {
            Method m = classz[0].getMethod(method, parameterTypes);
            if (m != null && m.isAnnotationPresent(DataSource.class)) {
                DataSource data = m
                        .getAnnotation(DataSource.class);
                DbContextHolder.setDataSource(data.value());
                System.out.println(data.value());
            }
 
        } catch (Exception e) {
//            e.printStackTrace();
        }
    }
}           

下面對切面進行配置

<aop:aspectj-autoproxy> </aop:aspectj-autoproxy>
    <bean id="manyDataSourceAspect" class="com.hy.base.db.DataSourceAspect" />
    <aop:config>
        <aop:aspect id="c" ref="manyDataSourceAspect">
            <aop:pointcut id="tx1"
                expression="execution(* *(..))" />
            <aop:before pointcut-ref="tx1" method="before" />
        </aop:aspect>
    </aop:config>           

OK,到現在就大功告成。下面進行測試。我們使用的是mybatis,這裡的具體配置就不給大家貼了。執行兩個操作,分别針對兩個庫,如果兩個庫都能正常的響應,就說明我們的配置成功了。下面的例子就是兩個接口,一個去找主庫,一個去找從庫,分别查詢在某個資料庫中唯一的一張表,主庫的t1表隻有主庫有,從庫的t2表隻有從庫有,這樣如果都能查出來資料,說名兩個庫都是可以連接配接的。下面是具體的代碼。

1.mapper,兩個接口,一個使用主庫,一個使用從庫

public interface AppMapper {

    @DataSource("master")
    List<Map<String, Object>> executeQuery(@Param("sql")String sql);

    @DataSource("slave")
    List<Map<String, Object>> executeQuery2(@Param("sql")String sql);

}           

2.測試方法     

@Test
    public void testDatasource() throws Exception {

        ApplicationContext ac = new ClassPathXmlApplicationContext("classpath:spring/applicationContext-*.xml");

        AppMapper mapper = (AppMapper) ac.getBean("appMapper");
        String sql = "select * from t1 limit 0,10";
        System.out.println(mapper.executeQuery(sql));

        sql = "select * from t2";

        System.out.println(mapper.executeQuery2(sql));

    }           

這兩個表在主庫和從庫都是唯一的。我們發現都能查出資料,說明我們配置正确了。當然了,這裡資料庫并沒有做主從。

--------------------- 

作者:jhappyfly 

來源:CSDN 

原文:https://blog.csdn.net/king_kgh/article/details/75043307 

版權聲明:本文為部落客原創文章,轉載請附上博文連結!