spring读写分离 - 事务注解篇

Posted 空白-键

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了spring读写分离 - 事务注解篇相关的知识,希望对你有一定的参考价值。


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

配置文件:

多数据源配置:

  <bean id="dataSource"
    class="com.lmiky.platform.database.datasource.DynamicDataSource">
    <property name="readDataSources">
      <list>
        <ref bean="readDataSource1" />
        <ref bean="readDataSource2" />
      </list>
    </property>
    <property name="writeDataSource" ref="writeDataSource" />
  </bean>

数据源拦截器:

  <bean id="dateSourceAspect"
    class="com.lmiky.platform.database.datasource.DateSourceAspect" />
  <aop:config expose-proxy="true">
    <aop:aspect ref="dateSourceAspect" order="0">
      <aop:pointcut id="dateSourcePointcut"
        expression="execution(* com.lmiky..service.impl..*.*(..))"/>
      <aop:around pointcut-ref="dateSourcePointcut" method="determineReadOrWriteDB" />
    </aop:aspect>
  </aop:config>

要保证让这个拦截在事务的拦截器之前,否则如果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<String, Boolean> 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<Object> 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<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DATASOURCE_KEY_WRITE, writeDataSource);
        if (this.readDataSources == null) 
            readDataSourceSize = 0;
         else 
            for(int i=0; i<readDataSources.size(); i++) 
                targetDataSources.put(DATASOURCE_KEY_READ + i, readDataSources.get(i));
            
            readDataSourceSize = readDataSources.size();
        
        setTargetDataSources(targetDataSources);
        super.afterPropertiesSet();
    

    /*
     * (non-Javadoc)
     * @see org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource#determineCurrentLookupKey()
     */
    @Override
    protected Object determineCurrentLookupKey() 
        if(DynamicDataSourceHolder.isChoiceNone() || DynamicDataSourceHolder.isChoiceWrite() || readDataSourceSize == 0) 
            return DATASOURCE_KEY_WRITE;
        
        int index = readIndex.incrementAndGet() % readDataSourceSize;
        return DATASOURCE_KEY_READ + index;
    

    /**
     * @return the writeDataSource
     */
    public Object getWriteDataSource() 
        return writeDataSource;
    

    /**
     * @param writeDataSource the writeDataSource to set
     */
    public void setWriteDataSource(Object writeDataSource) 
        this.writeDataSource = writeDataSource;
    

    /**
     * @return the readDataSources
     */
    public List<Object> getReadDataSources() 
        return readDataSources;
    

    /**
     * @param readDataSources the readDataSources to set
     */
    public void setReadDataSources(List<Object> 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<DataSourceType> 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();
    




以上是关于spring读写分离 - 事务注解篇的主要内容,如果未能解决你的问题,请参考以下文章

spring读写分离 - 事务配置篇(转)

spring实现读写分离aop注解方式

spring AOP 实现事务和主从读写分离

Spring+Mybatis实现主从数据库读写分离

从零开始写简易读写分离,不难嘛!

Spring Boot 声明式事务结合相关拦截器