本文将介绍spring中多个数据源的配置,同时使用注解的方式切换选择数据源。
spring的其他配置不再细说,只说数据源的相关配置。
参考多篇博文实践整理,不在一一查找出处,如有侵权请及时联系
====================================
1.修改spring配置
1.1加入数据源
......
<!-- 配置数据源 使用的是Druid数据源 -->
<bean id="dataSourceOne" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
<!-- 配置监控统计拦截的filters,去掉后监控界面sql无法统计 -->
<property name="filters" value="stat" />
</bean>
<bean id="dataSourceTwo" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver2}" />
<property name="url" value="${jdbc.url2}"></property>
<property name="username" value="${jdbc.username2}"></property>
<property name="password" value="${jdbc.password2}"></property>
<!-- 配置监控统计拦截的filters,去掉后监控界面sql无法统计 -->
<property name="filters" value="stat" />
</bean>
......
1.2用统一的类处理,切换数据源,指定如果不设置,默认使用第一个数据源
<bean id="dynamicDataSource" class="com.best.core.DynamicDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry value-ref="dataSourceOne" key="dataSourceOne"></entry>
<entry value-ref="dataSourceTwo" key="dataSourceTwo"></entry>
</map>
</property>
<property name="defaultTargetDataSource" ref="dataSourceOne">
</property>
</bean>
1.3在sqlsession中引用
<!-- myBatis文件 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dynamicDataSource" />
<property name="mapperLocations" value="classpath:sqlmap/*.xml" />
<property name="plugins">
<array>
<bean class="com.github.pagehelper.PageInterceptor">
<!-- 这里的几个配置主要演示如何使用,如果不理解,一定要去掉下面的配置 -->
<property name="properties">
<value>
helperDialect=mysql
reasonable=true
supportMethodsArguments=true
params=count=countSql
</value>
</property>
</bean>
</array>
</property>
</bean>
2.数据源控制类的编写
2.1DynamicDataSource.java
public class DynamicDataSource extends AbstractRoutingDataSource{
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceHolder.getDataSource();
}
}
2.2DynamicDataSourceHolder.java
public class DynamicDataSourceHolder {
/**
* 注意:数据源标识保存在线程变量中,避免多线程操作数据源时互相干扰
*/
private static final ThreadLocal<String> THREAD_DATA_SOURCE = new ThreadLocal<String>();
public static String getDataSource() {
return THREAD_DATA_SOURCE.get();
}
public static void setDataSource(String dataSource) {
THREAD_DATA_SOURCE.set(dataSource);
}
public static void clearDataSource() {
THREAD_DATA_SOURCE.remove();
}
}
3.调用
在相关的dao层加上数据源的切换即可,如:
DynamicDataSourceHolder.setDataSource("dataSourceTwo");
List<User> list = userMapper.selectAll();
4.使用spring的aop实现注解选择数据源
4.1定义注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSource {
String value();
}
4.2配置aop(基于xml配置文件的方式)
<bean id="dataSourceAspect" class="com.best.core.DataSourceAspect" />
<aop:config>
<aop:aspect ref="dataSourceAspect">
<!-- 拦截所有mapper方法 -->
<aop:pointcut id="dataSourcePointcut" expression="execution(* com.best.mapper.*.*(..))"/>
<aop:before pointcut-ref="dataSourcePointcut" method="intercept" />
</aop:aspect>
</aop:config>
在spring中使用aop除了导入相关jar包,别忘了一下代码
<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:tx="http://www.springframework.org/schema/tx" xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd ">
4.3编写aop的处理类
public class DataSourceAspect {
/**
* 拦截目标方法,获取由@DataSource指定的数据源标识,设置到线程存储中以便切换数据源
*
* @param point
* @throws Exception
*/
public void intercept(JoinPoint point) throws Exception {
Class<?> target = point.getTarget().getClass();
MethodSignature signature = (MethodSignature) point.getSignature();
// 默认使用目标类型的注解,如果没有则使用其实现接口的注解
for (Class<?> clazz : target.getInterfaces()) {
resolveDataSource(clazz, signature.getMethod());
}
resolveDataSource(target, signature.getMethod());
}
/**
* 提取目标对象方法注解和类型注解中的数据源标识
*
* @param clazz
* @param method
*/
private void resolveDataSource(Class<?> clazz, Method method) {
try {
Class<?>[] types = method.getParameterTypes();
DataSource source =null;
// 默认使用类型注解
if (clazz.isAnnotationPresent(DataSource.class)) {
source = clazz.getAnnotation(DataSource.class);
DynamicDataSourceHolder.setDataSource(source.value());
}
// 方法注解可以覆盖类型注解
Method m = clazz.getMethod(method.getName(), types);
if (m != null && m.isAnnotationPresent(DataSource.class)) {
source = m.getAnnotation(DataSource.class);
DynamicDataSourceHolder.setDataSource(source.value());
}
} catch (Exception e) {
System.out.println(clazz + ":" + e.getMessage());
}
}
}
4.4注解的使用,可以在类或者方法上声明,取决于DataSourceAspect类的编写
@DataSource("dataSourceTwo")
public interface UserMapper extends BaseMapper<User, Integer>{
User selectByMobile(String mobile);
int updateByMobileSelective(User user);
List<User> selectStudents();
}
以上是基于xml文件的aop配置
从4.2开始,也可以使用注解的方式配置aop,但是存在一些问题,在这里提一下,不推荐
4.2配置aop
在xml配置文件中加入
<!-- 激活自动代理功能 -->
<aop:aspectj-autoproxy proxy-target-class="true"/>
4.3编写aop处理类
@Component
@Aspect
//@EnableAspectJAutoProxy(proxyTargetClass = true) //若已经在xml中配置此处可以省略
public class DataSourceAspect {
//配置切入点,该方法无方法体,主要为方便同类中其他方法使用此处配置的切入点
@Pointcut("execution(* com.best.mapper.*.*(..))")
public void aspect(){}
/**
* 拦截目标方法,获取由@DataSource指定的数据源标识,设置到线程存储中以便切换数据源
*
* @param point
* @throws Exception
*/
@Before("aspect()")
public void intercept(JoinPoint point) throws Exception {
Class<?> target = point.getTarget().getClass();
MethodSignature signature = (MethodSignature) point.getSignature();
// 默认使用目标类型的注解,如果没有则使用其实现接口的注解
for (Class<?> clazz : target.getInterfaces()) {
resolveDataSource(clazz, signature.getMethod());
}
resolveDataSource(target, signature.getMethod());
}
/**
* 提取目标对象方法注解和类型注解中的数据源标识
*
* @param clazz
* @param method
*/
private void resolveDataSource(Class<?> clazz, Method method) {
try {
Class<?>[] types = method.getParameterTypes();
DataSource source =null;
// 默认使用类型注解
if (clazz.isAnnotationPresent(DataSource.class)) {
source = clazz.getAnnotation(DataSource.class);
DynamicDataSourceHolder.setDataSource(source.value());
}
// 方法注解可以覆盖类型注解
Method m = clazz.getMethod(method.getName(), types);
if (m != null && m.isAnnotationPresent(DataSource.class)) {
source = m.getAnnotation(DataSource.class);
DynamicDataSourceHolder.setDataSource(source.value());
}
} catch (Exception e) {
System.out.println(clazz + ":" + e.getMessage());
}
}
}
使用方法不变
说说这种方式碰到的问题及解决方式:
1. 0 can't find referenced pointcut
这是开发环境jdk版本导致的,见下表
jdk version | spring version | aspectjrt version and aspectjweaver version |
1.6 | 3.0 + | aspectjrt-1.6.2 and aspectjweaver-1.6.2 |
1.7 | 3.0 + | aspectjrt-1.7.3 and aspectjweaver-1.7.3 |
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.7.3</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.7.3</version>
</dependency>
2.Cannot subclass final class class com.sun.proxy.$Proxy16
从Spring3.2以后,spring框架本身不在需要cglib这个jar包了,因为cjlib.jar已经被spring项目的jar包集成进去。为了防止项目中其他对cglib版本依赖不一样的冲突。
所以切换spring版本为4.2.5.RELEASE即可