AbstractRoutingDataSource:提供了动态切换数据源的功能。
public class DynamicDataSourceHolder extends AbstractRoutingDataSource { private static final ThreadLocal<String> dataSourceHolder = new ThreadLocal<String>(); // 可选取slave的keys private List<String> slaveDataSourcesKeys; // 可选取master的keys private List<String> masterDataSourcesKeys; //从数据 private Map<String, DataSource> slavetDataSources; //主数据 private Map<String, DataSource> masterDataSources; /** * 重写这个抽象类是为了获取数据源 * protected DataSource determineTargetDataSource(){ * ...... * Object lookupKey = this.determineCurrentLookupKey(); * DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey); * ..... * } * @return */ @Override protected Object determineCurrentLookupKey() { return dataSourceHolder.get(); } /** * 将配置文件中的主数据源和从数据源加入到spring数据源中 */ @Override public void afterPropertiesSet(){ // 数据检验和合并 logger.debug("开始向spring routing datasource 提供数据源选取"); Map<Object, Object> allDataSources = new HashMap<Object, Object>(); allDataSources.putAll(masterDataSources); if (slavetDataSources != null) { allDataSources.putAll(slavetDataSources); } super.setTargetDataSources(allDataSources); super.afterPropertiesSet(); logger.debug("已经完成向spring routing datasource 提供数据源选取"); } /** * 在加载applicationContent-dataSource配置文件时,将从数据源加载到slavetDataSources中 * @param slavetDataSources */ public void setSlavetDataSources(Map<String, DataSource> slavetDataSources) { if (slavetDataSources == null || slavetDataSources.size() == 0) { return; } logger.debug("提供可选取slave数据源:{}", (Throwable) slavetDataSources.keySet()); this.slavetDataSources = slavetDataSources; slaveDataSourcesKeys = new ArrayList<String>(); for (Map.Entry<String, DataSource> entry : slavetDataSources.entrySet()) { slaveDataSourcesKeys.add(entry.getKey()); } } /** * 在加载applicationContent-dataSource配置文件时,将从数据源加载到slavetDataSources中 * @param masterDataSources */ public void setMasterDataSources(Map<String, DataSource> masterDataSources) { if (masterDataSources == null) { throw new IllegalArgumentException("Property 'masterDataSources' is required"); } logger.debug("提供可选取slave数据源:{}", (Throwable) masterDataSources.keySet()); this.masterDataSources = masterDataSources; masterDataSourcesKeys = new ArrayList<String>(); for (Map.Entry<String, DataSource> entry : masterDataSources.entrySet()) { masterDataSourcesKeys.add(entry.getKey()); } } /** * 标记选取slave数据源 */ public void markSlave() { if (dataSourceHolder.get() != null) { // 从现在的策略来看,不允许标记两次,直接抛异常,优于早发现问题 throw new IllegalArgumentException("当前已有选取数据源,不允许覆盖,已选数据源key:" + dataSourceHolder.get()); } setDataSource(selectFromSlave()); } /** * 标记选取master数据源 */ public void markMaster() { if (dataSourceHolder.get() != null) { // 从现在的策略来看,不允许标记两次,直接抛异常,优于早发现问题 throw new IllegalArgumentException("当前已有选取数据源,不允许覆盖,已选数据源key:" + dataSourceHolder.get()); } setDataSource(selectFromMaster()); } /** * 删除己标记选取的数据源 */ public void markRemove() { dataSourceHolder.remove(); } private void setDataSource(String dataSourceKey) { logger.debug("dataSourceHolder set datasource keys:{}"); dataSourceHolder.set(dataSourceKey); } /** * 是否已经绑定datasource 绑定:true 没绑定:false */ public boolean hasBindedDataSourse() { return dataSourceHolder.get() != null; } private String selectFromSlave() { if (slavetDataSources == null) { logger.debug("提供可选取slave数据源:{},将自动切换从主master选取数据源", (Throwable) slavetDataSources); return selectFromMaster(); } else { return slaveDataSourcesKeys.get(RandomUtils.nextInt(slaveDataSourcesKeys.size())); } } private String selectFromMaster() { String dataSourceKey = masterDataSourcesKeys.get(RandomUtils.nextInt(masterDataSourcesKeys.size())); return dataSourceKey; } }----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
public class DynamicDataSourceAop { @Resource private DynamicDataSourceHolder dataSourceHolder; private String[] writeStartWiths; public Object doAroundMethod(ProceedingJoinPoint pjp) throws Throwable { Object response = null; String method = pjp.getSignature().getName(); boolean hasBinded = false; try { hasBinded = dataSourceHolder.hasBindedDataSourse(); if (!hasBinded) { boolean isWrite = false; for (String writeStartWith : writeStartWiths) { if (method.startsWith(writeStartWith)) { isWrite = true; break; } } if (isWrite) { dataSourceHolder.markMaster(); } else { dataSourceHolder.markSlave(); } } response = pjp.proceed(); } finally { if (!hasBinded) { dataSourceHolder.markRemove(); } } return response; } public void setWriteStartWith(String[] writeStartWith) { this.writeStartWiths = writeStartWith; } }--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
applicationContent-dataSource.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context" xmlns="http://www.springframework.org/schema/beans" xmlns:mongo="http://www.springframework.org/schema/data/mongo" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/data/mongo http://www.springframework.org/schema/data/mongo/spring-mongo.xsd"> <context:component-scan base-package="com.tangCL.core" /> <bean id="master" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <!-- 数据源驱动类可不写,Druid默认会自动根据URL识别DriverClass --> <property name="url" value="${master.jdbc.url}"/> <property name="username" value="${master.jdbc.username}"/> <property name="password" value="${master.jdbc.password}"/> <!-- 配置初始化大小、最小、最大 --> <property name="initialSize" value="3"/> <property name="minIdle" value="3"/> <property name="maxActive" value="60"/> <!-- 配置获取连接等待超时的时间 --> <property name="maxWait" value="60000"/> <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 --> <property name="timeBetweenEvictionRunsMillis" value="60000"/> <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 --> <property name="minEvictableIdleTimeMillis" value="300000"/> <property name="validationQuery" value="SELECT 'x'"/> <property name="testWhileIdle" value="true"/> <property name="testOnBorrow" value="false"/> <property name="testOnReturn" value="false"/> <!-- 打开removeAbandoned功能 --> <property name="removeAbandoned" value="true"/> <!-- 1800秒,也就是30分钟 --> <property name="removeAbandonedTimeout" value="180"/> <!-- 关闭abanded连接时输出错误日志 --> <property name="logAbandoned" value="true"/> <property name="proxyFilters"> <list> <ref bean="slf4j-filter"/> <ref bean="stat-filter"/> </list> </property> <property name="filters" value="stat,config,wall"/> <property name="connectionProperties" value="config.decrypt=true;druid.log.conn=false;druid.log.stmt=false;druid.log.rs=false;druid.log.stmt.executableSql=true;"/> </bean> <bean id="slave" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <!-- 数据源驱动类可不写,Druid默认会自动根据URL识别DriverClass --> <property name="url" value="${slave.jdbc.url}"/> <property name="username" value="${slave.jdbc.username}"/> <property name="password" value="${slave.jdbc.password}"/> <!-- 配置初始化大小、最小、最大 --> <property name="initialSize" value="3"/> <property name="minIdle" value="3"/> <property name="maxActive" value="100"/> <!-- 配置获取连接等待超时的时间 --> <property name="maxWait" value="60000"/> <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 --> <property name="timeBetweenEvictionRunsMillis" value="60000"/> <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 --> <property name="minEvictableIdleTimeMillis" value="300000"/> <property name="validationQuery" value="SELECT 'x'"/> <property name="testWhileIdle" value="true"/> <property name="testOnBorrow" value="false"/> <property name="testOnReturn" value="false"/> <!-- 打开removeAbandoned功能 --> <property name="removeAbandoned" value="true"/> <!-- 1800秒,也就是30分钟 --> <property name="removeAbandonedTimeout" value="180"/> <!-- 关闭abanded连接时输出错误日志 --> <property name="logAbandoned" value="true"/> <property name="proxyFilters"> <list> <ref bean="slf4j-filter"/> <ref bean="stat-filter"/> </list> </property> <property name="filters" value="stat,config,wall"/> <property name="connectionProperties" value="config.decrypt=true;druid.log.conn=false;druid.log.stmt=false;druid.log.rs=false;druid.log.stmt.executableSql=true;"/> </bean> <!-- 自定义数据源 --> <bean id="dynamicDataSourceHolder" class="com.tangCL.utils.jdbc.DynamicDataSourceHolder"> <property name="slavetDataSources"> <map key-type="java.lang.String"> <entry value-ref="slave" key="slave"></entry> </map> </property> <property name="masterDataSources"> <map key-type="java.lang.String"> <entry value-ref="master" key="master"></entry> </map> </property> <property name="defaultTargetDataSource" ref="master"/> </bean> <bean id="slf4j-filter" class="com.alibaba.druid.filter.logging.Slf4jLogFilter"> </bean> <!-- 事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dynamicDataSourceHolder"/> </bean> <bean name="dynamicDataSourceAop" class="com.tangCL.utils.jdbc.DynamicDataSourceAop" scope="singleton"> <property name="writeStartWith"> <array> <value>insert</value> <value>update</value> <value>add</value> <value>save</value> <value>modify</value> <value>delete</value> <value>call</value> <value>change</value> <value>end</value> <value>cancel</value> </array> </property> </bean> <aop:config expose-proxy="true"> <aop:pointcut id="rwPointcut" expression="execution(表达式) or execution(表达式) or execution(表达式)"/> <!-- 通过AOP切面实现读/写库选择 --> <aop:aspect ref="dynamicDataSourceAop"> <aop:around pointcut-ref="rwPointcut" method="doAroundMethod"/> </aop:aspect> </aop:config> <!-- 开启注解事务机制 配置基于注解方式的事务 --> <tx:annotation-driven transaction-manager="transactionManager"/> <!--Spring 声明式配置 --> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="get*" read-only="true"/> <tx:method name="query*" read-only="true"/> <tx:method name="find*" read-only="true"/> <tx:method name="select*" read-only="true"/> <tx:method name="load*" read-only="true"/> <tx:method name="count*" read-only="true"/> <tx:method name="add*" rollback-for="Exception" propagation="REQUIRED"/> <tx:method name="call*" rollback-for="Exception" propagation="REQUIRED"/> <tx:method name="save*" rollback-for="Exception" propagation="REQUIRED"/> <tx:method name="update*" rollback-for="Exception" propagation="REQUIRED"/> <tx:method name="insert*" rollback-for="Exception,RuntimeException" propagation="REQUIRED"/> <tx:method name="delete*" rollback-for="Exception" propagation="REQUIRED"/> </tx:attributes> </tx:advice> <aop:config> <aop:pointcut id="txPointcut" expression="execution(表达式) or execution(表达式)"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/> </aop:config> </beans>