(七)spring+druid多數據源配置


druid多數據源配置

一、druid簡介

  Druid首先是一個數據庫連接池,但它不僅僅是一個數據庫連接池,它還包含一個ProxyDriver,一系列內置的JDBC組件庫,一個SQL Parser。

  Druid是目前最好的數據庫連接池,在功能、性能、擴展性方面,都超過其他數據庫連接池,包括DBCP、C3P0、BoneCP、Proxool、JBoss DataSource。Druid已經在阿里巴巴部署了超過600個應用,經過一年多生產環境大規模部署的嚴苛考驗。

  Druid 是目前比較流行的高性能的,分布式列存儲的OLAP框架(具體來說是MOLAP)。它有如下幾個特點:

  一. 亞秒級查詢

       druid提供了快速的聚合能力以及亞秒級的OLAP查詢能力,多租戶的設計,是面向用戶分析應用的理想方式。

  二.實時數據注入

       druid支持流數據的注入,並提供了數據的事件驅動,保證在實時和離線環境下事件的實效性和統一性

  三.可擴展的PB級存儲

       druid集群可以很方便的擴容到PB的數據量,每秒百萬級別的數據注入。即便在加大數據規模的情況下,也能保證時其效性

  四.多環境部署

       druid既可以運行在商業的硬件上,也可以運行在雲上。它可以從多種數據系統中注入數據,包括hadoop,spark,kafka,storm和samza等

  五.豐富的社區

二、druid多數據源使用

  1.直接配置

  配置兩個 dataSource,兩個sqlSessionFactory,兩個transactionManager,以及關鍵的地方在於MapperScannerConfigurer 的配置——使用sqlSessionFactoryBeanName屬性,注入不同的sqlSessionFactory的名稱,這樣就為不同的數據庫對應的 mapper 接口注入了對應的 sql於master-slave類型的多數據源配置而言不太適應,不支持分布式事務

  2.基於AbstractRoutingDataSource和AOP的多數據源配置

  我們自己定義一個DataSource類ThreadLocalRountingDataSource,來繼承AbstractRoutingDataSource,然后在配置文件中向ThreadLocalRountingDataSource注入 master 和 slave 的數據源,然后通過 AOP 來靈活配置。

  applicationContext.xml

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <beans xmlns="http://www.springframework.org/schema/beans"
 3     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4     xmlns:aop="http://www.springframework.org/schema/aop"
 5     xmlns:context="http://www.springframework.org/schema/context"
 6     xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
 7         http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
 8         http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd>
 9     
10     <!-- 啟動spring注解 -->
11     <context:annotation-config/>
12     <!-- 掃描注解所在的包 -->
13     <context:component-scan base-package="com.example"/>
14     <!-- 啟動aop注解 -->
15     <aop:aspectj-autoproxy proxy-target-class="true"/> 
16        
17        <!-- 引入屬性文件 -->
18     <context:property-placeholder location="classpath:jdbc.properties"/>
19   <!--   <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
20         <property name="driverClassName" value="${driver}"/>
21         <property name="url" value="${url}"/>
22     </bean> 
23      -->
24      <!-- 配置數據源master -->
25      <bean id="dataSourceMaster" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">   
26         <property name="driverClassName" value="${driver}"/>
27         <property name="url" value="${url}" />  
28         <!-- 初始化連接大小 -->
29         <property name="initialSize" value="0" />  
30        <!-- 連接池最大使用連接數量 -->
31         <property name="maxActive" value="20" />  
32         <!-- 連接池最小空閑 -->
33         <property name="minIdle" value="1" />  
34         <!-- 連接池最大空閑 -->
35         <property name="maxIdle" value="20" />  
36         <!-- 獲取連接最大等待時間 -->
37         <property name="maxWait" value="60000" />  
38     </bean>  
39     <!-- 配置數據源master -->
40      <bean id="dataSourceSlave" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">   
41         <property name="driverClassName" value="${driver}"/>
42         <property name="url" value="${url_slave}" />  
43         <!-- 初始化連接大小 -->
44         <property name="initialSize" value="0" />  
45        <!-- 連接池最大使用連接數量 -->
46         <property name="maxActive" value="20" />  
47         <!-- 連接池最小空閑 -->
48         <property name="minIdle" value="0" />  
49         <!-- 連接池最大空閑 -->
50         <property name="maxIdle" value="20" />  
51         <!-- 獲取連接最大等待時間 -->
52         <property name="maxWait" value="60000" />  
53     </bean>  
54      <bean id="dataSource" class="com.example.util.ThreadLocalRountingDataSource">
55          <property name="targetDataSources">
56              <map key-type="com.example.enums.DataSources">
57                  <entry key="MASTER" value-ref="dataSourceMaster" />
58                  <entry key="SLAVE" value-ref="dataSourceSlave"/>
59              </map>
60          </property>
61          <property name="defaultTargetDataSource" ref="dataSourceMaster"></property>
62         
63     </bean> 
64     
65     <!-- 配置SQLSessionFactory -->
66     <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
67         <property name="dataSource" ref="dataSource"/>  
68         <property name="configLocation" value="classpath:mybatis-config.xml"></property> 
69         <!-- 加載映射文件 -->
70         <property name="mapperLocations" value="classpath*:/com/example/dao/*Mapper.xml"></property>  
71     </bean>
72     
73     <!-- 接口方式 -->
74     <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
75         <property name="basePackage" value="com.example.dao"></property>
76         <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
77     </bean> 
78   
79 </beans>

  1.定義一個DataSource枚舉類

//定義一個enum來表示不同的數據源
public enum DataSources {
    MASTER,SLAVE
}

  2.ThreadLocalRountingDataSource,繼承AbstractRoutingDataSource

public class ThreadLocalRountingDataSource extends AbstractRoutingDataSource{

    @Override
    protected Object determineCurrentLookupKey() {
        // TODO Auto-generated method stub
        return DataSourceTypeManager.get();
    }

}

  3.DataSourceTypeManager

//通過 TheadLocal 來保存每個線程選擇哪個數據源的標志(key)

public class DataSourceTypeManager {
    
    private static final ThreadLocal<DataSources> dataSourceTypes=new ThreadLocal<>();
    
    public static DataSources get(){
        return dataSourceTypes.get();
    }
    
    public static void set(DataSources dataSourceType){
        dataSourceTypes.set(dataSourceType);
    }
    
    public static void reset(){
        dataSourceTypes.set(DataSources.MASTER);
    }
}

  4.aop管理

@Aspect
@Component
public class DataSourceInterceptor {

    @Pointcut("execution(public * com.example.service..*.selectByPrimaryKey(..))")
    public void dataSourceMaster(){
        
    };
    
    @Before("dataSourceMaster()")
    public void before(JoinPoint jp){
        DataSourceTypeManager.set(DataSources.MASTER);
    }
    //...
    /*這里我們定義了一個 Aspect 類,我們使用 @Before 來在符合
     *  @Pointcut("execution(public * net.aazj.service..*.selectByPrimaryKey(..))") 中的方法被調用之前,
     *  調用 DataSourceTypeManager.set(DataSources.SLAVE) 設置了 key 的類型為 DataSources.MASTER,
     *  所以 dataSource 會根據key=DataSources.MASTER 選擇 dataSourceSlave 這個dataSource。
     *  所以該方法對於的sql語句會在slave數據庫上執行.
     *  我們可以不斷的擴充 DataSourceInterceptor這個 Aspect,在中進行各種各樣的定義,
     *  來為某個service的某個方法指定合適的數據源對應的dataSource。
     *  這樣我們就可以使用 Spring AOP 的強大功能來,十分靈活進行配置了。
     */
}

  當調用selectByPrimaryKey方法的時候會進入切面類中切換數據源,方法調用完畢會把數據源切換回來

三、AbstractRoutingDataSource原理

源碼:

 1 public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean
 2 void afterPropertiesSet() throws Exception; 
 3  
 4     @Override
 5     public void afterPropertiesSet() {
 6         if (this.targetDataSources == null) {
 7             throw new IllegalArgumentException("Property 'targetDataSources' is required");
 8         }
 9         this.resolvedDataSources = new HashMap<Object, DataSource>(this.targetDataSources.size());
10         for (Map.Entry<Object, Object> entry : this.targetDataSources.entrySet()) {
11             Object lookupKey = resolveSpecifiedLookupKey(entry.getKey());
12             DataSource dataSource = resolveSpecifiedDataSource(entry.getValue());
13             this.resolvedDataSources.put(lookupKey, dataSource);
14         }
15         if (this.defaultTargetDataSource != null) {
16             this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource);
17         }
18     }

  targetDataSources 是我們在xml配置文件中注入的 dataSourceMaster 和 dataSourceSlave. afterPropertiesSet方法就是使用注入的dataSourceMaster 和 dataSourceSlave來構造一個HashMap——resolvedDataSources。方便后面根據 key 從該map 中取得對應的dataSource。

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;
}

  Object lookupKey = determineCurrentLookupKey(); 該方法是我們實現的,在其中獲取ThreadLocal中保存的 key 值。(見上述步驟2)獲得了key之后,在從afterPropertiesSet()(InitializingBean中實現的方法)中初始化好了的resolvedDataSources這個map中獲得key對應的dataSource。而ThreadLocal中保存 key 值。




免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM