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实现数据库切换操作就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持。