这里使用AOP拦截注解来切换数据源。
1. 在数据源配置文件context.xml中增加新的数据源信息,使存在多个数据库服务可以访问。注意区别开jndi名称。
2. 在spring配置文件(一般是spring.xml)中增加新数据源的连接配置。
3. 新建多数据源类(比如MultipleDataSource.java),需要继承自org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource
a) 增加静态属性用来保存数据源信息。因为线程间不需要访问对方的数据源,这里使用ThreadLocal保存各线程的数据源信息
public class DataSourceContextLocal
{
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
public static void setDataSourceId(String dataSourceId)
{
//增加静态设置方法,用于给当前线程设置数据源
contextHolder.set(dataSourceId);
}
public static String getDataSourceId()
{
return contextHolder.get();
}
public static void clearDataSourceId()
{
//增加静态清除方法,用于给无注解方法使用默认数据源
contextHolder.remove();
}
}
b) 重写determineCurrentLookupKey()方法。该方法是spring jdbc用来从targetDataSources中查找数据源的,如果返回为null, 则使用defaultTargetDataSource指定的默认数据源(这些属性在第4步配置)
public class DynamicDataSource extends AbstractRoutingDataSource
{
@Override
protected Object determineCurrentLookupKey()
{
return DataSourceContextLocal.getDataSourceId();
}
}
4. 在spring.xml增加多数据源类的bean
<!-- 多数据源 -->
<bean id="dynamicDataSource" class="com.iflytek.wh.base.dao.common.DynamicDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry key="defaultDs" value-ref="defaultDs" />
</map>
</property>
<property name="defaultTargetDataSource" ref="defaultDs" />
</bean>
a) 配置属性defaultTargetDataSource,ref值为几个数据库连接中的某一个。当没有指定数据源时,默认使用该连接。
b) 配置属性targetDataSources,map的条目key自由定义,用来识别不同的数据源,value-ref指向对应的数据库连接。
6. 新增注解类,需要包含一个字符串属性,由于结合AOP所以要指定反射期可用,同时该注解作用于方法上:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 用于在dao层切换数据源
*
* @author elsu@iflytek.com
* @date 2016年3月29日 下午1:04:58
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DataSource
{
String name() default DataSource.defaultDs;
public static String defaultDs = "defaultDs";
public static String otherDs = "otherDs";
public static String usercenterDs = "usercenterDs";
}
7. 新建一个AOP通知类,我们要在方法执行以前先切换数据源,所以要实现org.springframework.aop.MethodBeforeAdvice接口,重写before(Method method, Object[] args, Object target)方法:
import java.lang.reflect.Method;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.MethodBeforeAdvice;
import com.iflytek.wh.base.support.annotation.DataSource;
public class DataSourceSwitching implements MethodBeforeAdvice, AfterReturningAdvice {
private Logger logger = LoggerFactory.getLogger(getClass());
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
String fullMethodName = target.getClass().getSimpleName() + "." + method.getName();
DataSource dataSource = method.getAnnotation(DataSource.class);
if (dataSource != null) {
String dataSourceName = dataSource.name();
logger.debug(fullMethodName + ":切换到数据源" + dataSourceName);
DataSourceContextLocal.setDataSourceId(dataSourceName);
} else {
logger.debug(fullMethodName + ":使用默认数据源");
DataSourceContextLocal.clearDataSourceId();
}
}
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target)
throws Throwable {
// DataSourceContextLocal.clearDataSourceId();
}
}
8. 由于接口实现配置了事务管理,事务的优先级会高于数据源切换。一旦事务开启,再切换数据源是无效的,所以这里需要修改它们的次序
a) 通知类要实现org.springframework.core.Ordered接口,并重写getOrder()方法,令其返回1;
<!-- 事务 --> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" p:dataSource-ref="dynamicDataSource" /> <tx:annotation-driven transaction-manager="txManager" order="1" />
样,Spring会优先执行通知类方法,之后再开启事务。
9. 在需要切换数据源的接口方法上(必须是接口里的方法,在实现方法上无效)增加注解@BiDSChoicer(dsName="dsname"),属性值必须是配置的targetDataSources属性的条目key。
10. 在spring.xml里面新建通知类的bean,假设id是advice
11. 定义切入点,expression属性要根据情况具体修改,指向实现类
<!-- 注解切换数据源 --> <aop:config> <aop:pointcut id="daoMapping" expression="execution(* com.iflytek..service.*.*(..))" /> <!-- 关键配置,切换数据源一定要比持久层代码更先执行(事务也算持久层代码) --> <aop:advisor advice-ref="dataSourceSwitching" pointcut-ref="daoMapping" order="0" /> </aop:config> <bean id="dataSourceSwitching" class="com.iflytek.wh.base.dao.common.DataSourceSwitching" />
这样配置以后,需要修改数据源的接口方法只要在其上增加注解即可。AOP拦截方法后会先对数据源进行设置,有注解的根据注解属性设置,没有注解的设置为使用默认数据源。对于AOP不拦截的则使用默认数据源。
参考地址: