读写分离



package z.farrell.framework.core.datasource;

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

/**
 * 
 *
 * 此类实现了两个职责(为了减少类的数量将两个功能合并到一起了):
 *   读/写动态数据库选择处理器
 *   通过AOP切面实现读/写选择
 *
 *
 * ★★读/写动态数据库选择处理器★★
 * 1、首先读取事务属性配置
 *
 * 2、对于所有读方法设置 read-only="true" 表示读取操作(以此来判断是选择读还是写库),其他操作都是走写库
 *    如
 *
 * 3、 forceChoiceReadWhenWrite用于确定在如果目前是写(即开启了事务),下一步如果是读,
 *    是直接参与到写库进行读,还是强制从读库读
* forceChoiceReadWhenWrite:true 表示目前是写,下一步如果是读,强制参与到写事务(即从写库读) * 这样可以避免写的时候从读库读不到数据 * * 通过设置事务传播行为:SUPPORTS实现 * * forceChoiceReadWhenWrite:false 表示不管当前事务是写/读,都强制从读库获取数据 * 通过设置事务传播行为:NOT_SUPPORTS实现(连接是尽快释放) * 『此处借助了 NOT_SUPPORTS会挂起之前的事务进行操作 然后再恢复之前事务完成的』 * 4、配置方式 * * * * * 5、目前只适用于情况 , 支持@Transactional注解事务 * * * * ★★通过AOP切面实现读/写库选择★★ * * 1、首先将当前方法 与 根据之前【读/写动态数据库选择处理器】 提取的读库方法 进行匹配 * * 2、如果匹配,说明是读取数据: * 2.1、如果forceChoiceReadWhenWrite:true,即强制走读库 * 2.2、如果之前是写操作且forceChoiceReadWhenWrite:false,将从写库进行读取 * 2.3、否则,到读库进行读取数据 * * 3、如果不匹配,说明默认将使用写库进行操作 * * 4、配置方式 * * * * 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 * *
* 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 readMethodMap = new HashMap<>(); /** * Apply this BeanPostProcessor to the given new bean instance before 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 after 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. *

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. *

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 nameMap = (Map) nameMapField.get(transactionAttributeSource); for (Map.Entry 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); } }






你可能感兴趣的:(java,工具类,经验分享)