(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方式实现,这里实现的简单粗暴,具体应用时根据实际需要灵活变通吧