繼承Spring的AbstractRoutingDataSource來實現多數據源配置
1. 數據源配置
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:task="http://www.springframework.org/schema/task" xsi:schemaLocation="http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.2.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.2.xsd"> <!-- db配置目錄 --> <context:property-placeholder file-encoding="utf-8" location="classpath:db.properties" /> <!-- 掃描目錄 --> <context:component-scan base-package="demo.controller"></context:component-scan> <!-- executor線程池,含義和java.util.concurrent.Executor是一樣的,pool-size的大小官方推薦為5~10 --> <task:executor id="executor" pool-size="5" /> <!-- scheduler的pool-size是ScheduledExecutorService線程池,默認為1 --> <task:scheduler id="scheduler" pool-size="5" /> <task:annotation-driven executor="executor" scheduler="scheduler" /> <!-- 數據庫連接池配置 --> <bean id="dataSourceTask" name="dataSourceTask" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <!-- 指定連接數據庫的驅動--> <property name="driverClass" value="${db.task.jdbc.driverClassName}"/> <!-- 指定連接數據庫的URL--> <property name="jdbcUrl" value="${db.task.jdbc.url}"/> <!-- 指定連接數據庫的用戶名--> <property name="user" value="${db.task.jdbc.user}"/> <!-- 指定連接數據庫的密碼--> <property name="password" value="${db.task.jdbc.password}"/> <!-- 指定連接池中保留的最大連接數. Default:15--> <property name="maxPoolSize" value="${db.task.jdbc.maxPoolSize}"/> <!-- 指定連接池中保留的最小連接數--> <property name="minPoolSize" value="${db.task.jdbc.minPoolSize}"/> <!-- 指定連接池的初始化連接數 取值應在minPoolSize 與 maxPoolSize 之間.Default:3--> <property name="initialPoolSize" value="${db.task.jdbc.initialPoolSize}"/> <!-- 最大空閑時間,60秒內未使用則連接被丟棄。若為0則永不丟棄。 Default:0--> <property name="maxIdleTime" value="${db.task.jdbc.maxIdleTime}"/> <!-- 當連接池中的連接耗盡的時候c3p0一次同時獲取的連接數. Default:3--> <property name="acquireIncrement" value="${db.task.jdbc.acquireIncrement}"/> <!-- JDBC的標准,用以控制數據源內加載的PreparedStatements數量。但由於預緩存的statements屬於單個connection 而不是整個連接池所以設置這個參數需要考慮到多方面的因數.如果maxStatements與maxStatementsPerConnection均為0,則緩存被關閉。Default:0--> <property name="maxStatements" value="${db.task.jdbc.maxStatements}"/> <!-- 每60秒檢查所有連接池中的空閑連接.Default:0 --> <property name="idleConnectionTestPeriod" value="${db.task.jdbc.idleConnectionTestPeriod}"/> <!--連接池獲取新連接的時間,超時后將 拋出SQLException,如設為0則無限期等待。單位毫秒。Default: 0 --> <property name="checkoutTimeout" value="${db.task.jdbc.checkoutTimeout}"/> </bean> <bean id="dataSourceEnv" name="dataSourceEnv" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <!-- 指定連接數據庫的驅動--> <property name="driverClass" value="${db.lcmyw.jdbc.driverClassName}"/> <!-- 指定連接數據庫的URL--> <property name="jdbcUrl" value="${db.lcmyw.jdbc.url}"/> <!-- 指定連接數據庫的用戶名--> <property name="user" value="${db.lcmyw.jdbc.user}"/> <!-- 指定連接數據庫的密碼--> <property name="password" value="${db.lcmyw.jdbc.password}"/> <!-- 指定連接池中保留的最大連接數. Default:15--> <property name="maxPoolSize" value="${db.lcmyw.jdbc.maxPoolSize}"/> <!-- 指定連接池中保留的最小連接數--> <property name="minPoolSize" value="${db.lcmyw.jdbc.minPoolSize}"/> <!-- 指定連接池的初始化連接數 取值應在minPoolSize 與 maxPoolSize 之間.Default:3--> <property name="initialPoolSize" value="${db.lcmyw.jdbc.initialPoolSize}"/> <!-- 最大空閑時間,60秒內未使用則連接被丟棄。若為0則永不丟棄。 Default:0--> <property name="maxIdleTime" value="${db.lcmyw.jdbc.maxIdleTime}"/> <!-- 當連接池中的連接耗盡的時候c3p0一次同時獲取的連接數. Default:3--> <property name="acquireIncrement" value="${db.lcmyw.jdbc.acquireIncrement}"/> <!-- JDBC的標准,用以控制數據源內加載的PreparedStatements數量。但由於預緩存的statements屬於單個connection 而不是整個連接池所以設置這個參數需要考慮到多方面的因數.如果maxStatements與maxStatementsPerConnection均為0,則緩存被關閉。Default:0--> <property name="maxStatements" value="${db.lcmyw.jdbc.maxStatements}"/> <!-- 每60秒檢查所有連接池中的空閑連接.Default:0 --> <property name="idleConnectionTestPeriod" value="${db.lcmyw.jdbc.idleConnectionTestPeriod}"/> <!--連接池獲取新連接的時間,超時后將 拋出SQLException,如設為0則無限期等待。單位毫秒。Default: 0 --> <property name="checkoutTimeout" value="${db.lcmyw.jdbc.checkoutTimeout}"/> </bean> <bean id="dataSource" class="demo.comm.DynamicDataSource"> <property name="targetDataSources"> <map key-type="java.lang.String"> <entry key="dataSourceTask" value-ref="dataSourceTask"></entry> <entry key="dataSourceEnv" value-ref="dataSourceEnv"></entry> </map> </property> <property name="defaultTargetDataSource" ref="dataSourceEnv"></property> </bean> <!-- spring和MyBatis完美整合,不需要mybatis的配置映射文件 --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <!-- 自動掃描mapping.xml文件 --> <property name="mapperLocations" value="classpath*:demo/dao/*.xml"/> </bean> <!-- DAO接口所在包名,Spring會自動查找其下的類 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="demo.dao" /> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/> </bean> <!-- 配置事物管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <!-- 攔截器方式配置事物 --> <tx:advice id="transactionAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="add*" propagation="REQUIRED" /> <tx:method name="insert*" propagation="REQUIRED" /> <tx:method name="update*" propagation="REQUIRED" /> <tx:method name="delete*" propagation="REQUIRED" /> <tx:method name="get*" propagation="SUPPORTS" read-only="true" /> <tx:method name="find*" propagation="SUPPORTS" read-only="true" /> <tx:method name="select*" propagation="SUPPORTS" read-only="true" /> <tx:method name="seach*" propagation="SUPPORTS" read-only="true" /> </tx:attributes> </tx:advice> <!-- spring aop事物管理 --> <aop:config> <aop:pointcut id="transactionPointcut" expression="execution(* demo.service..*Impl.*(..))" /> <!-- 設置order的值為2,使得數據庫事物開啟在數據源切換之后,否則數據源切換不會達到效果 --> <aop:advisor pointcut-ref="transactionPointcut" advice-ref="transactionAdvice" order="2"/> </aop:config> <!-- 數據源動態切換切面配置 --> <aop:config> <aop:aspect id="dataSourceAspect" ref="dataSourceInterceptor" order="1"> <!-- 攔截所有service實現類的方法 --> <aop:pointcut id="dataSourcePointcut" expression="execution(* demo.service..*Impl.*(..))"/> <aop:before pointcut-ref="dataSourcePointcut" method="intercept" /> </aop:aspect> </aop:config> <!-- 數據源動態切換實體 --> <bean id="dataSourceInterceptor" class="demo.comm.DynamicDataSourceInterceptor"/> <context:component-scan base-package="demo.service.impl"></context:component-scan> </beans>
2. 定義一個類繼承AbstractRoutingDataSource實現determineCurrentLookupKey方法,來實現數據庫的動態切換
package demo.comm; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { // 從自定義的位置獲取數據源標識 return DynamicDataSourceHolder.getDataSource(); } }
3. 定義工具類,用於動態切換數據源
package demo.comm; public class DynamicDataSourceHolder { /** * 注意:數據源標識保存在線程變量中,避免多線程操作數據源時互相干擾 */ private static final ThreadLocal<String> THREAD_DATA_SOURCE = new ThreadLocal<String>(); public static String getDataSource() { return THREAD_DATA_SOURCE.get(); } public static void setDataSource(String dataSource) { THREAD_DATA_SOURCE.set(dataSource); } public static void clearDataSource() { THREAD_DATA_SOURCE.remove(); } }
4.定義注解,通過注解的值來獲取當前數據源,並進行切換
package demo.comm; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface DataSource { String value(); }
定義攔截器,解析注解切換數據源
package demo.comm; import java.lang.reflect.Method; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.reflect.MethodSignature; /** * * ClassName: DynamicDataSourceInterceptor <br/> * Function: 數據源動態切換攔截器. <br/> * date: 2017年3月15日 下午10:28:17 <br/> * * @author JohnFNash * @version * @since JDK 1.6 */ public class DynamicDataSourceInterceptor { /** * 攔截目標方法,獲取由@DataSource指定的數據源標識,設置到線程存儲中以便切換數據源 * * @param point * @throws Exception */ public void intercept(JoinPoint point) throws Exception { Class<?> target = point.getTarget().getClass(); MethodSignature signature = (MethodSignature) point.getSignature(); resolveDataSource(target, signature.getMethod()); } /** * 提取目標對象方法注解和類型注解中的數據源標識 * * @param clazz * @param method */ private void resolveDataSource(Class<?> clazz, Method method) { try { Class<?>[] types = method.getParameterTypes(); // 默認使用類型注解 if (clazz.isAnnotationPresent(DataSource.class)) { DataSource source = clazz.getAnnotation(DataSource.class); DynamicDataSourceHolder.setDataSource(source.value()); } // 方法注解可以覆蓋類型注解 Method m = clazz.getMethod(method.getName(), types); if (m != null && m.isAnnotationPresent(DataSource.class)) { DataSource source = m.getAnnotation(DataSource.class); DynamicDataSourceHolder.setDataSource(source.value()); } } catch (Exception e) { e.printStackTrace(); } } }
注意:設置執行順序為1,並使用 aop:before 在數據庫事物開啟前進行數據源切換
@DataSource注解使用示例:
package demo.service.impl; import javax.annotation.Resource; import org.springframework.stereotype.Service; import demo.comm.DataSource; import demo.dao.FxnsrDAO; import demo.dao.TestDAO; import demo.entity.FxnsrEntity; import demo.entity.TestEntity; import demo.service.FxnsrService; import demo.service.TestService; @Service("testService") @DataSource("dataSourceEnv") public class TestServiceImpl implements TestService{ @Resource(name="testDAO") TestDAO testDAO; @Override public String seachTest() { return testDAO.seachTest(); } //set public void setTestDAO(TestDAO testDAO) { this.testDAO = testDAO; } }
上面 @DataSource 注解是定義在類上的,也可以定義在方法上,方法上定義的注解優先級高於定義在類上的注解
文章轉自:http://blog.csdn.net/johnf_nash/article/details/63260561