美文网首页Java&Spring基础技术Spring Boot
自定义注解,aop+AbstractRoutingDataSou

自定义注解,aop+AbstractRoutingDataSou

作者: 沐兮_d64c | 来源:发表于2017-06-15 01:28 被阅读438次

1,动态数据源类

public class DynamicDataSource extends AbstractRoutingDataSource{

    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceHolder.getDbType();//负载均衡,需要在这里进行从库的key轮询。 
    }
}

2,使用本地ThreadLocal,存储当前线程的数据源。

threadlocal将具体的值保存在线程自身的threadLocalMap中

public abstract class DynamicDataSourceHolder {

    public static final String master = "master";

    public static final String slave = "slave";

    private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();//定义全局ThreadLocal对象,不同线程使用的是同一个对象。

    public static void setDbType(String dbTypeName){
        DynamicDataSourceHolder.threadLocal.set(dbTypeName);
    }

    public static String getDbType(){
        return DynamicDataSourceHolder.threadLocal.get();
    }

    public static void clearDbType(){
        DynamicDataSourceHolder.threadLocal.remove();
    }

}

3,配置多个数据源

    @Value("${datasource.username2}")
    private String username2;

    @Value("${datasource.password2}")
    private String password2;

    @Value("${datasource.url2}")
    private String url2;

    @Value("${datasource.username}")
    private String username;

    @Value("${datasource.password}")
    private String password;

    @Value("${datasource.url}")
    private String url;

    @Bean//配置主数据库源
    public DataSource master(){
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setUrl(url);
        druidDataSource.setUsername(username);
        druidDataSource.setPassword(password);
        return getDataSource(druidDataSource);
    }
   
   @Bean//配置从数据库源
    public DataSource slave(){
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setUrl(url2);
        druidDataSource.setUsername(username2);
        druidDataSource.setPassword(password2);
        return getDataSource(druidDataSource);
    }

    @Bean//配置动态数据源
    public DynamicDataSource dynamicDataSource(){
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        dynamicDataSource.setDefaultTargetDataSource(master());//default数据源
        Map<Object, Object> map = Maps.newHashMap();
        map.put(DynamicDataSourceHolder.master, master());//key - value
        map.put(DynamicDataSourceHolder.slave, slave());
        dynamicDataSource.setTargetDataSources(map);
        return dynamicDataSource;
    }

4,自定义注解,标识使用哪个数据源

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Order(Ordered.HIGHEST_PRECEDENCE)
public @interface DynamicSourceAno {

    /**
     * 默认使用的数据源
     * @return
     */

    String value() default DynamicDataSourceHolder.master;

}

5,实现切面。

@Aspect
@Component
@Order(1)//service层有@Transactional,事务也是aop实现的,这里定义order,要在事务开启的aop执行前,进行数据源的切换,事务的aop结束后,进行数据源的恢复默认。
public class DynamicSourceAspect {

    private static Logger logger = LoggerFactory.getLogger(DynamicSourceAspect.class);

    @Before("within(@org.springframework.stereotype.Service *) && @annotation(dynamicSourceAno)")
//在service的所有方法执行前,并且带有注解DynamicSourceAno
    public void beforeDynamicSource(DynamicSourceAno dynamicSourceAno){

        try {
            DynamicDataSourceHolder.setDbType(dynamicSource.value());
        } catch (Throwable t) {
            logger.info(t.getMessage(), t);
        }

    }

    @After("within(@org.springframework.stereotype.Service *) && @annotation(dynamicSource)")
    public void afterDynamicSource(DynamicSource dynamicSource){

        try {
            DynamicDataSourceHolder.clearDbType();
        } catch (Throwable t) {
            logger.info(t.getMessage(), t);
        }

    }

}

6,ThreadLocal与Thread

1)Thread类。定义了两个TheadLocalMap类型属性(可以用来保存对象),通过ThreadLocal类来维护的set,get,remove等操作

image.png
2)ThreadLocal类。ThreadLocalMap是ThreadLocal的静态内部类。
Entry类中value属性用来保存和线程关联的具体对象,key是ThreadLocal类型
image.png
3)ThreadLocal类操作当前线程的ThreadLocalMap。
//保存value到当前线程
public void set(T value) {
        Thread t = Thread.currentThread();//得到当前线程
        ThreadLocalMap map = getMap(t);//得到当前线程的ThreadLocalMap对象。
        if (map != null)
            map.set(this, value);//将自身对象作为key。每个线程保存这个对象,都是使用相同的key,因为该类型的ThreadLocal对象只new了一个。
        else
            createMap(t, value);
}

//当第一次调用set方法时,会创建一个ThreadLocalMap对象。
void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
}

ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
}
//从当前线程的threadlocals中去除指定key对应的value
public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);//不同的线程使用相同的key来取值,得到的各自线程保存的对象。
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

相关文章

网友评论

  • 颖辉小居:博主你好,请问是否还需要使不同数据源的数据同步,不然A库改了,B库数据不一致,请问有什么解决方案呢?
  • 梦中一点心雨:博主你好,我在项目中,使用这种方式实现了动态数据源切换,但是出现了问题,多个线程之间数据源不独立。不同用户登录系统之后,数据源切换就乱了,比如:A用户登录之后可能有涉及切换数据源的操作,然后B也登录了,A在切换数据源的过程中,B的数据源也被切换了,不知道博主有没有遇到这种问题呢
    沐兮_d64c:@梦中一点心雨 那就主动执行 DynamicDataSourceHolder.setDbType(dynamicSource.value());
    DynamicDataSourceHolder.clearDbType();操作。在contro中,调用service方法之前和之后分别执行。
    梦中一点心雨: @沐兮_d64c 是的
    沐兮_d64c:使用了threadlocal吗?

本文标题:自定义注解,aop+AbstractRoutingDataSou

本文链接:https://www.haomeiwen.com/subject/sofyqxtx.html