1. 程式人生 > >Spring+mybatis 使用註解配置多資料庫源,支援讀寫分離

Spring+mybatis 使用註解配置多資料庫源,支援讀寫分離

第一步:建立一個DynamicDataSource的類,繼承AbstractRoutingDataSource並重寫determineCurrentLookupKey方法,程式碼如下:

package spring;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class DynamicDataSource extends AbstractRoutingDataSource {
   @Override
protected Object determineCurrentLookupKey() {

      return 
DynamicDataSourceHolder.getDataSource(); } }
第二步:建立DynamicDataSourceHolder用於持有當前執行緒中使用的資料來源標識
package spring;

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(); } }
第三步:配置多個數據源和第一步裡建立的DynamicDataSource的bean,簡化的配置如下:
<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;
   }
}
然後自己寫個資料庫的操作測試一下是否成功