spring+mybatis多數據源配置

(1)單數據源配置

<bean id="dataSource" class="org.logicalcobwebs.proxool.ExtendsProxoolDataSource">
        <property name="alias" value="project"></property>
        <property name="delegateProperties">
            <value>user=${jdbc_health.username},password=${jdbc_health.password}</value>
        </property>
        <property name="user" value="${jdbc_health.username}"/>
        <property name="password" value="${jdbc_health.password}"/>
        <property name="driver" value="${jdbc_health.driverClassName}"/>
        <property name="driverUrl" value="${jdbc_health.url}"/>           
        <property name="maximumConnectionCount" value="${jdbc_health.maximumConnectionCount}"></property>
        <property name="maximumActiveTime" value="${jdbc_health.maximumActiveTime}"></property>
        <property name="maximumConnectionLifetime" value="${jdbc_health.maximumConnectionLifetime}"></property>
        <property name="prototypeCount" value="${jdbc_health.prototypeCount}"></property>
        <property name="houseKeepingSleepTime" value="${jdbc_health.houseKeepingSleepTime}"></property>
        <property name="simultaneousBuildThrottle" value="${jdbc_health.simultaneousBuildThrottle}"></property>
        <property name="houseKeepingTestSql" value="${jdbc_health.houseKeepingTestSql}"></property>
        <property name="verbose" value="${jdbc_health.verbose}"></property>
        <property name="statistics" value="${jdbc_health.statistics}"></property>
        <property name="statisticsLogLevel" value="${jdbc_health.statisticsLogLevel}"></property>
    </bean>

    <!--mybatis與Spring整合  -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="configLocation" value="classpath:SqlMapConfig.xml"/>
        <property name="dataSource" ref="dataSource"/> 
        <property name="plugins">
            <array>
                <ref bean="offsetLimitInterceptor"/>
            </array>
        </property>
    </bean>

(2)多數據源配置

    <!-- 多數據遠配置 -->
    <bean id="multipleDataSource" class="com.netease.haitao.common.datasource.MultipleDataSource" primary="true">  
        <property name="targetDataSources">  
            <map key-type="java.lang.String">  
                <entry value-ref="dataSource" key="dataSource"></entry>  
                <entry value-ref="readOnlyDataSource" key="readOnlyDataSource"></entry>  
            </map>  
        </property>  
        <property name="defaultTargetDataSource" ref="dataSource"/>
    </bean>  

    <bean id="dataSource" class="org.logicalcobwebs.proxool.ExtendsProxoolDataSource" primary="false">
        <property name="alias" value="travel"></property>
        <property name="delegateProperties">
            <value>user=${jdbc_health.username},password=${jdbc_health.password}</value>
        </property>
        <property name="user" value="${jdbc_health.username}"/>
        <property name="password" value="${jdbc_health.password}"/>
        <property name="driver" value="${jdbc_health.driverClassName}"/>
        <property name="driverUrl" value="${jdbc_health.url}"/>           
        <property name="maximumConnectionCount" value="${jdbc_health.maximumConnectionCount}"></property>
        <property name="maximumActiveTime" value="${jdbc_health.maximumActiveTime}"></property>
        <property name="maximumConnectionLifetime" value="${jdbc_health.maximumConnectionLifetime}"></property>
        <property name="prototypeCount" value="${jdbc_health.prototypeCount}"></property>
        <property name="houseKeepingSleepTime" value="${jdbc_health.houseKeepingSleepTime}"></property>
        <property name="simultaneousBuildThrottle" value="${jdbc_health.simultaneousBuildThrottle}"></property>
        <property name="houseKeepingTestSql" value="${jdbc_health.houseKeepingTestSql}"></property>
        <property name="verbose" value="${jdbc_health.verbose}"></property>
        <property name="statistics" value="${jdbc_health.statistics}"></property>
        <property name="statisticsLogLevel" value="${jdbc_health.statisticsLogLevel}"></property>
    </bean>

    <!-- 備庫數據源 -->
    <bean id="readOnlyDataSource" class="org.logicalcobwebs.proxool.ExtendsProxoolDataSource" primary="false">
        <property name="alias" value="readOnlyTravel"></property>
        <property name="delegateProperties">
            <value>user=${jdbc_health.readonly.username},password=${jdbc_health.readonly.password}</value>
        </property>
        <property name="user" value="${jdbc_health.username}"/>
        <property name="password" value="${jdbc_health.password}"/>
        <property name="driver" value="${jdbc_health.driverClassName}"/>
        <property name="driverUrl" value="${jdbc_health.readonly.url}"/>           
        <property name="maximumConnectionCount" value="${jdbc_health.maximumConnectionCount}"></property>
        <property name="maximumActiveTime" value="${jdbc_health.maximumActiveTime}"></property>
        <property name="maximumConnectionLifetime" value="${jdbc_health.maximumConnectionLifetime}"></property>
        <property name="prototypeCount" value="${jdbc_health.prototypeCount}"></property>
        <property name="houseKeepingSleepTime" value="${jdbc_health.houseKeepingSleepTime}"></property>
        <property name="simultaneousBuildThrottle" value="${jdbc_health.simultaneousBuildThrottle}"></property>
        <property name="houseKeepingTestSql" value="${jdbc_health.houseKeepingTestSql}"></property>
        <property name="verbose" value="${jdbc_health.verbose}"></property>
        <property name="statistics" value="${jdbc_health.statistics}"></property>
        <property name="statisticsLogLevel" value="${jdbc_health.statisticsLogLevel}"></property>
    </bean>

  <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="configLocation" value="classpath:SqlMapConfig.xml"/>
        <property name="dataSource" ref="multipleDataSource"/>
        <property name="plugins">
            <array>
                <ref bean="paginationInterceptor"/>
                <ref bean="rowBoundPaginationInterceptor"/>
            </array>
        </property>
    </bean>

    <!-- 從com.netease.health.dao中查找有Repository註解的接口,實例化爲Mybatis的實現類並註冊到Spring容器中-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.netease.health.dao" />
        <property name="annotationClass" value="org.springframework.stereotype.Repository"/>
    </bean>

<!-- 自定義aop -->
<aop:aspectj-autoproxy/>

(3)multipleDataSource實現

/**
 * 
* @ClassName: MultipleDataSource 
* @Description: 多數據源 
* @date 2015年8月26日 下午4:28:07 
*
 */
public class MultipleDataSource extends AbstractRoutingDataSource {
    private static final ThreadLocal<String> dataSourceKey = new ThreadLocal<String>();

    /**
     * 
     * @Description: 設置當前線程的數據源
     * @date 2015年8月26日 下午4:29:01 
     * @param dataSource
     */
    public static void setDataSourceKey(String dataSource) {
        dataSourceKey.set(dataSource);
    }

    @Override
    protected Object determineCurrentLookupKey() {
        return dataSourceKey.get();
    }

    public static void removeDataSourceKey() {
        dataSourceKey.remove();
    }
}

(4)dao定義

public interface MySqlMapper {
    @Select("select * from MyTable")
    List<Map<String,Object>> getList();
}

public interface SqlServerMapper {
        @Select("select * from MyTable")
        List<Map<String,Object>> getList();
}

(5)使用SpringAOP方式實現自動切換

/**
 * 
* @ClassName: DataSourceSelect 
* @Description: 數據源選擇註解
* @date 2015年8月26日 下午4:45:54 
*
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSourceSelect {
    /**
     * 
     * @Description: 數據源值,默認爲主庫
     * @date 2015年8月26日 下午4:49:05 
     * @return
     */
    DataSourceEnum value() default DataSourceEnum.READ_WRITE_DATA_SOURCE;
}
/**
 * 
* @ClassName: MultipleDataSourceAspectAdvice 
* @Description: 數據源切面
* @date 2015年8月26日 下午6:47:09 
*
 */
@Aspect
@Component
public class MultipleDataSourceAspectAdvice {

     @Pointcut("@annotation(com.netease.haitao.common.annotation.DataSourceSelect) && @annotation(org.springframework.web.bind.annotation.RequestMapping)") 
     public void dataSourceSelect() {} 

    /**
     * 
     * @Description: 在加了DataSourceSelect註解的方法前設置數據源
     * @date 2015年8月26日 下午6:47:17 
     * @param pjp
     * @throws Throwable
     */
    @Around(value="dataSourceSelect() && @annotation(selectAnnotation)")
    public Object changeDataSource(ProceedingJoinPoint pjp, DataSourceSelect selectAnnotation) throws Throwable {
        MultipleDataSource.setDataSourceKey(selectAnnotation.value().getName());
        try {
            return pjp.proceed();
        } finally {
            MultipleDataSource.removeDataSourceKey();
        }
    }
}

枚舉數據庫

/**
 * 
* @ClassName: DataSourceEnum 
* @Description: 多數據遠枚舉
* @date 2015年8月26日 下午4:59:51 
*
 */
public enum DataSourceEnum {
    /**
     * 讀寫數據源
     */
    READ_WRITE_DATA_SOURCE("dataSource"),

    /**
     * 只讀數據源
     */
    READ_ONLY_DATA_SOURCE("readOnlyDataSource");

    private String name;

    private DataSourceEnum(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}

(6)調用方法

@RequestMapping(value="/manage", params="opType=orderlist")
    @ResponseBody
    @DataSourceSelect(DataSourceEnum.READ_ONLY_DATA_SOURCE)
    public ResultInfo orderlist(String opType, HttpServletRequest request) {
        .......
    }

(7)說明
這裏就上面的實現做個簡單解釋,在我們配置單數據源時可以看到數據源類型使用了org.logicalcobwebs.proxool.ExtendsProxoolDataSource,而這個代碼實現了javax.sql.DataSource接口
配置sqlSessionFactory時org.mybatis.spring.SqlSessionFactoryBean注入參數dataSource類型就是javax.sql.DataSource

實現多數據源的方法就是我們自定義了一個MultipleDataSource,這個類繼承自AbstractRoutingDataSource,而AbstractRoutingDataSource繼承自AbstractDataSource ,AbstractDataSource 實現了javax.sql.DataSource接口,所以我們的MultipleDataSource也實現了javax.sql.DataSource接口,可以賦值給sqlSessionFactory的dataSource屬性

public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
}

public abstract class AbstractDataSource implements DataSource {
}

再來說下MultipleDataSource的實現原理,MultipleDataSource實現AbstractRoutingDataSource抽象類,然後實現了determineCurrentLookupKey方法,這個方法用於選擇具體使用targetDataSources中的哪一個數據源

<!-- 多數據遠配置 -->
    <bean id="multipleDataSource" class="com.netease.haitao.common.datasource.MultipleDataSource" primary="true">  
        <property name="targetDataSources">  
            <map key-type="java.lang.String">  
                <entry value-ref="dataSource" key="dataSource"></entry>  
                <entry value-ref="readOnlyDataSource" key="readOnlyDataSource"></entry>  
            </map>  
        </property>  
        <property name="defaultTargetDataSource" ref="dataSource"/>
    </bean>  

可以看到Spring配置中multipleDataSource設置了兩個屬性defaultTargetDataSource和targetDataSources,這兩個屬性定義在AbstractRoutingDataSource,當MyBatis執行查詢時會先選擇數據源,選擇順序時現根據determineCurrentLookupKey方法返回的值到targetDataSources中去找,若能找到怎返回對應的數據源,若找不到返回默認的數據源defaultTargetDataSource,具體參考AbstractRoutingDataSource的源碼

public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {

    private Map<Object, Object> targetDataSources;

    private Object defaultTargetDataSource;


    /**
     * Retrieve the current target DataSource. Determines the
     * {@link #determineCurrentLookupKey() current lookup key}, performs
     * a lookup in the {@link #setTargetDataSources targetDataSources} map,
     * falls back to the specified
     * {@link #setDefaultTargetDataSource default target DataSource} if necessary.
     * @see #determineCurrentLookupKey()
     */
    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;
    }

    /**
     * Determine the current lookup key. This will typically be
     * implemented to check a thread-bound transaction context.
     * <p>Allows for arbitrary keys. The returned key needs
     * to match the stored lookup key type, as resolved by the
     * {@link #resolveSpecifiedLookupKey} method.
     */
    protected abstract Object determineCurrentLookupKey();
  
  .............

}

在動態切換數據源方法時選擇了AOP方式實現,這裏實現的簡單粗暴,具體應用時根據實際需要靈活變通吧

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章