Spring配置使用多数据源

本文介绍了一种在MyBatis中实现多数据源切换的方法,通过自定义MultipleDataSource类继承AbstractRoutingDataSource,并结合Spring框架进行配置。同时展示了如何通过AOP和注解的方式在运行时动态切换数据源。
摘要由CSDN通过智能技术生成

实现多数据源的方法就是我们自定义了一个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());
		
	
	
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

奔跑的窝窝牛

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值