Spring-基于Spring使用自定义注解及Aspect实现数据库切换操作
实现思路
重写Spring的AbstractRoutingDataSource抽象类的determineCurrentLookupKey方法。
我们来看下Spring-AbstractRoutingDataSource的源码
AbstractRoutingDataSource获取数据源之前会先调用determineCurrentLookupKey方法查找当前的lookupKey。
Object lookupKey = determineCurrentLookupKey(); DataSource dataSource = this.resolvedDataSources.get(lookupKey); ....... return dataSource;
lookupKey为数据源标识,因此通过重写这个查找数据源标识的方法就可以让spring切换到指定的数据源.
从变量定义中可以知道resolvedDataSources为Map类型的对象。
private Map<Object, DataSource> resolvedDataSources;
示例
步骤一 新建Maven工程
依赖如下: pom.xml
<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.0</modelVersion> <groupId>com.artisan</groupId> <artifactId>dynamicDataSource</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>dynamicDataSource</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <file.encoding>UTF-8</file.encoding> <spring.version>4.3.9.RELEASE</spring.version> <servlet.version>3.1.0</servlet.version> <aspectj.version>1.8.1</aspectj.version> <commons-dbcp.version>1.4</commons-dbcp.version> <jetty.version>8.1.8.v20121106</jetty.version> <log4j.version>1.2.17</log4j.version> <log4j2.version>2.8.2</log4j2.version> <testng.version>6.8.7</testng.version> <oracle.version>11.2.0.4.0</oracle.version> <jstl.version>1.2</jstl.version> </properties> <dependencies> <!-- spring 依赖 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>commons-dbcp</groupId> <artifactId>commons-dbcp</artifactId> <version>${commons-dbcp.version}</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>${aspectj.version}</version> </dependency> <dependency> <groupId>org.testng</groupId> <artifactId>testng</artifactId> <version>${testng.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${spring.version}</version> <scope>test</scope> </dependency> <!-- oracle jdbc driver --> <dependency> <groupId>com.oracle</groupId> <artifactId>ojdbc6</artifactId> <version>${oracle.version}</version> </dependency> <dependency> <groupId>org.testng</groupId> <artifactId>testng</artifactId> <version>${testng.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${spring.version}</version> <scope>test</scope> </dependency> <!-- <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>${log4j.version}</version> </dependency> --> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> <version>${log4j2.version}</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>${log4j2.version}</version> </dependency> </dependencies> <build> <!-- 使用JDK1.7编译 --> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.1</version> <configuration> <source>1.7</source> <target>1.7</target> </configuration> </plugin> </plugins> </build> </project>
步骤二 继承AbstractRoutingDataSource并重写determineCurrentLookupKey方法获取特定数据源
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用于持有当前线程中使用的数据源标识
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
<?xml version="1.0" encoding="UTF-8" ?> <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"> <!-- 基类包,将标注Spring注解的类自动转化Bean,同时完成Bean的注入 --> <context:component-scan base-package="com.artisan"/> <!-- 使用context命名空间,在xml文件中配置数据库的properties文件 --> <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}" /> <!-- 主站点cc实例数据源 --> <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> <!-- 指定lookupKey和与之对应的数据源 --> <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> <!-- 配置Jdbc模板 JdbcTemplate使用动态数据源的配置 --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate" p:dataSource-ref="dynamicDataSource" /> <!-- 配置数据源注解的拦截规则,比如拦截service层或者dao层的所有方法,这里拦截了com.artisan下的全部方法 --> <bean id="dataSourceAspect" class="com.artisan.dynamicDB.DataSourceAspect" /> <aop:config> <aop:aspect ref="dataSourceAspect"> <!-- 拦截所有XXX方法 --> <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配置提供事务增强,让com.artisan包下所有Bean的所有方法拥有事务 --> <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的注解
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的线程变量中
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配置文件中配置拦截规则
<!-- 配置数据源注解的拦截规则,比如拦截service层或者dao层的所有方法,这里拦截了com.artisan下的全部方法 --> <bean id="dataSourceAspect" class="com.artisan.dynamicDB.DataSourceAspect" /> <aop:config> <aop:aspect ref="dataSourceAspect"> <!-- 拦截所有XXX方法 --> <aop:pointcut id="dataSourcePointcut" expression="execution(* com.artisan..*(..))"/> <aop:before pointcut-ref="dataSourcePointcut" method="intercept" /> </aop:aspect> </aop:config>
步骤八 使用注解切换多数据源
ExtractDataService.java 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")); } }); } }
步骤九 测试
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
<?xml version="1.0" encoding="UTF-8"?> <!-- log4j2使用说明: 使用方式如下: private static final Logger logger = LogManager.getLogger(实际类名.class.getName()); --> <!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL --> <!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出--> <!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数--> <configuration status="info" monitorInterval="180"> <!-- 文件路径和文件名称,方便后面引用 --> <Properties> <Property name="backupFilePatch">D:/workspace/workspace-sts/backupOracle/log/</Property> <Property name="fileName">backupOracle.log</Property> </Properties> <!--先定义所有的appender--> <appenders> <!--这个输出控制台的配置--> <Console name="Console" target="SYSTEM_OUT"> <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)--> <ThresholdFilter level="trace" onMatch="ACCEPT" onMismatch="DENY" /> <!-- 输出日志的格式--> <PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %class{36} %L %M - %msg%xEx%n" /> </Console> <!--这个会打印出所有的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档--> <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属性如不设置,则默认为最多同一文件夹下7个文件,这里设置了20 --> <DefaultRolloverStrategy max="20"/> </RollingFile> </appenders> <!--然后定义logger,只有定义了logger并引入的appender,appender才会生效--> <loggers> <!--过滤掉spring和mybatis的一些无用的DEBUG信息--> <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
########################## ## ## ## 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
以上这篇Spring-基于Spring使用自定义注解及Aspect实现数据库切换操作就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持我们。