一、简要概述
在做项目的时候遇到需要从两个数据源获取数据,项目使用的Spring + Mybatis环境,看到网上有一些关于多数据源的配置,自己也整理学习一下,然后自动切换实现从不同的数据源获取数据功能。
二、代码详解
2.1 DataSourceConstants 数据源常量类
/**
* 数据源名称常量类
* 对应 application. 中 bean multipleDataSource
* @author:dufy
* @version:1.0.0
* @date 2018/12/17
*/
public class DataSourceConstants {
/**
* 数据源1,默认数据源配置
*/
public static final String DATASOURCE_1 = \"dataSource1\";
/**
* 数据源2
*/
public static final String DATASOURCE_2 = \"dataSource2\";
}
2.2 DataSourceType 自定义数据源注解
/**
* 自定义数据源类型注解
*
* @author:dufy
* @version:1.0.0
* @date 2018/12/17
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSourceType {
String value() default DataSourceConstants.DATASOURCE_1;
}
2.3 MultipleDataSource 多数据源配置类
MultipleDataSource 继承 AbstractRoutingDataSource 类,为什么继承这个类就可以了?请看 第五章 :实现原理。
import org.spring work.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* 自定义多数据源配置类
*
* @author:dufy
* @version:1.0.0
* @date 2018/12/17
*/
public class MultipleDataSource extends AbstractRoutingDataSource {
private static final ThreadLocal<String> dataSourceHolder = new ThreadLocal<String>();
/**
* 设置数据源
* @param dataSource 数据源名称
*/
public static void setDataSource(String dataSource){
dataSourceHolder.set(dataSource);
}
/**
* 获取数据源
* @return
*/
public static String getDatasource() {
return dataSourceHolder.get();
}
/**
* 清除数据源
*/
public static void clearDataSource(){
dataSourceHolder.remove();
}
@Override
protected determineCurrentLookupKey() {
return dataSourceHolder.get();
}
}
2.4 MultipleDataSourceAop 多数据源自动切换通知类
注意:请设置 @Order(0)。否则可能出现 数据源切换失败问题! 因为要在事务开启之前就进行判断,并进行切换数据源!
/**
* 多数据源自动切换通知类<br>
* <p>
* 首先判断当前类是否被该DataSourceType注解进行注释,如果没有指定注解,则采用默认的数据源配置; <br>
* 如果有,则读取注解中的value值,将数据源切到value指定的数据源
*
* @author:dufy
* @version:1.0.0
* @date 2018/12/17
*/
@Aspect // for aop
@Component // for auto scan
@Order(0) // execute before @Transactional
public class MultipleDataSourceAop {
private final Logger logger = Logger.getLogger(MultipleDataSourceAop.class);
/**
* 拦截 com.**.servicee中所有的方法,根据配置情况进行数据源切换
* com.jiuling.tz.service
* com.jiuling.web.service
* @param joinPoint
* @throws Throwable
*/
@Before(\"execution(* com.dufy.*.service.*.*(..))\")
public void changeDataSource(JoinPoint joinPoint) throws Throwable {
try {
// 拦截的实体类,就是当前正在执行的service
Class<?> clazz = joinPoint.getTarget().getClass();
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
// 提取目标对象方法注解和类型注解中的数据源标识
Class<?>[] types = method.getParameterTypes();
if (clazz.isAnnotationPresent(DataSourceType.class)) {
DataSourceType source = clazz.getAnnotation(DataSourceType.class);
MultipleDataSource.setDataSource(source.value());
logger.info(\"Service Class 数据源切换至--->\" + source.value());
}
Method m = clazz.getMethod(method.getName(), types);
if (m != null && m.isAnnotationPresent(DataSourceType.class)) {
DataSourceType source = m.getAnnotation(DataSourceType.class);
MultipleDataSource.setDataSource(source.value());
logger.info(\"Service Method 数据源切换至--->\" + source.value());
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 方法结束后
*/
@After(\"execution(* com.dufy.*.service.*.*(..))\")
public void afterReturning() throws Throwable {
try {
MultipleDataSource.clearDataSource();
logger.debug(\"数据源已移除!\");
} catch (Exception e) {
e.printStackTrace();
logger.debug(\"数据源移除报错!\");
}
}
}
三、配置详情
在 applicationContext. 中配置详情
<? version=\"1.0\" encoding=\"UTF-8\"?>
<beans ns=\"http://www.spring work.org/schema/beans\" ns:context=\"http://www.spring work.org/schema/context\"
ns:xsi=\"http://www.w3.org/2001/ Schema-instance\" ns:aop=\"http://www.spring work.org/schema/aop\"
ns:tx=\"http://www.spring work.org/schema/tx\" ns:p=\"http://www.spring work.org/schema/p\"
ns:util=\"http://www.spring work.org/schema/util\" ns:jdbc=\"http://www.spring work.org/schema/jdbc\"
ns:cache=\"http://www.spring work.org/schema/cache\"
xsi:schemaLocation=\"
http://www.spring work.org/schema/context
http://www.spring work.org/schema/context/spring-context.xsd
http://www.spring work.org/schema/beans
http://www.spring work.org/schema/beans/spring-beans.xsd
http://www.spring work.org/schema/tx
http://www.spring work.org/schema/tx/spring-tx.xsd
http://www.spring work.org/schema/jdbc
http://www.spring work.org/schema/jdbc/spring-jdbc.xsd
http://www.spring work.org/schema/cache
http://www.spring work.org/schema/cache/spring-cache.xsd
http://www.spring work.org/schema/aop
http://www.spring work.org/schema/aop/spring-aop.xsd
http://www.spring work.org/schema/util
http://www.spring work.org/schema/util/spring-util.xsd\">
<!-- 自动扫描包 ,将带有注解的类 纳入spring容器管理 -->
<context:component-scan -package=\"com.dufy\"></context:component-scan>
<!-- 引入配置文件 -->
<bean id=\"propertyConfigurer\" class=\"org.spring work.beans.factory.config.PropertyPlaceholderConfigurer\">
<property name=\"locations\">
<list>
<value>classpath*:jdbc.properties</value>
</list>
</property>
</bean>
<!-- dataSource1 配置 -->
<bean id=\"dataSource1\" class=\"com.alibaba.druid.pool.DruidDataSource\" init-method=\"init\" destroy-method=\"close\">
<!-- 基本属性 url、user、password -->
<property name=\"url\" value=\"${jdbc.url}\"/>
<property name=\"username\" value=\"${jdbc.username}\"/>
<property name=\"password\" value=\"${jdbc.password}\"/>
<!-- 配置初始化大小、最小、最大 -->
<property name=\"initialSize\" value=\"${ds.initialSize}\"/>
<property name=\"minIdle\" value=\"${ds.minIdle}\"/>
<property name=\"maxActive\" value=\"${ds.maxActive}\"/>
<!-- 配置获取连接等待超时的时间 -->
<property name=\"maxWait\" value=\"${ds.maxWait}\"/>
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name=\"timeBetweenEvictionRunsMillis\" value=\"${ds.timeBetweenEvictionRunsMillis}\"/>
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name=\"minEvictableIdleTimeMillis\" value=\"${ds.minEvictableIdleTimeMillis}\"/>
<property name=\"validationQuery\" value=\"SELECT \'x\'\"/>
<property name=\"testWhileIdle\" value=\"true\"/>
<property name=\"testOnBorrow\" value=\"false\"/>
<property name=\"testOnReturn\" value=\"false\"/>
<!-- 打开PSCache,并且指定每个连接上PSCache的大小 -->
<property name=\"poolPreparedStatements\" value=\"false\"/>
<property name=\"maxPoolPreparedStatementPerConnectionSize\" value=\"20\"/>
<!-- 配置监控统计拦截的filters -->
<property name=\"filters\" value=\"stat\"/>
</bean>
<!-- dataSource2 配置-->
<bean id=\"dataSource2\" class=\"com.alibaba.druid.pool.DruidDataSource\" init-method=\"init\" destroy-method=\"close\">
<!-- 基本属性 url、user、password -->
<property name=\"url\" value=\"${jd.jdbc.url}\"/>
<property name=\"username\" value=\"${jd.jdbc.username}\"/>
<property name=\"password\" value=\"${jd.jdbc.password}\"/>
<!-- 其他配置省略 -->
</bean>
<!--多数据源配置-->
<bean id=\"multipleDataSource\" class=\"com.jiuling.core.ds.MultipleDataSource\">
<property name=\"defaultTargetDataSource\" ref=\"dataSource1\" />
<property name=\"targetDataSources\">
<map key-type = \"java.lang.String\">
<entry key=\"dataSource1\" value-ref=\"dataSource1\"/>
<entry key=\"dataSource2\" value-ref=\"dataSource2\"/>
<!-- 这里还可以加多个dataSource -->
</map>
</property>
</bean>
<!-- mybatis文件配置,扫描所有mapper文件 -->
<bean id=\"sqlSessionFactory\" class=\"org.mybatis.spring.SqlSessionFactoryBean\" p:dataSource-ref=\"multipleDataSource\"
p:configLocation=\"classpath:mybatis-config. \"
p:mapperLocations=\"classpath:com/dufy/*/dao/*. \"/>
<!-- spring与mybatis整合配置,扫描所有dao -->
<bean class=\"org.mybatis.spring.mapper.MapperScannerConfigurer\" p: Package=\"com.dufy.*.dao\"
p:sqlSessionFactoryBeanName=\"sqlSessionFactory\"/>
<!-- 对dataSource 数据源进行事务管理 -->
<bean id=\"transactionManager\" class=\"org.spring work.jdbc.datasource.DataSourceTransactionManager\"
p:dataSource-ref=\"multipleDataSource\"/>
<!-- 事务管理 通知 -->
<tx:advice id=\"txAdvice\" transaction-manager=\"transactionManager\">
<tx:attributes>
<!-- 对insert,update,delete 开头的方法进行事务管理,只要有异常就回滚 -->
<tx:method name=\"insert*\" propagation=\"REQUIRED\" rollback-for=\"java.lang.Throwable\"/>
<tx:method name=\"update*\" propagation=\"REQUIRED\" rollback-for=\"java.lang.Throwable\"/>
<tx:method name=\"delete*\" propagation=\"REQUIRED\" rollback-for=\"java.lang.Throwable\"/>
<!-- select,count开头的方法,开启只读,提高数据库访问性能 -->
<tx:method name=\"select*\" read-only=\"true\"/>
<tx:method name=\"count*\" read-only=\"true\"/>
<!-- 对其他方法 使用默认的事务管理 -->
<tx:method name=\"*\"/>
</tx:attributes>
</tx:advice>
<!-- 事务 aop 配置 -->
<aop:config>
<aop:pointcut id=\"serviceMethods\" =\"execution(* com.dufy.*.service..*(..))\"/>
<aop:advisor advice-ref=\"txAdvice\" pointcut-ref=\"serviceMethods\"/>
</aop:config>
<!-- 配置使Spring采用CGLIB代理 -->
<aop:aspectj-autoproxy proxy-target-class=\"true\"/>
<!-- 启用对事务注解的支持 -->
<tx:annotation-driven transaction-manager=\"transactionManager\"/>
</beans>
jdbc.properties 配置内容
##-------------mysql数据库连接配置 ---------------------###
# dataSource1
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://192.168.1.110:3306/jdsc?useUnicode=true&characterEncoding=utf-8
jdbc.username=test
jdbc.password=123456
#配置初始化大小、最小、最大
ds.initialSize=1
ds.minIdle=1
ds.maxActive=20
#配置获取连接等待超时的时间
ds.maxWait=60000
#配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
ds.timeBetweenEvictionRunsMillis=60000
#配置一个连接在池中最小生存的时间,单位是毫秒
ds.minEvictableIdleTimeMillis=300000
# dataSource2
jd.jdbc.url=jdbc:mysql://192.168.1.120:3306/jdsc?useUnicode=true&characterEncoding=utf-8
jd.jdbc.username=root
jd.jdbc.password=123456
四、测试切换
注意:测试服务类的包路径,因为只有被AOP拦截的到的指定的Service才会进行数据源的切换。
package com.dufy.web.service.impl.Data1ServiceImpl;
/**
* 使用 dataSourc1 ,配置dataSourc1的数据源
* @author:duf
* @version:1.0.0
* @date 2018/12/17
*/
@Service
@DataSourceType(value = DataSourceConstants.DATASOURCE_1)
public class Data1ServiceImpl implements Data1Service {
@Resource
private Data1Mapper data1Mapper;
@Override
public List<String> selectCaseByUpdateTime(String name) {
List<String> data1 = data1Mapper.selectData1(name);
return data1;
}
}
package com.dufy.web.service.impl.Data2ServiceImpl;
/**
* 使用 dataSourc2 ,配置dataSourc2的数据源
* @author:duf
* @version:1.0.0
* @date 2018/12/17
*/
@Service
@DataSourceType(value = DataSourceConstants.DATASOURCE_2)
public class Data2ServiceImpl implements Data2Service {
@Resource
private Data2Mapper data2Mapper;
@Override
public List<String> selectCaseByUpdateTime(String name) {
List<String> data2 = data2Mapper.selectData2(name);
return data2;
}
}
通过测试后发现,两个Service服务器分别调用自己的配置的数据源进行数据的获取!
五、实现原理
基于AbstractRoutingDataSource 实现 多数据源配置,通过AOP来进行数据源的灵活切换。AOP的相关原理这里不做说明,就简单
继续阅读与本文标签相同的文章
上一篇 :
数据智能:工具之王
-
RocketMQ消息轨迹-设计篇
2026-05-18栏目: 教程
-
前端进阶|第七天 携程笔试题,嵌套数组去层级去重并排序
2026-05-18栏目: 教程
-
阿里巴巴“新六脉神剑”背后的故事
2026-05-18栏目: 教程
-
阿里巴巴20周年年会结束以后,你知道发生了什么吗?
2026-05-18栏目: 教程
-
13年IT老兵:闷头做智能家居体系容易走火入魔
2026-05-18栏目: 教程
