复习电商笔记-22-SpringAOP实现读写分离

*SpringAOP实现读写分离

工作原理

使用SpringAOP动态切换数据源。在调用service方法之前,使用AOP进行判断,是使用读库还是使用写库。根据要执行的方法名调用不同的数据库,例如使用query、find、get等开头的方法就访问读库,其他的访问写库。

 

AOP切面实现动态数据源

在后台系统jt-manage中来改造:

com.jt.manage.datasource.DynamicDataSource

package com.jt.manage.datasource;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
 * 定义动态数据源,实现通过集成Spring提供的AbstractRoutingDataSource,只需要实现determineCurrentLookupKey方法即可
 * 
 * 由于DynamicDataSource是单例的,线程不安全的,所以采用ThreadLocal保证线程安全,由DynamicDataSourceHolder完成。
 *
 */
public class DynamicDataSource extends AbstractRoutingDataSource{
  @Override
    protected Object determineCurrentLookupKey() {
        // 使用DynamicDataSourceHolder保证线程安全,并且得到当前线程中的数据源key
        return DynamicDataSourceHolder.getDataSourceKey();
    }

}

com.jt.manage.datasource.DynamicDataSourceHolder

package com.jt.manage.datasource;
/**
 * 
 * 使用ThreadLocal技术来记录当前线程中的数据源的key
 *
 */
public class DynamicDataSourceHolder {
    
    //写库对应的数据源key
    private static final String MASTER = "master";

    //读库对应的数据源key
    private static final String SLAVE = "slave";
    
    //使用ThreadLocal记录当前线程的数据源key
    private static final ThreadLocal<String> holder = new ThreadLocal<String>();

    /**
     * 设置数据源key
     * @param key
     */
    public static void putDataSourceKey(String key) {
        holder.set(key);
    }

    /**
     * 获取数据源key
     * @return
     */
    public static String getDataSourceKey() {
        return holder.get();
    }
    
    /**
     * 标记写库
     */
    public static void markMaster(){
        putDataSourceKey(MASTER);
    }
    
    /**
   * 标记读库
     */
    public static void markSlave(){
        putDataSourceKey(SLAVE);
    }

}

com.jt.manage.datasource.DataSourceAspect

package com.jt.manage.datasource;
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)) {
            // 标记为读库
            DynamicDataSourceHolder.markSlave();
        } else {
            // 标记为写库
            DynamicDataSourceHolder.markMaster();
        }
    }

    /**
     * 判断是否为读库
     * 
     * @param methodName
     * @return
     */
    private Boolean isSlave(String methodName) {
        // 方法名以query、find、get开头的方法名走从库
        return StringUtils.startsWithAny(methodName, "query", "find", "get");
    }

}

 

增加两个数据源    jdbc.properties

jdbc.master.driver=com.mysql.jdbc.Driver
jdbc.master.url=jdbc:mysql://127.0.0.1:3308/jtdb?useUnicode=true&cha
racterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
jdbc.master.username=root
jdbc.master.password=root

jdbc.slave01.driver=com.mysql.jdbc.Driver
jdbc.slave01.url=jdbc:mysql://127.0.0.1:3309/jtdb?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true
jdbc.slave01.username=root
jdbc.slave01.password=root

 

引用两个数据源     applicationContext.xml

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
	xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	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/aop http://www.springframework.org/schema/aop/spring-aop-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">

	<!-- 使用spring自带的占位符替换功能,可以实现注解方式获取属性文件中的配置值 -->
	<bean
		class="com.jt.common.spring.exetend.ExtendedPropertyPlaceholderConfigurer">
		<!-- 允许JVM参数覆盖 -->
		<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" />
		<!-- 忽略没有找到的资源文件 -->
		<property name="ignoreResourceNotFound" value="true" />
		<!-- 配置资源文件 -->
		<property name="locations">
			<list>
				<value>classpath:jdbc.properties</value>
				<value>classpath:env.properties</value>
				<value>classpath:httpclient.properties</value>
				<value>classpath:redis.properties</value>
				<value>classpath:rabbitmq.properties</value>
			</list>
		</property>
	</bean>

	<!-- 扫描包 -->
	<context:component-scan base-package="com.jt" />
	<!-- 定义数据源,使用自己实现的数据源 -->
	<bean id="dataSource" class="com.jt.manage.datasource.DynamicDataSource">
		<!-- 设置多个数据源 -->
		<property name="targetDataSources">
			<map key-type="java.lang.String">
				<!-- 这个key需要和程序中的key一致 -->
				<entry key="master" value-ref="masterDataSource" />
				<entry key="slave" value-ref="slave01DataSource" />
			</map>
		</property>
		<!-- 设置默认的数据源,这里默认走写库 -->
		<property name="defaultTargetDataSource" ref="masterDataSource" />
	</bean>


	<!-- 配置连接池 -->
	<bean id="masterDataSource" class="com.jolbox.bonecp.BoneCPDataSource"
		destroy-method="close">
		<!-- 数据库驱动 -->
		<property name="driverClass" value="${jdbc.master.driver}" />
		<!-- 相应驱动的jdbcUrl -->
		<property name="jdbcUrl" value="${jdbc.master.url}" />
		<!-- 数据库的用户名 -->
		<property name="username" value="${jdbc.master.username}" />
		<!-- 数据库的密码 -->
		<property name="password" value="${jdbc.master.password}" />
		<!-- 检查数据库连接池中空闲连接的间隔时间,单位是分,默认值:240,如果要取消则设置为0 -->
		<property name="idleConnectionTestPeriod" value="60" />
		<!-- 连接池中未使用的链接最大存活时间,单位是分,默认值:60,如果要永远存活设置为0 -->
		<property name="idleMaxAge" value="30" />
		<!-- 每个分区最大的连接数 -->
		<property name="maxConnectionsPerPartition" value="150" />
		<!-- 每个分区最小的连接数 -->
		<property name="minConnectionsPerPartition" value="5" />
	</bean>

	<!-- 配置连接池 -->
	<bean id="slave01DataSource" class="com.jolbox.bonecp.BoneCPDataSource"
		destroy-method="close">
		<!-- 数据库驱动 -->
		<property name="driverClass" value="${jdbc.slave01.driver}" />
<!-- 相应驱动的jdbcUrl -->
		<property name="jdbcUrl" value="${jdbc.slave01.url}" />
		<!-- 数据库的用户名 -->
		<property name="username" value="${jdbc.slave01.username}" />
		<!-- 数据库的密码 -->
		<property name="password" value="${jdbc.slave01.password}" />
		<!-- 检查数据库连接池中空闲连接的间隔时间,单位是分,默认值:240,如果要取消则设置为0 -->
		<property name="idleConnectionTestPeriod" value="60" />
		<!-- 连接池中未使用的链接最大存活时间,单位是分,默认值:60,如果要永远存活设置为0 -->
		<property name="idleMaxAge" value="30" />
		<!-- 每个分区最大的连接数 -->
		<property name="maxConnectionsPerPartition" value="150" />
		<!-- 每个分区最小的连接数 -->
		<property name="minConnectionsPerPartition" value="5" />
	</bean>

</beans>

 

定义AOP切面处理

将切面应用到自定义的切面处理器上   applicationContext-transaction.xml

<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
	xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	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/aop http://www.springframework.org/schema/aop/spring-aop-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">
	
	<!-- 定义事务管理器 -->
	<bean id="transactionManager"
		class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource" />
	</bean>

	<!-- 定义事务策略 -->
	<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
			<!--定义查询方法都是只读的 -->
			<tx:method name="query*" read-only="true" />
			<tx:method name="find*" read-only="true" />
			<tx:method name="get*" read-only="true" />

			<!-- 主库执行操作,事务传播行为定义为默认行为 -->
			<tx:method name="save*" propagation="REQUIRED" />
			<tx:method name="update*" propagation="REQUIRED" />
			<tx:method name="delete*" propagation="REQUIRED" />

			<!--其他方法使用默认事务策略 -->
			<tx:method name="*" />
		</tx:attributes>
	</tx:advice>
	
	<!-- 定义AOP切面处理器 -->
	<bean class="com.jt.manage.datasource.DataSourceAspect" id="dataSourceAspect" />

	<aop:config>
		<!-- 定义切面,所有的service的所有方法 -->
		<aop:pointcut id="txPointcut" expression="execution(* com.jt.manage.service.*.*(..))" />
		<!-- 应用事务策略到Service切面 -->
		<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
		
		
		<!-- 将切面应用到自定义的切面处理器上,-9999保证该切面优先级最高执行 -->
		<aop:aspect ref="dataSourceAspect" order="-9999">
			<aop:before method="before" pointcut-ref="txPointcut" />
		</aop:aspect>
	</aop:config>

	
	
</beans>

 

 

猜你喜欢

转载自blog.csdn.net/qq_40680190/article/details/84102393