实现多数据源的方法就是我们自定义了一个MultipleDataSource,这个类继承自AbstractRoutingDataSource,而AbstractRoutingDataSource继承自AbstractDataSource ,AbstractDataSource 实现了javax.sql.DataSource接口,所以我们的MultipleDataSource也实现了javax.sql.DataSource接口,可以赋值给sqlSessionFactory的dataSource属性
xml配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="classpath:jdbc.properties"/>
</bean>
<bean id="sqlServerDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.sqlserver.driver}"/>
<property name="url" value="${jdbc.sqlserver.url}"/>
<property name="username" value="${jdbc.sqlserver.username}"/>
<property name="password" value="${jdbc.sqlserver.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="mySqlDataSource" class="org.apache.commons.dbcp.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="multipleDataSource" class="com.cn.common.MultipleDataSource">
<property name="defaultTargetDataSource" ref="mySqlDataSource"/>
<property name="targetDataSources">
<map>
<entry key="mySqlDataSource" value-ref="mySqlDataSource"/>
<entry key="sqlServerDataSource" value-ref="sqlServerDataSource"/>
</map>
</property>
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="multipleDataSource"/>
</bean></Beans>
可以看到Spring配置中multipleDataSource设置了两个属性defaultTargetDataSource和targetDataSources,这两个属性定义在AbstractRoutingDataSource,当MyBatis执行查询时会先选择数据源,选择顺序时现根据determineCurrentLookupKey方法返回的值到targetDataSources中去找,若能找到怎返回对应的数据源,若找不到返回默认的数据源defaultTargetDataSource,具体参考AbstractRoutingDataSource的源码
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class MultipleDataSource extends AbstractRoutingDataSource {
private static final ThreadLocal<String> dataSourceKey = new InheritableThreadLocal<String>();
public static void setDataSourceKey(String dataSource) {
dataSourceKey.set(dataSource);
}
@Override
protected Object determineCurrentLookupKey() {
return dataSourceKey.get();
}
}
测试
public class Main {
public static void main(String[] args) { // 初始化ApplicationContext
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
"applicationContext.xml");
MySqlMapper mySqlMapper = applicationContext
.getBean(MySqlMapper.class);
SqlServerMapper sqlServerMapper = applicationContext
.getBean(SqlServerMapper.class); // 设置数据源为MySql,使用了AOP测试时请将下面这行注释
MultipleDataSource.setDataSourceKey("mySqlDataSource");
mySqlMapper.getList(); // 设置数据源为SqlServer,使用AOP测试时请将下面这行注释
MultipleDataSource.setDataSourceKey("sqlServerDataSource");
sqlServerMapper.getList();
}
}
可根据业务写拦截器或者aop根据条件自动切换 dataSource
@Component
@Aspect
public class DataSourceAdvice {
//第一个* 表示返回所有类型, 后面接着是包名,impl..表示当前包和子包下
//第二个 * 表示 所有类
//第三个* 所有方法
//(..) 方法参数不限
@Pointcut("execution(public * com.powercn.fcity.modules.app.service.impl..*.*(..))")
private void pointcut(){}
@Before("pointcut()")
public void before(JoinPoint jp){
System.out.println(jp.getTarget().getClass().getName());
// MultipleDataSource.setDataSourceKey("ssfDataSource");
System.out.println(jp.getSignature().getName());
if (jp.getTarget().getClass().getName().equals("com.powercn.fcity.modules.app.service.impl.MessageService")) {
MultipleDataSource.setDataSourceKey("ssfDataSource");
}else{
MultipleDataSource.setDataSourceKey("dataSource");
}
}
上面的只是对MessageService这个类的所有方法使用 ssfDataSource ,也可以改execution表达式拦截需要的类写dataSource
或者可以自定义一个注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DataSource {
String value();
}
在需要用到不同dataSource的地方类上接口上方法名@dataSource("ssfDataSource")或者@dataSource("dataSource1").....
public interface UserMapper {
@DataSource("master")
public void add(User user);
@DataSource("master")
public void update(User user);
@DataSource("master")
public void delete(int id);
@DataSource("slave")
public User loadbyid(int id);
@DataSource("master")
public User loadbyname(String name);
@DataSource("slave")
public List<User> list();
}
@Component
@Aspect
public class DataSourceAdvice {
@Before("pointcut()")
public void before(JoinPoint point)
{
Object target = point.getTarget();
String method = point.getSignature().getName();
Class<?>[] classz = target.getClass().getInterfaces();
Class<?>[] parameterTypes = ((MethodSignature) point.getSignature())
.getMethod().getParameterTypes();
try {
Method m = classz[0].getMethod(method, parameterTypes);
if (m != null && m.isAnnotationPresent(DataSource.class)) {
DataSource data = m .getAnnotation(DataSource.class);
MultipleDataSource.setDataSourceKey(data.value());
}
} catch (Exception e) {
}
}
}
或者 在类方法名上注解@DataSource("dataSource11111")
@Service
public class MessageService {
@Autowired
private DataMessageDao dataMsgDao;
@DataSource("dss")
public List<Map<String, String>> findDataMessageList(Map<String, Object> map){
return dataMsgDao.findDataMessageList(new HashMap<String, Object>());
}
@DataSource("ssfDs")
public List<Map<String, String>> ssfDataMessageList(Map<String, Object> map){
return dataMsgDao.findDataMessageList(new HashMap<String, Object>());
}
}
@Component
@Aspect
public class DataSourceAdvice {
@Before("pointcut()")
public void before(JoinPoint point)
{
String targetName = point.getTarget().getClass().getName();
String methodName = point.getSignature().getName();
Class targetClass = Class.forName(targetName);
Method[] method = targetClass.getMethods();
for (Method m : method) {
if (m.getName().equals(methodName)) {
DataSource dataSource = m.getAnnotation(DataSource.class);
if (dataSource != null) {
MultipleDataSource.setDataSourceKey(dataSource.value());
}
break;
}
}
}
}
或者在类上注解@DataSource("dataSource11111")
@DataSource("dss")
public class MessageService {
}
@Component
@Aspect
public class DataSourceAdvice {
@Before("pointcut()")
public void before(JoinPoint point)
{
Class<?> classz = point.getTarget().getClass()
String methodName = point.getSignature().getName();
DataSource ds = (DataSource)classz.getAnnotation(DataSource.class);
if (ds!=null) {
MultipleDataSource.setDataSourceKey(dataSource.value());
}
}
}
}
或者以上三种任意一种都行,三种拦截写在一个方法内全部拦截过滤一次都行。 建议顺序 interface-class-method生效,最后是以method配置生效
@Component
@Aspect
public class DataSourceAdvice {
//第一个* 表示返回所有类型, 后面接着是包名,impl..表示当前包和子包下
//第二个 * 表示 所有类
//第三个* 所有方法
//(..) 方法参数不限
@Pointcut("execution(public * com.powercn.fcity.modules.app.service.impl..*.*(..))")
private void pointcut(){}
@Before("pointcut()")
public void before(JoinPoint point) throws NoSuchMethodException, SecurityException{
Class<?> classz = point.getTarget().getClass();
String methodName = point.getSignature().getName();
DataSource ds= null;
//判断interface是否有注解datasource
Class<?>[] parameterTypes = ((MethodSignature)point.getSignature()).getParameterTypes();
ds = (DataSource)classz.getInterfaces()[0].getAnnotation(DataSource.class);
if (ds != null) {
MultipleDataSource.setDataSourceKey(ds.value());
}
//判断interface里面的方法名是否有注解dataSource
try {
Method m = classz.getInterfaces()[0].getMethod(methodName, parameterTypes);
if (m != null && m.isAnnotationPresent(DataSource.class)) {
DataSource data = m .getAnnotation(DataSource.class);
MultipleDataSource.setDataSourceKey(data.value());
}
} catch (Exception e) {
}
//判断类名上是否有注解dataSource
ds = (DataSource)classz.getAnnotation(DataSource.class);
if (ds!=null) {
MultipleDataSource.setDataSourceKey(dataSource.value());
}
//类中方法上是否有注解
Method method = classz.getMethod(methodName, parameterTypes);
ds = (DataSource)method.getAnnotation(DataSource.class);
if (ds!=null) {
MultipleDataSource.setDataSourceKey(dataSource.value());
}