spring使用aop进行读写分离

来源:互联网 发布:五十音图软件 编辑:程序博客网 时间:2024/05/16 14:14

前提:已成功搭建MySQL主从集群。 
mysql主从复制环境搭建 
原理:首先使用spring配置动态数据源,然后使用切面拦截service方法,判断执行的是写操作还是读操作,以此来动态的修改此次请求所使用的数据源。

spring配置文件:

<?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:util="http://www.springframework.org/schema/util"    xmlns:context="http://www.springframework.org/schema/context"    xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:jee="http://www.springframework.org/schema/jee"    xmlns:tx="http://www.springframework.org/schema/tx" xmlns:task="http://www.springframework.org/schema/task"    xmlns:aop="http://www.springframework.org/schema/aop"    xsi:schemaLocation="        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd        http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-4.0.xsd        http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.0.xsd        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd         http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd         http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.0.xsd        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">    <description>Spring公共配置</description>    <context:component-scan base-package="com.lxg.*" />    <!-- 配置主数据源  -->    <bean id="dataSourceMaster" class="org.apache.commons.dbcp.BasicDataSource">        <property name="driverClassName" value="${jdbc.driver}" />        <property name="url" value="${jdbc.master.url}" />        <property name="username" value="${jdbc.master.username}" />        <property name="password" value="${jdbc.master.password}" />    </bean>    <!-- 配置从数据源 -->    <bean id="dataSourceSlaver" class="org.apache.commons.dbcp.BasicDataSource">        <property name="driverClassName" value="${jdbc.driver}" />        <property name="url" value="${jdbc.slaver.url}" />        <property name="username" value="${jdbc.slaver.username}" />        <property name="password" value="${jdbc.slaver.password}" />    </bean>    <bean id="dataSource" class="com.lxg.springdatesource.DynamicDataSource">        <!-- 通过key-value的形式来关联数据源 -->        <property name="targetDataSources">            <map>                <entry value-ref="dataSourceMaster" key="Master"></entry>                <entry value-ref="dataSourceSlaver" key="Slaver"></entry>            </map>        </property>        <property name="defaultTargetDataSource" ref="dataSourceMaster" />    </bean>    <!-- MyBatis配置 -->    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">        <property name="dataSource" ref="dataSource" />        <!-- 显式指定Mapper文件位置 -->        <property name="mapperLocations" value="classpath*:/mybatis/*Mapper.xml" />        <!-- mybatis配置文件路径 -->        <property name="configLocation" value="classpath:/mybatis/config.xml" />    </bean>    <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">        <constructor-arg index="0" ref="sqlSessionFactory" />        <!-- 这个执行器会批量执行更新语句, 还有SIMPLE 和 REUSE -->        <constructor-arg index="1" value="BATCH" />    </bean>    <!-- 扫描basePackage接口 -->    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">        <!-- 映射器接口文件的包路径, -->        <property name="basePackage" value="com.lxg.dao" />        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />    </bean>    <!-- 开启注解式事务控制 -->    <tx:annotation-driven transaction-manager="transactionManager" />    <!-- 配置事务管理器 -->    <bean id="transactionManager"        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">        <property name="dataSource" ref="dataSource"></property>    </bean>    <!-- 定义事务策略 -->    <tx:advice id="txAdvice" transaction-manager="transactionManager"/>    <!-- 定义AOP切面处理器 -->    <bean class="com.lxg.springdatesource.DataSourceAspect" id="dataSourceAspect" />    <aop:config>        <!--pointcut元素定义一个切入点,execution中的第一个星号 用以匹配方法的返回类型, 这里星号表明匹配所有返回类型。         com.lxg.service.*.*(..)表明匹配com.lxg.service包下的所有类的所有             方法 -->        <aop:pointcut id="myPointcut" expression="execution(* com.lxg.service.impl.*.*(..))" />        <!--将定义好的事务处理策略应用到上述的切入点 -->        <aop:advisor advice-ref="txAdvice" pointcut-ref="myPointcut" />        <!-- 将切面应用到自定义的切面处理器上,-9999保证该切面优先级最高执行 -->        <aop:aspect ref="dataSourceAspect" order="-9999">            <aop:before method="before" pointcut-ref="myPointcut" />        </aop:aspect>    </aop:config>    <!-- production环境 -->    <beans>        <context:property-placeholder            ignore-unresolvable="true" file-encoding="utf-8" location="classpath:jdbc.properties" />    </beans></beans>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109

数据库连接信息:

jdbc.driver=com.mysql.jdbc.Driver#主数据库jdbc.master.url=jdbc:mysql://localhost:3306/slave_lxgjdbc.master.username=rootjdbc.master.password=root#从数据库jdbc.slaver.url=jdbc:mysql://192.168.0.12:3306/slave_lxgjdbc.slaver.username=rootjdbc.slaver.password=root
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

aop切面: 
DataSourceAspect.Java

package com.lxg.springdatesource;import org.apache.commons.lang3.StringUtils;import org.aspectj.lang.JoinPoint;/** * 定义数据源的AOP切面,通过该Service的方法名判断是应该走读库还是写库 *  */public class DataSourceAspect {    /**     * 在进入Service方法之前执行     *      * @param point 切面对象     */    public void before(JoinPoint point) {        // 获取到当前执行的方法名        String methodName = point.getSignature().getName();        if (isSlave(methodName)) {            // 标记为从库            DBContextHolder.setDBType(DBContextHolder.DATA_SOURCE_SLAVER);        } else {            // 标记为主库            DBContextHolder.setDBType(DBContextHolder.DATA_SOURCE_MASTER);        }    }    /**     * 判断是否为读库     *      * @param methodName     * @return     */    private Boolean isSlave(String methodName) {        // 方法名以query、find、get开头的方法名走从库        return StringUtils.startsWithAny(methodName, "query", "find", "get");    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

如果想用注解方式区分主从库,可以用一下方式:

public void before(JoinPoint point) {System.out.println("aop before进来了...");// 获取到当前执行的方法名String methodName = point.getSignature().getName();Class clazz = point.getSignature().getDeclaringType();System.out.println("methodName:" + methodName);System.out.println("clazz:" + clazz);try {if (isSlave(point)) {System.out.println("slaver");// 标记为从库DBContextHolder.setDBType(DBContextHolder.DATA_SOURCE_SLAVER);} else {System.out.println("master");// 标记为主库DBContextHolder.setDBType(DBContextHolder.DATA_SOURCE_MASTER);}} catch (NoSuchMethodException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (SecurityException e) {// TODO Auto-generated catch blocke.printStackTrace();}}/** * 判断是否为读库 *  * @param methodName * @return * @throws SecurityException * @throws NoSuchMethodException */private Boolean isSlave(JoinPoint point) throws NoSuchMethodException, SecurityException {// 方法名以query、find、get开头的方法名走从库// return StringUtils.startsWithAny(methodName, "query", "find", "get");Object target = point.getTarget();String method = point.getSignature().getName();Class<?>[] classz = target.getClass().getInterfaces();Class<?>[] parameterTypes = ((MethodSignature) point.getSignature()).getMethod().getParameterTypes();Method m = classz[0].getMethod(method, parameterTypes);if (m != null && m.getAnnotation(Master.class) != null) {return false;}if (m != null && m.getAnnotation(Slaver.class) != null) {return true;}return false;}


DBContextHolder.java

package com.lxg.springdatesource;public class DBContextHolder{      public static final String DATA_SOURCE_MASTER = "Master";      public static final String DATA_SOURCE_SLAVER = "Slaver";      private static final ThreadLocal<String> THREAD_LOCAL = new ThreadLocal<String>();      public static void setDBType(String dbType) {          THREAD_LOCAL.set(dbType);      }      public static String getDBType() {          return THREAD_LOCAL.get();      }      public static void clearDBType() {          THREAD_LOCAL.remove();      }  }  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

DynamicDataSource.java


package com.lxg.springdatesource;import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; public class DynamicDataSource extends AbstractRoutingDataSource{      @Override      protected Object determineCurrentLookupKey() {          return DBContextHolder.getDBType();      }  }
0 0