使用Spring
的AbstractRoutingDataSource
类来进行拓展多数据源。
该类就相当于一个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>
这里分别配置了两个数据源:ssm1DataSource
和ssm2DataSource
。
之后再通过Spring
的依赖注入方式将两个数据源设置进targetDataSources
。
最后利用Spring
的AOP来实现动态切换数据源
在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.youkuaiyun.com/qq_33251859/article/details/78037627