我們在很多的項目中經常會有用到多個資料源。比如資料庫讀寫分離,讀操作都去從庫裡讀,寫操作都往主庫裡寫。那麼這裡主庫和從庫就是兩個不同的資料源。再比如要做兩個資料庫之間的資料轉換,從一個資料庫讀取資料寫到另一個資料庫中,等等這些情況都需要系統使用兩個或多個資料源。那麼該如何配置多個資料源呢?我這裡使用的是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
版權聲明:本文為部落客原創文章,轉載請附上博文連結!