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 值。