读写分离

17 篇文章 0 订阅
4 篇文章 0 订阅


package z.farrell.framework.core.datasource;

/**
 * <pre>
 * 读/写动态数据库 决策者
 * 根据DataSourceType是write/read 来决定是使用读/写数据库
 * 通过ThreadLocal绑定实现选择功能
 * </pre>
 * Created by farrell on 2017/1/6.
 */
public class ReadWriteDataSourceDecision {

    private static final ThreadLocal<DataSourceType> holder = new ThreadLocal<>();

    public enum DataSourceType {
        write, read;
    }

    /**
     * 设置为写数据库
     */
    public static void markWrite() {
        holder.set(DataSourceType.write);
    }

    /**
     * 设置为读数据库
     */
    public static void markRead() {
        holder.set(DataSourceType.read);
    }

    /**
     * 重置
     */
    public static void reset() {
        holder.set(null);
    }

    /**
     * 判断是否没有选择数据源
     *
     * @return
     */
    public static boolean isChoiceNone() {
        return null == holder.get();
    }

    /**
     * 判断是否选择的写数据库
     *
     * @return true是 false不是
     */
    public static boolean isChoiceWrite() {
        return DataSourceType.write == holder.get();
    }

    /**
     * 判断是否选择的读数据库
     *
     * @return true是 false不是
     */
    public static boolean isChoiceRead() {
        return DataSourceType.read == holder.get();
    }

}

package z.farrell.framework.core.datasource;

import org.springframework.core.NestedRuntimeException;

/**
 * Created by farrell on 2017/1/6.
 */

public class ReadWriteDataSourceTransactionException extends NestedRuntimeException{

    private static final long serialVersionUID = 6021269017524827565L;

    /**
     * Construct a {@code NestedRuntimeException} with the specified detail message
     * and nested exception.
     *
     * @param msg   the detail message
     * @param cause the nested exception
     */
    public ReadWriteDataSourceTransactionException(String msg, Throwable cause) {
        super(msg, cause);
    }
}


package z.farrell.framework.core.datasource;

import org.aspectj.lang.ProceedingJoinPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource;
import org.springframework.transaction.interceptor.RuleBasedTransactionAttribute;
import org.springframework.transaction.interceptor.TransactionAttribute;
import org.springframework.util.PatternMatchUtils;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

/**
 * <pre>
 *
 * 此类实现了两个职责(为了减少类的数量将两个功能合并到一起了):
 *   读/写动态数据库选择处理器
 *   通过AOP切面实现读/写选择
 *
 *
 * ★★读/写动态数据库选择处理器★★
 * 1、首先读取<tx:advice>事务属性配置
 *
 * 2、对于所有读方法设置 read-only="true" 表示读取操作(以此来判断是选择读还是写库),其他操作都是走写库
 *    如<tx:method name="×××" read-only="true"/>
 *
 * 3、 forceChoiceReadWhenWrite用于确定在如果目前是写(即开启了事务),下一步如果是读,
 *    是直接参与到写库进行读,还是强制从读库读<br/>
 *      forceChoiceReadWhenWrite:true 表示目前是写,下一步如果是读,强制参与到写事务(即从写库读)
 *                                  这样可以避免写的时候从读库读不到数据
 *
 *                                  通过设置事务传播行为:SUPPORTS实现
 *
 *      forceChoiceReadWhenWrite:false 表示不管当前事务是写/读,都强制从读库获取数据
 *                                  通过设置事务传播行为:NOT_SUPPORTS实现(连接是尽快释放)
 *                                  『此处借助了 NOT_SUPPORTS会挂起之前的事务进行操作 然后再恢复之前事务完成的』
 * 4、配置方式
 *  <bean id="readWriteDataSourceTransactionProcessor" class="z.farrell.framework.core.datasource.ReadWriteDataSourceProcessor">
 *      <property name="forceChoiceReadWhenWrite" value="false"/>
 *  </bean>
 *
 * 5、目前只适用于<tx:advice>情况 , 支持@Transactional注解事务
 *
 *
 *
 * ★★通过AOP切面实现读/写库选择★★
 *
 * 1、首先将当前方法 与 根据之前【读/写动态数据库选择处理器】  提取的读库方法 进行匹配
 *
 * 2、如果匹配,说明是读取数据:
 *  2.1、如果forceChoiceReadWhenWrite:true,即强制走读库
 *  2.2、如果之前是写操作且forceChoiceReadWhenWrite:false,将从写库进行读取
 *  2.3、否则,到读库进行读取数据
 *
 * 3、如果不匹配,说明默认将使用写库进行操作
 *
 * 4、配置方式
 *      <aop:aspect order="-2147483648" ref="readWriteDataSourceTransactionProcessor">
 *          <aop:around pointcut-ref="transactionPointCut" method="determineReadOrWriteDB"/>
 *      </aop:aspect>
 *  4.1、此处order = Integer.MIN_VALUE 即最高的优先级(请参考http://jinnianshilongnian.iteye.com/blog/1423489)
 *  4.2、切入点:txPointcut 和 实施事务的切入点一样
 *  4.3、determineReadOrWriteDB方法用于决策是走读/写库的,请参考
 *       @ z.farrell.framework.core.datasource.ReadWriteDataSourceDecision
 *       @ z.farrell.framework.core.datasource.ReadWriteDataSource
 *
 * </pre>
 * Created by farrell on 2017/1/6.
 */
public class ReadWriteDataSourceProcessor implements BeanPostProcessor {

    /**
     * log
     */
    private final Logger logger = LoggerFactory.getLogger(ReadWriteDataSourceProcessor.class);

    /**
     * 是否强制转换为读库true即强制走读库,false将从写库进行读取,默认为false
     */
    private boolean forceChoiceReadWhenWrite = false;

    private Map<String, Boolean> readMethodMap = new HashMap<>();


    /**
     * Apply this BeanPostProcessor to the given new bean instance <i>before</i> any bean
     * initialization callbacks (like InitializingBean's {@code afterPropertiesSet}
     * or a custom init-method). The bean will already be populated with property values.
     * The returned bean instance may be a wrapper around the original.
     *
     * @param bean     the new bean instance
     * @param beanName the name of the bean
     * @return the bean instance to use, either the original or a wrapped one;
     * if {@code null}, no subsequent BeanPostProcessors will be invoked
     * @throws BeansException in case of errors
     * @see InitializingBean#afterPropertiesSet
     */
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    /**
     * Apply this BeanPostProcessor to the given new bean instance <i>after</i> any bean
     * initialization callbacks (like InitializingBean's {@code afterPropertiesSet}
     * or a custom init-method). The bean will already be populated with property values.
     * The returned bean instance may be a wrapper around the original.
     * <p>In case of a FactoryBean, this callback will be invoked for both the FactoryBean
     * instance and the objects created by the FactoryBean (as of Spring 2.0). The
     * post-processor can decide whether to apply to either the FactoryBean or created
     * objects or both through corresponding {@code bean instanceof FactoryBean} checks.
     * <p>This callback will also be invoked after a short-circuiting triggered by a
     * {@link InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation} method,
     * in contrast to all other BeanPostProcessor callbacks.
     *
     * @param bean     the new bean instance
     * @param beanName the name of the bean
     * @return the bean instance to use, either the original or a wrapped one;
     * if {@code null}, no subsequent BeanPostProcessors will be invoked
     * @throws BeansException in case of errors
     * @see InitializingBean#afterPropertiesSet
     * @see FactoryBean
     */
    @SuppressWarnings("unchecked")
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (!(bean instanceof NameMatchTransactionAttributeSource))
            return bean;
        NameMatchTransactionAttributeSource transactionAttributeSource = (NameMatchTransactionAttributeSource) bean;
        Field nameMapField = ReflectionUtils.findField(NameMatchTransactionAttributeSource.class,"nameMap");
        nameMapField.setAccessible(true);
        try {
            Map<String,TransactionAttribute> nameMap = (Map<String, TransactionAttribute>) nameMapField.get(transactionAttributeSource);
            for (Map.Entry<String,TransactionAttribute> entry:nameMap.entrySet()){
                RuleBasedTransactionAttribute attr = (RuleBasedTransactionAttribute) entry.getValue();
                //仅对read-only的处理
                if (!attr.isReadOnly())
                    continue;
                String methodName = entry.getKey();
                Boolean isForceChoiceRead = Boolean.FALSE;
                if (forceChoiceReadWhenWrite){
                    // 不管之前操作是写,默认强制从读库读 (设置为NOT_SUPPORTED即可)
                    // NOT_SUPPORTED会挂起之前的事务
                    attr.setPropagationBehavior(Propagation.NOT_SUPPORTED.value());
                    isForceChoiceRead = Boolean.TRUE;
                }else{
                    // 否则 设置为SUPPORTS(这样可以参与到写事务)
                    attr.setPropagationBehavior(Propagation.SUPPORTS.value());
                }
                logger.debug("read/write transaction process  method:{} force read:{}", methodName, isForceChoiceRead);
                readMethodMap.put(methodName, isForceChoiceRead);
            }
        } catch (IllegalAccessException e) {
            throw new ReadWriteDataSourceTransactionException("process read/write transaction error", e);
        }
        return bean;
    }


    /**
     * 选择读库?写库?
     * @param pjp
     * @return
     */
    public Object determineReadOrWriteDB(ProceedingJoinPoint pjp) throws Throwable {
        if (isChoiceReadDB(pjp.getSignature().getName()))
            ReadWriteDataSourceDecision.markRead();
        else
            ReadWriteDataSourceDecision.markWrite();

        try{
            return pjp.proceed();
        }finally {
            ReadWriteDataSourceDecision.reset();
        }
    }

    /**
     * 是否选择读库
     *
     * @param methodName
     * @return true 选择读库
     * false 选择写库
     */
    private boolean isChoiceReadDB(String methodName) {
        String bestNameMatch = null;
        for (String mappedName : readMethodMap.keySet()) {
            if (isMatch(methodName, mappedName)) {
                bestNameMatch = mappedName;
                break;
            }
        }

        Boolean isForceChoiceRead = readMethodMap.get(bestNameMatch);
        //表示强制选择读库
        if (isForceChoiceRead == Boolean.TRUE)
            return true;
        //如果之前选择了写库,现在还选择写库
        if (ReadWriteDataSourceDecision.isChoiceWrite())
            return false;
        //表示应该选择读库
        if (isForceChoiceRead != null)
            return true;
        //默认选择写库
        return false;
    }

    /**
     * 判断方法名称与springAOP拦截名称是否匹配
     *
     * @param methodName 方法名
     * @param mappedName 拦截名
     * @return
     */
    private boolean isMatch(String methodName, String mappedName) {
        return PatternMatchUtils.simpleMatch(mappedName, methodName);
    }

}





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值