ssm maven spring AOP读写分离
总体流程
配置最开始写在pom.xml文件,解析到数据库配置文件,再解析到spring配置文件。
自定义注解DataSource;通过这个注解并且在spring、springmv配置文件添加AOP拦截,去定义拦截函数,根据参数切换数据源。
即通过注解实现AOP拦截controller,或者service层。从而实现读写分离。
具体见代码和注释。
1,pom.xml 配置数据库部分
<profile> <id>local</id> <properties> <db-url><![CDATA[jdbc:mysql://XXX?autoReconnect=true&autoReconnectForPools=true&useUnicode=true&characterEncoding=utf8]]></db-url> <db-driverClassName>com.mysql.jdbc.Driver</db-driverClassName> <db-username>XXX</db-username> <db-password>XXX</db-password> <aliyun.oss.endpoint><![CDATA[oss-cn-beijing.aliyuncs.com]]></aliyun.oss.endpoint> <aliyun_oss_endpointURL><![CDATA[http://oss-cn-beijing.aliyuncs.com]]></aliyun_oss_endpointURL> <aliyun.oss.accessKeyId>LTAIz0kIEQRSBc6G</aliyun.oss.accessKeyId> <aliyun.oss.accessKeySecret>3YS3fXPvIHng11ft7wQRGDeojP2Neg</aliyun.oss.accessKeySecret> <aliyun.oss.templateInputBucket>iceplay</aliyun.oss.templateInputBucket> <aliyun.oss.templateResultBucket>iceplay</aliyun.oss.templateResultBucket> <aliyun.oss.templatePicBucket>iceplay</aliyun.oss.templatePicBucket> <aliyun.oss.picTaskInputBucket>iceplay</aliyun.oss.picTaskInputBucket> <aliyun.oss.picTaskResultBucket>iceplay</aliyun.oss.picTaskResultBucket> <aliyun.oss.avatorBucket>iceplay</aliyun.oss.avatorBucket> <aliyun.oss.adviceBucket>iceplay</aliyun.oss.adviceBucket> <aliyun.oss.fontBucket>iceplay</aliyun.oss.fontBucket> <aliyun.oss.picimgPath>app-image</aliyun.oss.picimgPath> <aliyun.oss.watermark></aliyun.oss.watermark> <aliyun.mq.accessKey>LTAItRByuljwgIgI</aliyun.mq.accessKey> <aliyun.mq.secretKey>fauyIEV8LMwSj8alFYUqEXeU5VMfwY</aliyun.mq.secretKey> <aliyun.mq.sendMsgTimeoutMillis>3000</aliyun.mq.sendMsgTimeoutMillis> <aliyun.mq.templateTaskProducerId>PID-Template-Task</aliyun.mq.templateTaskProducerId> <aliyun.mq.consumerId>CID-AppServer</aliyun.mq.consumerId> <aliyun.mq.topicTemplateTask>template-tasks</aliyun.mq.topicTemplateTask> <aliyun.mq.topicTemplateResult>template-results</aliyun.mq.topicTemplateResult> <aliyun.mq.topicImageCreateTask>pic-tasks</aliyun.mq.topicImageCreateTask> <aliyun.mq.topicImageCreateResult>pic-results</aliyun.mq.topicImageCreateResult> <aliyun.mq.imageCreateTaskProducerId>PID-Pic-Task</aliyun.mq.imageCreateTaskProducerId> <aliyun.mq.topicServerStatus>server-status</aliyun.mq.topicServerStatus> <aliyun.mq.serverConfigProducerId>PID-Manage</aliyun.mq.serverConfigProducerId> <aliyun.mq.topicServerConfig>server-config</aliyun.mq.topicServerConfig> </properties> <activation> <activeByDefault>true</activeByDefault> </activation> </profile> <profile> <id>localSalve</id> <properties> <dbs-url><![CDATA[jdbc:mysql://XXX?autoReconnect=true&autoReconnectForPools=true&useUnicode=true&characterEncoding=utf8]]></dbs-url> <dbs-driverClassName>com.mysql.jdbc.Driver</dbs-driverClassName> <dbs-username>XXX</dbs-username> <dbs-password>XXX</dbs-password> <aliyun.oss.endpoint><![CDATA[oss-cn-beijing.aliyuncs.com]]></aliyun.oss.endpoint> <aliyun_oss_endpointURL><![CDATA[http://oss-cn-beijing.aliyuncs.com]]></aliyun_oss_endpointURL> <aliyun.oss.accessKeyId>LTAIz0kIEQRSBc6G</aliyun.oss.accessKeyId> <aliyun.oss.accessKeySecret>3YS3fXPvIHng11ft7wQRGDeojP2Neg</aliyun.oss.accessKeySecret> <aliyun.oss.templateInputBucket>iceplay</aliyun.oss.templateInputBucket> <aliyun.oss.templateResultBucket>iceplay</aliyun.oss.templateResultBucket> <aliyun.oss.templatePicBucket>iceplay</aliyun.oss.templatePicBucket> <aliyun.oss.picTaskInputBucket>iceplay</aliyun.oss.picTaskInputBucket> <aliyun.oss.picTaskResultBucket>iceplay</aliyun.oss.picTaskResultBucket> <aliyun.oss.avatorBucket>iceplay</aliyun.oss.avatorBucket> <aliyun.oss.adviceBucket>iceplay</aliyun.oss.adviceBucket> <aliyun.oss.fontBucket>iceplay</aliyun.oss.fontBucket> <aliyun.oss.picimgPath>app-image</aliyun.oss.picimgPath> <aliyun.oss.watermark></aliyun.oss.watermark> <aliyun.mq.accessKey>LTAItRByuljwgIgI</aliyun.mq.accessKey> <aliyun.mq.secretKey>fauyIEV8LMwSj8alFYUqEXeU5VMfwY</aliyun.mq.secretKey> <aliyun.mq.sendMsgTimeoutMillis>3000</aliyun.mq.sendMsgTimeoutMillis> <aliyun.mq.templateTaskProducerId>PID-Template-Task</aliyun.mq.templateTaskProducerId> <aliyun.mq.consumerId>CID-AppServer</aliyun.mq.consumerId> <aliyun.mq.topicTemplateTask>template-tasks</aliyun.mq.topicTemplateTask> <aliyun.mq.topicTemplateResult>template-results</aliyun.mq.topicTemplateResult> <aliyun.mq.topicImageCreateTask>pic-tasks</aliyun.mq.topicImageCreateTask> <aliyun.mq.topicImageCreateResult>pic-results</aliyun.mq.topicImageCreateResult> <aliyun.mq.imageCreateTaskProducerId>PID-Pic-Task</aliyun.mq.imageCreateTaskProducerId> <aliyun.mq.topicServerStatus>server-status</aliyun.mq.topicServerStatus> <aliyun.mq.serverConfigProducerId>PID-Manage</aliyun.mq.serverConfigProducerId> <aliyun.mq.topicServerConfig>server-config</aliyun.mq.topicServerConfig> </properties> <activation> <activeByDefault>true</activeByDefault> </activation> </profile>
2,数据库配置文件(部分)
config_url:${db-url} config_driverClassName:${db-driverClassName} config_username:${db-username} config_password:${db-password} salve_config_url:${dbs-url} salve_config_driverClassName:${dbs-driverClassName} salve_config_username:${dbs-username} salve_config_password:${dbs-password}
3,spring springmvc配置文件
spring 配置数据连接池,数据源,拦截等。
<!-- 阿里 druid数据库连接池 --> <bean id="dataSource_write" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close"> <!-- 数据库基本信息配置 --> <property name="url" value="${config_url}" /> <property name="username" value="${config_username}" /> <property name="password" value="${config_password}" /> <property name="driverClassName" value="${config_driverClassName}" /> <property name="filters" value="${filters}" /> <!-- 最大并发连接数 --> <property name="maxActive" value="${maxActive}" /> <!-- 初始化连接数量 --> <property name="initialSize" value="${initialSize}" /> <!-- 配置获取连接等待超时的时间 --> <property name="maxWait" value="${maxWait}" /> <!-- 最小空闲连接数 --> <property name="minIdle" value="${minIdle}" /> <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 --> <property name="timeBetweenEvictionRunsMillis" value="${timeBetweenEvictionRunsMillis}" /> <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 --> <property name="minEvictableIdleTimeMillis" value="${minEvictableIdleTimeMillis}" /> <property name="validationQuery" value="${validationQuery}" /> <property name="testWhileIdle" value="${testWhileIdle}" /> <property name="testOnBorrow" value="${testOnBorrow}" /> <property name="testOnReturn" value="${testOnReturn}" /> <property name="maxOpenPreparedStatements" value="${maxOpenPreparedStatements}" /> <!-- 打开removeAbandoned功能 --> <property name="removeAbandoned" value="${removeAbandoned}" /> <!-- 1800秒,也就是30分钟 --> <property name="removeAbandonedTimeout" value="${removeAbandonedTimeout}" /> <!-- 关闭abanded连接时输出错误日志 --> <property name="logAbandoned" value="${logAbandoned}" /> </bean> <!-- 阿里 druid数据库连接池 salve add by 胜杰--> <bean id="dataSource_read_one" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close"> <!-- 数据库基本信息配置 --> <property name="url" value="${salve_config_url}" /> <property name="username" value="${salve_config_username}" /> <property name="password" value="${salve_config_password}" /> <property name="driverClassName" value="${salve_config_driverClassName}" /> <property name="filters" value="${filters}" /> <!-- 最大并发连接数 --> <property name="maxActive" value="${maxActive}" /> <!-- 初始化连接数量 --> <property name="initialSize" value="${initialSize}" /> <!-- 配置获取连接等待超时的时间 --> <property name="maxWait" value="${maxWait}" /> <!-- 最小空闲连接数 --> <property name="minIdle" value="${minIdle}" /> <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 --> <property name="timeBetweenEvictionRunsMillis" value="${timeBetweenEvictionRunsMillis}" /> <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 --> <property name="minEvictableIdleTimeMillis" value="${minEvictableIdleTimeMillis}" /> <property name="validationQuery" value="${validationQuery}" /> <property name="testWhileIdle" value="${testWhileIdle}" /> <property name="testOnBorrow" value="${testOnBorrow}" /> <property name="testOnReturn" value="${testOnReturn}" /> <property name="maxOpenPreparedStatements" value="${maxOpenPreparedStatements}" /> <!-- 打开removeAbandoned功能 --> <property name="removeAbandoned" value="${removeAbandoned}" /> <!-- 1800秒,也就是30分钟 --> <property name="removeAbandonedTimeout" value="${removeAbandonedTimeout}" /> <!-- 关闭abanded连接时输出错误日志 --> <property name="logAbandoned" value="${logAbandoned}" /> </bean> <!-- 配置动态数据源,并设置默认的数据源。如果想要在类上加注解,则要去掉这个默认的数据源。 --> <bean id="dataSource" class="com.fh.dataexchanger.DynamicDataSource"> <property name="targetDataSources"> <map key-type="java.lang.String"> <!-- write --> <entry key="write" value-ref="dataSource_write"/> <!-- read --> <entry key="read" value-ref="dataSource_read_one"/> </map> </property> <property name="defaultTargetDataSource" ref="dataSource_write"/> </bean> <!--第一个 * —— 通配 任意返回值类型 第二三个 * —— 通配 包com.(任意).service下的任意class 第四个 * —— 通配 包com.evan.crm.service下的任意class的任意方法 第四个 .. —— 通配 方法可以有0个或多个参数 --> <aop:aspectj-autoproxy proxy-target-class="true" /> <bean id="dataSourceAspect" class="com.fh.dataexchanger.DataSourceAspect" /> <aop:config> <aop:aspect id="c" ref="dataSourceAspect"> <aop:pointcut id="tx" expression="execution(* com.*.controller..*.*(..))" /> <aop:before pointcut-ref="tx" method="before" /> </aop:aspect> </aop:config>
spring mvc:加这一句主要是为了使之可以拦截controller
<!-- 启动对@AspectJ注解的支持 --> <aop:aspectj-autoproxy />
加上边一句的同时头部要加上下边三句到相应的位置。
xmlns:aop="http://www.springframework.org/schema/aop" http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
4,java 数据源文件
定义一个名为DataSource的注解,Target用于指定注解可以添加的位置。两个参数的意思是:类和接口、方法。Retention 的作用时指定注解运行时间,运行时。
@Target({ElementType.TYPE,ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface DataSource { String value(); }
定义AOP切面以便拦截所有带有注解@DataSource的方法,取出注解的值作为数据源标识放到DynamicDataSourceHolder的线程变量中
public class DataSourceAspect { public void before(JoinPoint point){ Object target = point.getTarget(); String method = point.getSignature().getName(); System.out.println(method); Class<?> classz = target.getClass(); Class<?>[] parameterTypes = ((MethodSignature) point.getSignature()) .getMethod().getParameterTypes(); try { Method m = classz.getDeclaredMethod(method, parameterTypes); if(m != null && m.isAnnotationPresent(DataSource.class)){ DataSource ds = m.getAnnotation(DataSource.class); DynamicDataSourceHolder.putDataSource(ds.value()); } } catch (Exception e) { e.printStackTrace(); } } }
Spring 的AbstractRoutingDataSource获取数据源之前会先调用determineCurrentLookupKey方法查找当前的lookupKey,这个lookupKey就是数据源标识。
因此通过重写这个查找数据源标识的方法就可以让spring切换到指定的数据源了。
创建一个DynamicDataSource的类,继承AbstractRoutingDataSource并重写determineCurrentLookupKey方法,
public class DynamicDataSource extends AbstractRoutingDataSource{ @Override protected Object determineCurrentLookupKey() { return DynamicDataSourceHolder.getDataSource(); } }
创建DynamicDataSourceHolder用于持有和设置当前线程中使用的数据源标识
public class DynamicDataSourceHolder { public static final ThreadLocal<String> holder = new ThreadLocal<String>(); public static void putDataSource(String name){ holder.set(name); } public static String getDataSource(){ return holder.get(); } }
参考:http://www.cnblogs.com/liujiduo/p/5004691.html
http://blog.csdn.net/u010004317/article/details/47700447
http://blog.csdn.net/mfc2003/article/details/48490151
----名白
转载注明出处:http://www.cnblogs.com/mingbai/p/writeread.html
来源:https://www.cnblogs.com/mingbai/p/writeread.html