文章目录
- 实现思路
- 示例
- 步骤一 新建Maven工程
- 步骤二 继承AbstractRoutingDataSource并重写determineCurrentLookupKey方法获取特定数据源
- 步骤三 创建DynamicDataSourceHolder用于持有当前线程中使用的数据源标识
- 步骤四 配置多个数据源和DynamicDataSource的bean
- 步骤五 定义名为@DataSource的注解
- 步骤六 定义AOP切面以便拦截所有带有注解@DataSource的方法,取出注解的值作为数据源标识放到DynamicDataSourceHolder的线程变量中
- 步骤七 在spring配置文件中配置拦截规则
- 步骤八 使用注解切换多数据源
- 步骤九 测试
- 其他代码
- log4j2.xml
- jdbc.properties
- 运行结果:
- 代码
实现思路
重写Spring的AbstractRoutingDataSource抽象类的determineCurrentLookupKey方法。
我们来看下Spring-AbstractRoutingDataSource的源码
AbstractRoutingDataSource获取数据源之前会先调用determineCurrentLookupKey方法查找当前的lookupKey。
代码语言:javascript复制Object lookupKey = determineCurrentLookupKey();
DataSource dataSource = this.resolvedDataSources.get(lookupKey);
.......
return dataSource;
lookupKey为数据源标识,因此通过重写这个查找数据源标识的方法就可以让spring切换到指定的数据源.
从变量定义中可以知道resolvedDataSources为Map类型的对象。
代码语言:javascript复制private Map<Object, DataSource> resolvedDataSources;
示例
步骤一 新建Maven工程
依赖如下: pom.xml
代码语言:javascript复制<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>com.artisangroupId>
<artifactId>dynamicDataSourceartifactId>
<version>0.0.1-SNAPSHOTversion>
<packaging>jarpackaging>
<name>dynamicDataSourcename>
<url>http://maven.apache.orgurl>
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<file.encoding>UTF-8file.encoding>
<spring.version>4.3.9.RELEASEspring.version>
<servlet.version>3.1.0servlet.version>
<aspectj.version>1.8.1aspectj.version>
<commons-dbcp.version>1.4commons-dbcp.version>
<jetty.version>8.1.8.v20121106jetty.version>
<log4j.version>1.2.17log4j.version>
<log4j2.version>2.8.2log4j2.version>
<testng.version>6.8.7testng.version>
<oracle.version>11.2.0.4.0oracle.version>
<jstl.version>1.2jstl.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-beansartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-context-supportartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-jdbcartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webmvcartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>commons-dbcpgroupId>
<artifactId>commons-dbcpartifactId>
<version>${commons-dbcp.version}version>
dependency>
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>${aspectj.version}version>
dependency>
<dependency>
<groupId>org.testnggroupId>
<artifactId>testngartifactId>
<version>${testng.version}version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-testartifactId>
<version>${spring.version}version>
<scope>testscope>
dependency>
<dependency>
<groupId>com.oraclegroupId>
<artifactId>ojdbc6artifactId>
<version>${oracle.version}version>
dependency>
<dependency>
<groupId>org.testnggroupId>
<artifactId>testngartifactId>
<version>${testng.version}version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-testartifactId>
<version>${spring.version}version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-apiartifactId>
<version>${log4j2.version}version>
dependency>
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-coreartifactId>
<version>${log4j2.version}version>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<version>3.1version>
<configuration>
<source>1.7source>
<target>1.7target>
configuration>
plugin>
plugins>
build>
project>
步骤二 继承AbstractRoutingDataSource并重写determineCurrentLookupKey方法获取特定数据源
代码语言:javascript复制package com.artisan.dynamicDB;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
*
*
* @ClassName: DynamicDataSource
*
* @Description:
* AbstractRoutingDataSource中的抽象方法determineCurrentLookupKey是实现数据源的route的核心
* .需要重写该方法
*
* @author: Mr.Yang
*
* @date: 2017年7月24日 下午8:28:46
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceHolder.getDataSource();
}
}
步骤三 创建DynamicDataSourceHolder用于持有当前线程中使用的数据源标识
代码语言:javascript复制package com.artisan.dynamicDB;
/**
*
*
* @ClassName: DynamicDataSourceHolder
*
* @Description:创建DynamicDataSourceHolder用于持有当前线程中使用的数据源标识
*
* @author: Mr.Yang
*
* @date: 2017年7月24日 下午8:23:50
*/
public class DynamicDataSourceHolder {
/**
* 数据源标识保存在线程变量中,避免多线程操作数据源时互相干扰
*/
private static final ThreadLocal<String> dataSourceHolder = new ThreadLocal<String>();
/**
*
*
* @Title: setDataSource
*
* @Description: 设置数据源
*
* @param dataSource
*
* @return: void
*/
public static void setDataSource(String dataSource) {
dataSourceHolder.set(dataSource);
}
/**
*
*
* @Title: getDataSource
*
* @Description: 获取数据源
*
* @return
*
* @return: String
*/
public static String getDataSource() {
return dataSourceHolder.get();
}
/**
*
*
* @Title: clearDataSource
*
* @Description: 清除数据源
*
*
* @return: void
*/
public static void clearDataSource() {
dataSourceHolder.remove();
}
}
步骤四 配置多个数据源和DynamicDataSource的bean
代码语言:javascript复制<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util.xsd">
<context:component-scan base-package="com.artisan"/>
<context:property-placeholder location="classpath:jdbc.properties" />
<bean id="dataSourcePR" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"
p:driverClassName="${jdbc.driverClassNamePR}"
p:url="${jdbc.urlPR}"
p:username="${jdbc.usernamePR}"
p:password="${jdbc.passwordPR}" />
<bean id="dataSourceDR" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"
p:driverClassName="${jdbc.driverClassNameDR}"
p:url="${jdbc.urlDR}"
p:username="${jdbc.usernameDR}"
p:password="${jdbc.passwordDR}" />
<bean id="dataSourceCC" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"
p:driverClassName="${jdbc.driverClassNameCC}"
p:url="${jdbc.urlCC}"
p:username="${jdbc.usernameCC}"
p:password="${jdbc.passwordCC}" />
<bean id="dynamicDataSource" class="com.artisan.dynamicDB.DynamicDataSource">
<property name="targetDataSources" ref="dynamicDatasourceMap" />
<property name="defaultTargetDataSource" ref="dataSourcePR" />
bean>
<util:map id="dynamicDatasourceMap" key-type="java.lang.String">
<entry key="dataSourcePR" value-ref="dataSourcePR" />
<entry key="dataSourceDR" value-ref="dataSourceDR" />
<entry key="dataSourceCC" value-ref="dataSourceCC" />
util:map>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"
p:dataSource-ref="dynamicDataSource" />
<bean id="dataSourceAspect" class="com.artisan.dynamicDB.DataSourceAspect" />
<aop:config>
<aop:aspect ref="dataSourceAspect">
<aop:pointcut id="dataSourcePointcut" expression="execution(* com.artisan..*(..))"/>
<aop:before pointcut-ref="dataSourcePointcut" method="intercept" />
aop:aspect>
aop:config>
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
p:dataSource-ref="dynamicDataSource" />
<aop:config proxy-target-class="true">
<aop:pointcut id="serviceMethod"
expression="(execution(* com.artisan..*(..))) and (@annotation(org.springframework.transaction.annotation.Transactional))" />
<aop:advisor pointcut-ref="serviceMethod" advice-ref="txAdvice" />
aop:config>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" />
tx:attributes>
tx:advice>
beans>
配置到这里,我们就可以使用多个数据源了,只需要在操作数据库之前只要DynamicDataSourceHolder.setDataSource(“dataSourcePR”)即可切换到数据源dataSourcePR并对数据库dataSourcePR进行操作了。
问题:每次使用都需要调用DynamicDataSourceHolder#setDataSource,十分繁琐,并且难以维护。
我们可以通过Spring的AOP和注解, 直接通过注解的方式指定需要访问的数据源。 继续改进下吧
步骤五 定义名为@DataSource的注解
代码语言:javascript复制package com.artisan.dynamicDB;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
*
*
* @ClassName: DataSource
*
*
* @Description: 注解@DataSource既可以加在方法上,也可以加在接口或者接口的实现类上,优先级别:方法>实现类>接口。
* 如果接口、接口实现类以及方法上分别加了@DataSource注解来指定数据源,则优先以方法上指定的为准。
*
* @author: Mr.Yang
*
* @date: 2017年7月24日 下午9:59:29
*/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
// 和配置文件中 dynamicDatasourceMap中的key保持一致
public static String PR_RB = "dataSourcePR";
public static String DR_RB = "dataSourceDR";
public static String PR_CC = "dataSourceCC";
/**
*
*
* @Title: name
*
* @Description: 如果仅标注@DataSource 默认为PR_RB数据库实例
*
* @return
*
* @return: String
*/
String name() default DataSource.PR_RB;
}
步骤六 定义AOP切面以便拦截所有带有注解@DataSource的方法,取出注解的值作为数据源标识放到DynamicDataSourceHolder的线程变量中
代码语言:javascript复制package com.artisan.dynamicDB;
import java.lang.reflect.Method;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
/**
*
*
* @ClassName: DataSourceAspect
*
* @Description:
* 定义AOP切面以便拦截所有带有注解@DataSource的方法,取出注解的值作为数据源标识放到DBContextHolder的线程变量中
*
* @author: Mr.Yang
*
* @date: 2017年7月25日 上午10:51:41
*/
public class DataSourceAspect {
/**
*
*
* @Title: intercept
*
* @Description: 拦截目标方法,获取由@DataSource指定的数据源标识,设置到线程存储中以便切换数据源
*
* @param point
* @throws Exception
*
* @return: void
*/
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());
}
/**
*
*
* @Title: resolveDataSource
*
* @Description: 提取目标对象方法注解和类型注解中的数据源标识
*
* @param clazz
* @param method
*
* @return: void
*/
private void resolveDataSource(Class<?> clazz, Method method) {
try {
Class<?>[] types = method.getParameterTypes();
// 默认使用类型注解
if (clazz.isAnnotationPresent(DataSource.class)) {
DataSource source = clazz.getAnnotation(DataSource.class);
DynamicDataSourceHolder.setDataSource(source.name());
}
// 方法注解可以覆盖类型注解
Method m = clazz.getMethod(method.getName(), types);
if (m != null && m.isAnnotationPresent(DataSource.class)) {
DataSource source = m.getAnnotation(DataSource.class);
DynamicDataSourceHolder.setDataSource(source.name());
}
} catch (Exception e) {
System.out.println(clazz ":" e.getMessage());
}
}
}
步骤七 在spring配置文件中配置拦截规则
代码语言:javascript复制 <bean id="dataSourceAspect" class="com.artisan.dynamicDB.DataSourceAspect" />
<aop:config>
<aop:aspect ref="dataSourceAspect">
<aop:pointcut id="dataSourcePointcut" expression="execution(* com.artisan..*(..))"/>
<aop:before pointcut-ref="dataSourcePointcut" method="intercept" />
aop:aspect>
aop:config>
步骤八 使用注解切换多数据源
ExtractDataService.java
代码语言:javascript复制package com.artisan.extractService;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowCallbackHandler;
import org.springframework.stereotype.Service;
import com.artisan.dynamicDB.DataSource;
/**
*
*
* @ClassName: ExtractDataService
*
* @Description: 业务类,这里暂时作为测试多数据源切换用
*
* @author: Mr.Yang
*
* @date: 2017年7月24日 下午9:07:38
*/
@Service
public class ExtractDataService {
private static final Logger logger = LogManager
.getLogger(ExtractDataService.class.getName());
private JdbcTemplate jdbcTemplate;
@Autowired
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
/**
*
*
* @Title: selectDataFromPR
*
* @Description:
*
*
* @return: void
*/
@DataSource(name = DataSource.PR_RB)
public void selectDataFromPR_RB() {
String sql = "select subs_id from owe_event_charge where event_inst_id = 10229001 ";
jdbcTemplate.query(sql, new RowCallbackHandler() {
@Override
public void processRow(ResultSet rs) throws SQLException {
logger.info(rs.getInt("subs_id"));
}
});
}
@DataSource(name = DataSource.DR_RB)
public void selectDataFromDR_RB() {
// 改为通过注解指定DB
// DynamicDataSourceHolder.setDataSource(DBContextHolder.DATA_SOURCE_DR);
String sql = " select a.task_comments from nm_task_type a where a.task_name = 'ALARM_LOG_LEVEL' ";
jdbcTemplate.query(sql, new RowCallbackHandler() {
@Override
public void processRow(ResultSet rs) throws SQLException {
logger.info(rs.getString("task_comments"));
}
});
}
@DataSource(name = DataSource.PR_CC)
public void selectDataFromPR_CC() {
// DBContextHolder.setDataSource(DBContextHolder.DATA_SOURCE_CC);
String sql = "select acc_nbr from acc_nbr where acc_nbr_id = 82233858 ";
jdbcTemplate.query(sql, new RowCallbackHandler() {
@Override
public void processRow(ResultSet rs) throws SQLException {
logger.info(rs.getString("acc_nbr"));
}
});
}
}
步骤九 测试
代码语言:javascript复制package com.artisan;
import java.io.IOException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.LoggerContext;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import com.artisan.extractService.ExtractDataService;
/**
*
*
* @ClassName: App
*
* @Description: 入口类
*
* @author: Mr.Yang
*
* @date: 2017年7月24日 下午8:50:25
*/
public class App {
public static void main(String[] args) {
try {
// 加载日志框架 log4j2
LoggerContext context = (LoggerContext) LogManager
.getContext(false);
ResourceLoader loader = new PathMatchingResourcePatternResolver();
Resource resource = loader.getResource("classpath:log4j2.xml");
context.setConfigLocation(resource.getFile().toURI());
// 加载spring配置信息
ApplicationContext ctx = new ClassPathXmlApplicationContext(
"classpath:spring-context.xml");
// 从容器中获取Bean
ExtractDataService service = ctx.getBean("extractDataService",
ExtractDataService.class);
// 从PR的RB实例中获取数据
service.selectDataFromPR_RB();
// 从DR的RB实例中获取数据
service.selectDataFromDR_RB();
// 从PR的CC实例中获取数据
service.selectDataFromPR_CC();
} catch (IOException e) {
e.printStackTrace();
}
}
}
其他代码
log4j2.xml
代码语言:javascript复制<configuration status="info" monitorInterval="180">
<Properties>
<Property name="backupFilePatch">D:/workspace/workspace-sts/backupOracle/log/Property>
<Property name="fileName">backupOracle.logProperty>
Properties>
<appenders>
<Console name="Console" target="SYSTEM_OUT">
<ThresholdFilter level="trace" onMatch="ACCEPT" onMismatch="DENY" />
<PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n" />
Console>
<RollingFile name="RollingFile" fileName="${backupFilePatch}${fileName}"
filePattern="${backupFilePatch}$${date:yyyy-MM}/app-%d{yyyyMMddHHmmssSSS}.log.gz">
<PatternLayout
pattern="%d{yyyy.MM.dd 'at' HH:mm:ss.SSS z} %-5level %class{36} %L %M - %msg%xEx%n" />
<SizeBasedTriggeringPolicy size="20MB" />
<DefaultRolloverStrategy max="20"/>
RollingFile>
appenders>
<loggers>
<logger name="org.springframework" level="INFO">logger>
<logger name="org.mybatis" level="INFO">logger>
<root level="trace">
<appender-ref ref="RollingFile"/>
<appender-ref ref="Console"/>
root>
loggers>
configuration>
jdbc.properties
代码语言:javascript复制##########################
##
##
## dbcp datasource pool ,basic configuration first.
## the other parameters keep default for now , you can change them if you want
##
##
##########################
#Database in Lapaz
jdbc.driverClassNamePR=oracle.jdbc.driver.OracleDriver
jdbc.urlPR=jdbc:oracle:thin:@172.25.243.4:1521:xx
jdbc.usernamePR=xxx
jdbc.passwordPR=xxxxxxxx
#Database in Scluz
jdbc.driverClassNameDR=oracle.jdbc.driver.OracleDriver
jdbc.urlDR=jdbc:oracle:thin:@172.25.246.1:1521:xx
jdbc.usernameDR=xxx
jdbc.passwordDR=xxxxxxx
#Database in Lapaz
jdbc.driverClassNameCC=oracle.jdbc.driver.OracleDriver
jdbc.urlCC=jdbc:oracle:thin:@172.25.243.3:1521:xx
jdbc.usernameCC=xxx
jdbc.passwordCC=xxxxxx
运行结果:
代码
https://github.com/yangshangwei/DynamicDataSource