Spring aop-读写分离

通过Spring实现应用程序的读写分离,实现原理是采用AOP。在方法执行之前选择对应的数据源,而在Spring中刚好有对应的动态数据源抽象类:

org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource

配置数据源

#读库配置
jdbc.read.driver=com.mysql.cj.jdbc.Driver
jdbc.read.url=${jdbc.read.url}
jdbc.read.username=${jdbc.read.username}
jdbc.read.password=${jdbc.read.password}

#写库配置
jdbc.write.driver=com.mysql.cj.jdbc.Driver
jdbc.write.url=${jdbc.write.url}
jdbc.write.username=${jdbc.write.username}
jdbc.write.password=${jdbc.write.password}

applicationContext.xml配置

<?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:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:aop="http://www.springframework.org/schema/aop"
       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">

    <description>Spring公共配置</description>

    <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
        <property name="transactionManager" ref="transactionManager"/>
    </bean>

    <!-- 使用annotation定义事务 -->
    <tx:annotation-driven proxy-target-class="true"/>

    <!-- 数据源配置, 使用alibaba druid连接池 -->
    <bean id="masterDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <!-- Connection Info -->
        <property name="driverClassName" value="${jdbc.write.driver}"/>
        <property name="url" value="${jdbc.write.url}"/>
        <property name="username" value="${jdbc.write.username}"/>
        <property name="password" value="${jdbc.write.password}"/>
    </bean>

    <bean id="slaveDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <!-- Connection Info -->
        <property name="driverClassName" value="${jdbc.read.driver}"/>
        <property name="url" value="${jdbc.read.url}"/>
        <property name="username" value="${jdbc.read.username}"/>
        <property name="password" value="${jdbc.read.password}"/>
    </bean>

    <!-- 动态数据源,根据service接口上的注解来决定取哪个数据源 -->
    <bean id="dataSource" class="com.snail.config.DynamicDataSource">
        <property name="targetDataSources">
            <map key-type="java.lang.String">
                <!-- master -->
                <entry key="master" value-ref="slaveDataSource"/>
                <!-- slave  -->
                <entry key="slave" value-ref="slaveDataSource"/>
            </map>
        </property>
        <property name="defaultTargetDataSource" ref="master"/>
    </bean>

    <!-- 事务管理器 -->
    <bean id="transactionManager"
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!-- 声明式开启 -->
    <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true" order="2"/>

    <!-- 为业务逻辑层的方法解析@DataSource注解  为当前线程的HandleDataSource注入数据源 -->
    <bean id="dataSourceAspect" class="com.snail.config.DataSourceAspect"/>
    <aop:config proxy-target-class="true">
        <aop:aspect id="dataSourceAspect" ref="dataSourceAspect" order="1">
            <aop:pointcut id="tx" expression="@annotation(com.snail.config.DataSource) "/>
            <aop:before pointcut-ref="tx" method="before"/>
        </aop:aspect>
    </aop:config>
</beans>

如果我们有仔细看applicationContext.xml文件,我们会发现有以下三个类

com.snail.config.DynamicDataSource
com.snail.config.DataSourceAspect
com.snail.config.DataSource

DynamicDataSource.java

@Slf4j
public class DynamicDataSource extends AbstractRoutingDataSource {

    /**
     * 获取与数据源相关的key 此key是Map<String,DataSource> resolvedDataSources 中与数据源绑定的key值
     * 在通过determineTargetDataSource获取目标数据源时使用
     */
    @Override
    protected Object determineCurrentLookupKey() {
        String dataSourceKet = HandleDataSource.getDataSource();
        log.debug("使用数据源:{}", dataSourceKet);
        return dataSourceKet;
    }

}

DataSourceAspect.java

@Slf4j
public class DataSourceAspect {

    /**
     * 在service层方法获取datasource对象之前,在切面中指定当前线程数据源
     */
    public void before(JoinPoint point) {
    	//获取实现类方法上的注解消息
        Method method = ((MethodSignature) point.getSignature()).getMethod();
        try {
            if (method != null && method.isAnnotationPresent(DataSource.class)) {
                DataSource data = method.getAnnotation(DataSource.class);
                log.debug("数据库切换类型:{}:{},{}", point.getTarget().getClass().getName(), method.getName(), data.value());
                // 数据源放到当前线程中
                HandleDataSource.putDataSource(data.value().toString());                        
            } 
        } catch (Exception e) {
            log.debug("异常时使用数据库类型:{}:{},{}", point.getTarget().getClass().getName(), method.getName(), DBTarget.write.toString());
            HandleDataSource.putDataSource("master");
        }
    }
}

DataSource.java

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DataSource {
    String value();
}

HandleDataSource.java

public class HandleDataSource {

    public static final ThreadLocal<String> holder = new ThreadLocal<>();

    /**
     * 绑定当前线程数据源
     *
     * @param datasource
     */
    public static void putDataSource(String datasource) {
        holder.set(datasource);
    }

    /**
     * 获取当前线程的数据源
     *
     * @return
     */
    public static String getDataSource() {
        return holder.get();
    }

    public static void clear() {
         holder.remove();
    }

}

在service服务上指定数据源:

public class SnailService {
	
	/**
	 * 通过id查询信息
	 * @param snailId 蜗牛ID
	 */
	@DataSource("slave") 
	Snail querySnail(Long snailId){
	}

	/**
	 * 存储蜗牛信息
	 * @param snail 蜗牛信息
	 */
	 @DataSource("master") 
	Snail saveSnail(Snail snail){
	}
}

以上通过Spring Aop方式完成了数据库的读写分离操作,需要注意的是事务和读写分离在Aop的织入顺序问题。
要在事务开始之前完成对读写数据源的选择,否则在事务开始后,则读写分离不起作用,此时可以利用order关键字控制Aop的织入顺序。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值