spring读写分离 - 事务注解篇


思路参照 spring读写分离 - 事务配置篇(转) ,不过是基于@Transactional判断,所以每个需要事务的方法上都必须添加上这个注解,这里直接贴出代码:

配置文件:

多数据源配置:

  
    
      
        
        
      
    
    
  

数据源拦截器:

  
  
    
      
      
    
  

要保证让这个拦截在事务的拦截器之前,否则如果spring先拦截事务的话,就不会起效了。可以用order设的值来排序,或者把这个配置的代码放在跟事务配置的代码同一个页面,并且放在事务配置代码的前面,spring是按代码顺序来执行的。

其他的跟单个数据源配置一样。

java代码:


数据源拦截器
package com.lmiky.platform.database.datasource;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.transaction.annotation.Transactional;

import java.lang.reflect.Method;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 数据源切片
 *
 * @author lmiky
 * @date 2015年9月7日 下午3:25:54
 */
public class DateSourceAspect {
    /**
     * 缓存
     */
    private static ConcurrentHashMap methodIsReadCache = new ConcurrentHashMap<>();

    /**
     * 决策是否只读
     *
     * @param pjp 织入点
     * @return 方法执行结果
     * @throws Throwable
     * @author lmiky
     * @date 2015年9月7日 下午3:45:27
     */
    public Object determineReadOrWriteDB(ProceedingJoinPoint pjp) throws Throwable {
        Method method = ((MethodSignature) pjp.getSignature()).getMethod();
        Object target = pjp.getTarget();
        String cacheKey = target.getClass().getName() + "." + method.getName();
        Boolean isReadCacheValue = methodIsReadCache.get(cacheKey);
        if (isReadCacheValue == null) {
            // 重新获取方法,否则传递的是接口的方法信息
            Method realMethod = target.getClass().getMethod(method.getName(), method.getParameterTypes());
            isReadCacheValue = isChoiceReadDB(realMethod);
            methodIsReadCache.put(cacheKey, isReadCacheValue);
        }
        if (isReadCacheValue) {
            DynamicDataSourceHolder.markRead();
        } else {
            DynamicDataSourceHolder.markWrite();
        }
        try {
            return pjp.proceed();
        } finally {
            DynamicDataSourceHolder.reset();
        }
    }

    /**
     * 判断是否只读方法
     *
     * @param method 执行方法
     * @return 当前方法是否只读
     * @author lmiky
     * @date 2015年9月7日 下午3:45:10
     */
    private boolean isChoiceReadDB(Method method) {
        Transactional transactionalAnno = AnnotationUtils.findAnnotation(method, Transactional.class);
        if (transactionalAnno == null) {
            return true;
        }
        // 如果之前选择了写库,则现在还选择写库
        if (DynamicDataSourceHolder.isChoiceWrite()) {
            return false;
        }
        if (transactionalAnno.readOnly()) {
            return true;
        }
        return false;
    }
}
数据源选择:

package com.lmiky.platform.database.datasource;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
 * 动态数据源
 *
 * @author lmiky
 * @date 2015年9月7日 下午2:01:31
 */
public class DynamicDataSource extends AbstractRoutingDataSource {

    private Object writeDataSource;
    private List readDataSources;
    private int readDataSourceSize = 0;

    private AtomicInteger readIndex = new AtomicInteger(0);

    /**
     * 数据源键名
     */
    private static final String DATASOURCE_KEY_WRITE = "write";
    private static final String DATASOURCE_KEY_READ = "read";

    /* (non-Javadoc)
     * @see org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource#afterPropertiesSet()
     */
    @Override
    public void afterPropertiesSet() {
        if (this.writeDataSource == null) {
            throw new IllegalArgumentException("Property 'writeDataSource' is required");
        }
        setDefaultTargetDataSource(writeDataSource);
        Map targetDataSources = new HashMap<>();
        targetDataSources.put(DATASOURCE_KEY_WRITE, writeDataSource);
        if (this.readDataSources == null) {
            readDataSourceSize = 0;
        } else {
            for(int i=0; i getReadDataSources() {
        return readDataSources;
    }

    /**
     * @param readDataSources the readDataSources to set
     */
    public void setReadDataSources(List readDataSources) {
        this.readDataSources = readDataSources;
    }


}
 
  
package com.lmiky.platform.database.datasource;

/**
 * 数据源管理器
 *
 * @author lmiky
 * @date 2015年9月7日 下午2:02:23
 */
public class DynamicDataSourceHolder {
    private static enum DataSourceType {
        write, read;
    }

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

    /**
     * 数据源名称
     */
    public static final String DATASOURCE_WRITE = "write";
    public static final String DATASOURCE_READ = "read";

    
    /**
     * 标记为写数据源
     * @author lmiky
     * @date 2015年9月9日 下午8:57:43
     */
    public static void markWrite() {
        holder.set(DataSourceType.write);
    }
    
    /**
     * 标记为读数据源
     * @author lmiky
     * @date 2015年9月9日 下午8:57:43
     */
    public static void markRead() {
        holder.set(DataSourceType.read);
    }
    
    /**
     * 重置
     * @author lmiky
     * @date 2015年9月9日 下午8:58:01
     */
    public static void reset() {
        holder.set(null);
    }
    
    /**
     * 是否还未设置数据源
     * @author lmiky
     * @date 2015年9月9日 下午8:58:09
     * @return
     */
    public static boolean isChoiceNone() {
        return null == holder.get(); 
    }
    
    /**
     * 当前是否选择了写数据源
     * @author lmiky
     * @date 2015年9月9日 下午8:58:19
     * @return
     */
    public static boolean isChoiceWrite() {
        return DataSourceType.write == holder.get();
    }
    
    /**
     * 当前是否选择了读数据源
     * @author lmiky
     * @date 2015年9月9日 下午8:58:29
     * @return
     */
    public static boolean isChoiceRead() {
        return DataSourceType.read == holder.get();
    }
}



你可能感兴趣的:(Java)