Spring+mybatis 使用註解配置多資料庫源,支援讀寫分離
阿新 • • 發佈:2019-02-04
第一步:建立一個DynamicDataSource的類,繼承AbstractRoutingDataSource並重寫determineCurrentLookupKey方法,程式碼如下:
package spring; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return第二步:建立DynamicDataSourceHolder用於持有當前執行緒中使用的資料來源標識DynamicDataSourceHolder.getDataSource(); } }
package spring; public class DynamicDataSourceHolder { private static final ThreadLocal<String> THREAD_DATA_SOURCE = new ThreadLocal<String>(); public static String getDataSource() { return第三步:配置多個數據源和第一步裡建立的DynamicDataSource的bean,簡化的配置如下:THREAD_DATA_SOURCE.get(); } public static void setDataSource(String dataSource) { THREAD_DATA_SOURCE.set(dataSource); } public static void clearDataSource() { THREAD_DATA_SOURCE.remove(); } }
<bean id="readServerDataSource"class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${jdbc.mysql.driver}"/> <property name="url" value="${jdbc.mysql.url}"/> <property name="username" value="${jdbc.mysql.username}"/> <property name="password" value="${jdbc.mysql.password}"/> <property name="initialSize" value="${jdbc.initialSize}"/> <property name="minIdle" value="${jdbc.minIdle}"/> <property name="maxIdle" value="${jdbc.maxIdle}"/> <!--<property name="maxActive" value="${jdbc.maxActive}"/>--> <!--<property name="maxWait" value="${jdbc.maxWait}"/>--> <property name="defaultAutoCommit" value="${jdbc.defaultAutoCommit}"/> <!--<property name="removeAbandoned" value="${jdbc.removeAbandoned}"/>--> <property name="removeAbandonedTimeout" value="${jdbc.removeAbandonedTimeout}"/> <property name="testWhileIdle" value="${jdbc.testWhileIdle}"/> <property name="timeBetweenEvictionRunsMillis" value="${jdbc.timeBetweenEvictionRunsMillis}"/> <property name="numTestsPerEvictionRun" value="${jdbc.numTestsPerEvictionRun}"/> <property name="minEvictableIdleTimeMillis" value="${jdbc.minEvictableIdleTimeMillis}"/> </bean> <bean id="writeServerDataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${jdbc.mysql.driver}"/> <property name="url" value="${jdbc.mysql.url}"/> <property name="username" value="${jdbc.mysql.username}"/> <property name="password" value="${jdbc.mysql.password}"/> <property name="initialSize" value="${jdbc.initialSize}"/> <property name="minIdle" value="${jdbc.minIdle}"/> <property name="maxIdle" value="${jdbc.maxIdle}"/> <!--<property name="maxActive" value="${jdbc.maxActive}"/>--> <!--<property name="maxWait" value="${jdbc.maxWait}"/>--> <property name="defaultAutoCommit" value="${jdbc.defaultAutoCommit}"/> <!--<property name="removeAbandoned" value="${jdbc.removeAbandoned}"/>--> <property name="removeAbandonedTimeout" value="${jdbc.removeAbandonedTimeout}"/> <property name="testWhileIdle" value="${jdbc.testWhileIdle}"/> <property name="timeBetweenEvictionRunsMillis" value="${jdbc.timeBetweenEvictionRunsMillis}"/> <property name="numTestsPerEvictionRun" value="${jdbc.numTestsPerEvictionRun}"/> <property name="minEvictableIdleTimeMillis" value="${jdbc.minEvictableIdleTimeMillis}"/> </bean>
<bean id="dataSource" class="spring.DynamicDataSource"> <property name="targetDataSources"> <map key-type="java.lang.String"> <!-- 指定lookupKey和與之對應的資料來源 --> <entry key="readServerDataSource" value-ref="readServerDataSource"></entry> <entry key="writeServerDataSource" value-ref="writeServerDataSource"></entry> </map> </property> <!-- 這裡可以指定預設的資料來源 --> <property name="defaultTargetDataSource" ref="readServerDataSource"/> </bean> <!-- MyBatis配置 --> <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <!-- 自動掃描domain目錄, 省掉Configuration.xml裡的手工配置 --> <property name="typeAliasesPackage" value="order.dao.domain"/> <!-- 顯式指定Mapper檔案位置 --> <property name="mapperLocations" value="classpath*:mybatis/*.xml"/> <property name="configLocation" value="classpath:mybatis-config.xml"/> </bean>到這裡已經可以使用多資料來源了,在操作資料庫之前只要DynamicDataSourceHolder.setDataSource("readServerDataSource")即可切換到資料來源對其只進行讀的操作,需要使用只要DynamicDataSourceHolder.setDataSource("writeServerDataSource")
接下來配置自定義註解用來切換資料來源,這樣操作起來更加的簡便,不用每次都去set
首先,我們得定義一個名為DataSource的註解
package spring; import org.springframework.stereotype.Component; import java.lang.annotation.*; import static java.lang.annotation.RetentionPolicy.RUNTIME; @Target({ElementType.TYPE,ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface DataSource { String value(); }然後,定義AOP切面以便攔截所有帶有註解@DataSource的方法,取出註解的值作為資料來源標識放到DynamicDataSourceHolder的執行緒變數中
package spring; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.stereotype.Component; import java.lang.annotation.Annotation; import java.lang.reflect.Method; @Component @Aspect public class DataSourceAspect { /** * 攔截目標方法,獲取由@DataSource指定的資料來源標識,設定到執行緒儲存中以便切換資料來源 * * @param point * @throws Exception */ public void intercept(JoinPoint point) throws Exception { Class<?> target = point.getTarget().getClass(); MethodSignature signature = (MethodSignature) point.getSignature(); // 預設使用目標型別的註解,如果沒有則使用其實現介面的註解 for (Class<?> clazz : target.getInterfaces()) { resolveDataSource(clazz, signature.getMethod()); } resolveDataSource(target, signature.getMethod()); } /** * 提取目標物件方法註解和型別註解中的資料來源標識 * * @param clazz * @param method */ private void resolveDataSource(Class<?> clazz, Method method) { try { Class<?>[] types = method.getParameterTypes(); // 預設使用型別註解 if (clazz.isAnnotationPresent((Class<? extends Annotation>) 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) { System.out.println(clazz + ":" + e.getMessage()); } } }OK,這樣就可以直接在類或者方法上使用註解@DataSource來指定資料來源,不需要每次都手動設定了。
提示:註解@DataSource既可以加在方法上,也可以加在介面或者介面的實現類上,優先級別:方法>實現類>介面。也就是說如果介面、介面實現類以及方法上分別加了@DataSource註解來指定資料來源,則優先以方法上指定的為準。
例子用讀的資料來源:
@Service("ProductDAOImpl") @DataSource("readServerDataSource") public class ProductDAOImpl extends MyAbstractPageService<IProductDAO, Product> { @Autowired private IProductDAO dao; @Override public IProductDAO getDao() { return dao; } public IProductDAO querySum() { return dao; } public IProductDAO queryId(String id) { return dao; } }然後自己寫個資料庫的操作測試一下是否成功