在现在开发的过程中应该大多数朋友都有遇到过切换数据源的需求。比如现在常用的数据库读写分离,或者就是有两个数据库的情况,这些都需要用到切换数据源。
使用Spring的AbstractRoutingDataSource类来进行拓展多数据源。
该类就相当于一个dataSource的路由,用于根据key值来进行切换对应的dataSource。
下面简单来看下AbstractRoutingDataSource类的几段关键源码:
@Override
public Connection getConnection() throws SQLException {
return determineTargetDataSource().getConnection();
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return determineTargetDataSource().getConnection(username, password);
}
/**
* 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();
可以看到其中获取链接的方法getConnection()调用的determineTargetDataSource则是关键方法。该方法用于返回我们使用的数据源。
其中呢又是determineCurrentLookupKey()方法来返回当前数据源的key值。
之后通过该key值在resolvedDataSources这个map中找到对应的value(该value就是数据源)。
resolvedDataSources这个map则是在:
@Override
public void afterPropertiesSet() {
if (this.targetDataSources == null) {
throw new IllegalArgumentException("Property 'targetDataSources' is required");
}
this.resolvedDataSources = new HashMap<Object, DataSource>(this.targetDataSources.size());
for (Map.Entry<Object, Object> entry : this.targetDataSources.entrySet()) {
Object lookupKey = resolveSpecifiedLookupKey(entry.getKey());
DataSource dataSource = resolveSpecifiedDataSource(entry.getValue());
this.resolvedDataSources.put(lookupKey, dataSource);
}
if (this.defaultTargetDataSource != null) {
this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource);
}
}
这个方法通过targetDataSources这个map来进行赋值的。targetDataSources则是我们在配置文件中进行赋值的,下面会讲到。
再来看看determineCurrentLookupKey()方法,从protected来修饰就可以看出是需要我们来进行重写的。
第一步:创建一个DynamicDataSource的类,继承AbstractRoutingDataSource并重写determineCurrentLookupKey方法,代码如下:
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
// 从自定义的位置获取数据源标识
return DynamicDataSourceHolder.getDataSource();
}
}
第二步:创建DynamicDataSourceHolder用于持有当前线程中使用的数据源标识,代码如下:
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,简化的配置如下:applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<mvc:annotation-driven />
<context:property-placeholder location="classpath:jdbc.properties" />
<bean class="com.mchange.v2.c3p0.ComboPooledDataSource" id="mysqldataSource">
<property name="driverClass" value="${driverClass}"></property>
<property name="jdbcUrl" value="${url}"></property>
<property name="user" value="${user}"></property>
<property name="password" value="${password}"></property>
</bean>
<bean class="com.mchange.v2.c3p0.ComboPooledDataSource" id="pgdataSource">
<property name="driverClass" value="${pgdriverClass}"></property>
<property name="jdbcUrl" value="${pgurl}"></property>
<property name="user" value="${pguser}"></property>
<property name="password" value="${pgpassword}"></property>
</bean>
<bean id="dataSource" class="com.taas.common.base.DynamicDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<!-- 指定lookupKey和与之对应的数据源 -->
<entry key="mysqldataSource" value-ref="mysqldataSource"></entry>
<entry key="pgdataSource" value-ref="pgdataSource"></entry>
</map>
</property>
<!-- 这里可以指定默认的数据源 -->
<property name="defaultTargetDataSource" ref="mysqldataSource" />
</bean>
<bean id="dataSourceAspect" class="com.helloworld.common.base.DataSourceAspect" />
<aop:config>
<aop:aspect ref="dataSourceAspect">
<!-- 拦截所有service方法 -->
<aop:pointcut id="dataSourcePointcut"
expression="execution(* com.helloworld.*.service.*.*(..))" />
<aop:before pointcut-ref="dataSourcePointcut" method="intercept" />
</aop:aspect>
</aop:config>
<!-- sqlSessionFactory -->
<bean class="org.mybatis.spring.SqlSessionFactoryBean" id="sqlSessionFactory">
<property name="dataSource" ref="dataSource"></property>
<property name="configLocation" value="classpath:mybatis.xml"></property>
<property name="mapperLocations" value="classpath*:com/helloworld/*/mapping/*.xml"></property>
</bean>
<!-- mapper scan -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
<property name="basePackage" value="com.helloworld.*.dao"></property>
</bean>
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
id="transactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<tx:annotation-driven transaction-manager="transactionManager" />
</beans>
到这里已经可以使用多数据源了,在操作数据库之前只要DynamicDataSourceHolder.setDataSource(“dataSource2”)即可切换到数据源2并对数据库db2进行操作了。
示例代码如下:
@Service
public class DataServiceImpl implements DataService {
@Autowired
private DataMapper dataMapper;
@Override
public List<Map<String, Object>> getList1() {
// 没有指定,则默认使用数据源1
return dataMapper.getList1();
}
@Override
public List<Map<String, Object>> getList2() {
// 指定切换到数据源2
DynamicDataSourceHolder.setDataSource("dataSource2");
return dataMapper.getList2();
}
}
但是每次切换数据源时都调用DynamicDataSourceHolder.setDataSource(“xxx”)就显得十分繁琐了,而且代码量大了很容易会遗漏,后期维护起来也比较麻烦。
所以,最好的方法还是直接通过注解的方式指定需要访问的数据源,比如在dao层使用@DataSource(“xxx”)就指定访问数据源xxx。
我们得定义一个名为DataSource的注解
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Documented
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSource {
String value();
}
然后,定义AOP切面以便拦截所有带有注解@DataSource的方法,取出注解的值作为数据源标识放到DynamicDataSourceHolder的线程变量中:
import java.lang.reflect.Method;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
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(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());
}
}
}
最后在spring配置文件中配置拦截规则就可以了,比如拦截service层或者dao层的所有方法: 上面已经贴了相关代码了,醒目一点,再贴个重要的吧
<bean id="dataSourceAspect" class="com.helloworld.common.base.DataSourceAspect" />
<aop:config>
<aop:aspect ref="dataSourceAspect">
<!-- 拦截所有service方法 -->
<aop:pointcut id="dataSourcePointcut"
expression="execution(* com.helloworld.*.service.*.*(..))" />
<aop:before pointcut-ref="dataSourcePointcut" method="intercept" />
</aop:aspect>
</aop:config>
现在就可以,直接在类或者方法上使用注解@DataSource来指定数据源,不需要每次都手动设置了。
import javax.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
@DataSource("mysqldataSource")
public class UserServiceImpl implements UserService {
@Resource
private UserMapper userMapper;
public int deleteByPrimaryKey(Integer id) {
// TODO Auto-generated method stub
return userMapper.deleteByPrimaryKey(id);
}
public int insert(User record) {
// TODO Auto-generated method stub
return userMapper.insert(record);
}
public int insertSelective(User record) {
// TODO Auto-generated method stub
return userMapper.insertSelective(record);
}
public User selectByPrimaryKey(Integer id) {
// TODO Auto-generated method stub
return userMapper.selectByPrimaryKey(id);
}
public int updateByPrimaryKeySelective(User record) {
// TODO Auto-generated method stub
return userMapper.updateByPrimaryKeySelective(record);
}
public int updateByPrimaryKey(User record) {
// TODO Auto-generated method stub
return userMapper.updateByPrimaryKey(record);
}
}
提示:注解@DataSource既可以加在方法上,也可以加在接口或者接口的实现类上,优先级别:方法>实现类>接口。也就是说如果接口、接口实现类以及方法上分别加了@DataSource注解来指定数据源,则优先以方法上指定的为准。
参考大神博客:http://blog.csdn.net/a15020059230/article/details/76677322