切换数据源

使用SpringAbstractRoutingDataSource类来进行拓展多数据源。

该类就相当于一个dataSource的路由,用于根据key值来进行切换对应的dataSource

首先编写DynamicDataSource

package com.bsk.util;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
 * 继承Spring的AbstractRoutingDataSource类来进行拓展多数据源
 * @author Lenovo
 *
 */
public class DynamicDataSource extends AbstractRoutingDataSource {
	
	/**
	 * 返回当前数据源的key值
	 * 之后通过该key值在resolvedDataSources这个map中找到对应的value,该value就是数据源
	 */
	@Override
	protected Object determineCurrentLookupKey() {
		return DataSourceHolder.getDataSources();
	}
	
}

其中DataSourceHolder类的代码:

package com.bsk.util;
/**
 * 动态数据源
 * @author Lenovo
 *
 */
public class DataSourceHolder {
	private static final ThreadLocal<String> dataSources = new ThreadLocal<String>();
	
	public static void setDataSources(String dataSource) {
		dataSources.set(dataSource);
	}
	
	public static String getDataSources() {
		return dataSources.get();
	}
}

使用了ThreadLocal来保存数据源

然后呢,在Spring的配置文件applicationContext.xml中配置我们的数据源

<bean id="ssm1DataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource "
		destroy-method="close">
		<property name="driverClass" value="com.mysql.jdbc.Driver" />
		<property name="jdbcUrl" value="jdbc:mysql://${jdbc.host}:${jdbc.port}/${jdbc.database}?characterEncoding=utf8" />
		<property name="user" value="${jdbc.username}" />
		<property name="password" value="${jdbc.password}" />
		<property name="acquireIncrement" value="1" />
		<property name="initialPoolSize" value="5" />
		<property name="maxPoolSize" value="20" />
		<property name="minPoolSize" value="5" />
		<property name="maxStatements" value="100" />
		<property name="testConnectionOnCheckout" value="true" />
	</bean>
	<bean id="ssm2DataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource "
		destroy-method="close">
		<property name="driverClass" value="com.mysql.jdbc.Driver" />
		<property name="jdbcUrl" value="jdbc:mysql://${jdbc.host}:${jdbc.port}/${jdbc.database2}?characterEncoding=utf8" />
		<property name="user" value="${jdbc.username}" />
		<property name="password" value="${jdbc.password}" />
		<property name="acquireIncrement" value="1" />
		<property name="initialPoolSize" value="5" />
		<property name="maxPoolSize" value="20" />
		<property name="minPoolSize" value="5" />
		<property name="maxStatements" value="100" />
		<property name="testConnectionOnCheckout" value="true" />
	</bean>
	<bean id="dataSource" class="com.bsk.util.DynamicDataSource">
		<property name="targetDataSources">
			<map key-type="java.lang.String">
				<entry key="ssm1DataSource" value-ref="ssm1DataSource"></entry>
				<entry key="ssm2DataSource" value-ref="ssm2DataSource"></entry>
			</map>
		</property>
		<!-- 默认数据源 -->
		<property name="defaultTargetDataSource" ref="ssm1DataSource"/>
	</bean>

这里分别配置了两个数据源:ssm1DataSourcessm2DataSource
之后再通过Spring的依赖注入方式将两个数据源设置进targetDataSources

最后利用SpringAOP来实现动态切换数据源

在pom.xml 添加AOP的相关依赖:

    <dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-aop</artifactId>
	<version>4.2.0.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.8.9</version>
    </dependency>

    

第一种方式:通过SpringMVC对注解驱动的支持和AspectJ自动代理的方式,如下

在springmvc-servlet.xml中配置:

    <!-- 开启spring对注解驱动的支持 -->
    <mvc:annotation-driven/>
    <!-- 启动AspectJ自动代理 --> 
    <aop:aspectj-autoproxy proxy-target-class="true"/>

定义我们的切面类DataSourceExchange:

package com.bsk.util;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

import com.bsk.entity.Constants;

/**
 * 利用Spring的AOP来实现动态切换数据源
 * 动态切换数据源的切面类
 * @author Lenovo
 *
 */
@Aspect
@Component
public class DataSourceExchange {
	private final Log logger = LogFactory.getLog(this.getClass());
	
	/**
	 * 定义切点方法
	 */
	@Pointcut("execution(* com.bsk.controller.*.*.*(..))")
	public void aspect() {
		
	}
	/**
	 * 切点执行前动态切换数据源
	 * @param point
	 */
	@Before("aspect()")
	public void doBefore(JoinPoint point) {
		
		// 获取目标对象的类类型
		Class<?> aClass = point.getTarget().getClass();
		// 获取包名用于区分不同数据源
		String whichDataSource = aClass.getName().substring(19, aClass.getName().lastIndexOf("."));
		logger.info("当前切点的包名" + whichDataSource);
		switch(whichDataSource) {
			case "ssmone":
				DataSourceHolder.setDataSources(Constants.DATASOURCE_ONE);
				break;
			case "ssmtwo":
				DataSourceHolder.setDataSources(Constants.DATASOURCE_TWO);
				break;
		}
	}
	
	/**
	 * 执行后将数据源置空
	 */
	@After("aspect()")
	public void doAfter() {
		DataSourceHolder.setDataSources(null);
	}
}

在执行数据库操作之前做一个切面。

  • 通过JoinPoint对象获取目标对象。
  • 在目标对象中获取包名来区分不同的数据源。
  • 根据不同数据源来进行赋值。
  • 执行完毕之后将数据源清空。

通过包名来区分不同数据源,目录结构如下:

第二种方式:通过在Spring的applicationContext中进行对service层中的方法进行切面配置的实现方式

 	<bean id="dataSourceExchange" class="com.bsk.util.DataSourceExchange" order="1"></bean>
 	<aop:config proxy-target-class="false">
		<!--所有数据库操作的方法加入切面-->
 		<aop:aspect ref="dataSourceExchange"> 
 			<aop:pointcut id="dataSourcePointcut" expression="execution(* com.bsk.service.*.*.*(..))"/>
 			<aop:before pointcut-ref="dataSourcePointcut" method="doBefore"/>
 			<aop:after pointcut-ref="dataSourcePointcut" method="doAfter"/>
 		</aop:aspect> 
 	</aop:config> 

 注意:<aop:aspect ref="dataSourceExchange" order="1">中order="1",必须要添加上,由于order这一项,配置了执行的优先级,一定要在事务开启前,将数据源切换完毕。不然,即使代码进入了切面方法,切换数据源也不会生效,因为事务已经建立。

package com.bsk.util;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.aspectj.lang.JoinPoint;

import com.bsk.entity.Constants;

/**
 * 利用Spring的AOP来实现动态切换数据源
 * 动态切换数据源的切面类
 * @author Lenovo
 *
 */
public class DataSourceExchange {
	private final Log logger = LogFactory.getLog(this.getClass());
	
	/**
	 * 切点执行前动态切换数据源
	 * @param point
	 */
	public void doBefore(JoinPoint point) {
		
		// 获取目标对象的类类型
		Class<?> aClass = point.getTarget().getClass();
		// 获取包名用于区分不同数据源
		String whichDataSource = aClass.getName().substring(16, aClass.getName().lastIndexOf("."));
		logger.info("当前切点的包名" + whichDataSource);
		switch(whichDataSource) {
			case "ssmone":
				DataSourceHolder.setDataSources(Constants.DATASOURCE_ONE);
				break;
			case "ssmtwo":
				DataSourceHolder.setDataSources(Constants.DATASOURCE_TWO);
				break;
		}
	}
	
	/**
	 * 执行后将数据源置空
	 */
	public void doAfter() {
		DataSourceHolder.setDataSources(null);
	}
}

 这样切换数据源的问题就变得简单了!

后续想要增加新的数据源,把该数据源的实现类放在新的包中,然后就可以通过包名来灵活的切换数据源了!

 

有关参考文章:

https://crossoverjie.top/2017/01/05/SSM8/

https://blog.csdn.net/qq_33251859/article/details/78037627

https://dove19900520.iteye.com/blog/2364875

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

笑看风云路

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

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

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

打赏作者

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

抵扣说明:

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

余额充值