首页 > 编程语言 > 详细

SpringBoot 多数据源配置

时间:2019-03-02 17:33:58      阅读:153      评论:0      收藏:0      [点我收藏+]

主要利用AOP+ThreadLocal+自定义注释实现注释切换

技术分享图片

 

单数据源的场景(一般的Web项目工程这样配置进行处理,就已经比较能够满足我们的业务需求)
多数据源多SessionFactory这样的场景,估计作为刚刚开始想象想处理在使用框架的情况下处理业务,配置多个SessionFactory,然后在Dao层中对于特定的请求,通过特定的SessionFactory即可处理实现这样的业务需求,不过这样的处理带来了很多的不便之处,所有很多情况下我们宁愿直接使用封装的JDBC编程,或者使用Mybatis处理这样的业务场景
使用AbstractRoutingDataSource 的实现类,进行灵活的切换,可以通过AOP或者手动编程设置当前的DataSource,不用修改我们编写的对于继承HibernateSupportDao的实现类的修改,这样的编写方式比较好,至于其中的实现原理,让我细细到来。我们想看看如何去应用,实现原理慢慢的说!
编写AbstractRoutingDataSource的实现类,HandlerDataSource就是提供给我们动态选择数据源的数据的信息,我们这里编写一个根据当前线程来选择数据源,然后通过AOP拦截特定的注解,设置当前的数据源信息,也可以手动的设置当前的数据源,在编程的类中。

pom.xml

<!-- springboot-aop包,AOP切面注解,Aspectd等相关注解 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
AbstractRoutingDataSource
只支持单库事务,也就是说切换数据源要在开启事务之前执行。 
spring DataSourceTransactionManager进行事务管理,开启事务,会将数据源缓存到DataSourceTransactionObject对象中进行后续的commit rollback等事务操作。

这里主要看AbstractRoutingDataSource.java源码:

技术分享图片

配置的多个数据源会放在AbstractRoutingDataSource的 targetDataSources和defaultTargetDataSource中,然后通过afterPropertiesSet()方法将数据源分别进行复制到resolvedDataSources和resolvedDefaultDataSource中(AbstractAutowireCapableBeanFactory-->InitializingBean.afterPropertiesSet() 在创建bean的时候反射调用)
AbstractRoutingDataSource的getConnection()的方法的时候,先调用determineTargetDataSource()方法返回DataSource
(JdbcTemplate.execute()->DataSourceUtils.getConnection->dataSource.getConnection()->AbstractRoutingDataSource实现了了DataSource)

 技术分享图片

技术分享图片

标黄部分:根据Key获取对应数据源。

这里新建类继承AbstractRoutingDataSource

package com.paic.phssp.springtest.dataSource;

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

public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDB();
    }
}

 

 这里新建类,ThreadLocal用于存放数据源

package com.paic.phssp.springtest.dataSource;

public class DataSourceContextHolder {

    /**
     * 默认数据源
     */
    public static final String DEFAULT_DS = "chenDataSource";

    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

    // 设置数据源名
    public static void setDB(String dbType) {
        System.out.println("切换到{" + dbType + "}数据源");
        contextHolder.set(dbType);
    }

    // 获取数据源名
    public static String getDB() {
        return (contextHolder.get());
    }

    // 清除数据源名
    public static void clearDB() {
        contextHolder.remove();
    }
}

说明:

此处,会疑惑会不会线程不安全啊,那么就要看看ThreadLocal.java的源码实现了。
ThreadLocal:

用于保存某个线程共享变量:对于同一个static ThreadLocal,不同线程只能从中get,set,remove自己的变量,而不会影响其他线程的变量。

1、ThreadLocal.get: 获取ThreadLocal中当前线程共享变量的值。

2、ThreadLocal.set: 设置ThreadLocal中当前线程共享变量的值。

3、ThreadLocal.remove: 移除ThreadLocal中当前线程共享变量的值。

4、ThreadLocal.initialValue: ThreadLocal没有被当前线程赋值时或当前线程刚调用remove方法后调用get方法,返回此方法值。

工作原理:

1、Thread类中有一个成员变量属于ThreadLocalMap类(一个定义在ThreadLocal类中的内部类),它是一个Map,他的key是ThreadLocal实例对象。

2、当为ThreadLocal类的对象set值时,首先获得当前线程的ThreadLocalMap类属性,然后以ThreadLocal类的对象为key,设定value。get值时则类似。

3、ThreadLocal变量的活动范围为某线程,是该线程“专有的,独自霸占”的,对该变量的所有操作均由该线程完成!也就是说,ThreadLocal 不是用来解决共享对象的多线程访问的竞争问题的,因为ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的。当线程终止后,这些值会作为垃圾回收。

4、由ThreadLocal的工作原理决定了:每个线程独自拥有一个变量,并非是共享的,下面给出一个例子:

 

package com.paic.phssp.springtest;

import com.paic.phssp.springtest.dataSource.DynamicDataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

@Configuration
public class DataSourceConfig {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Bean(name = "chenDataSource")
    @Qualifier("chenDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.chen")
    public DataSource chenDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "quartzds")
    @Qualifier("quartzds")
    @ConfigurationProperties(prefix = "spring.datasource.quartzds")
    public DataSource quartzDSDataSource() {
        return DataSourceBuilder.create().build();
    }

    /**
     * 动态数据源: 通过AOP在不同数据源之间动态切换
     *
     * @return
     */
    @Primary
    @Bean(name = "dynamicDataSource")
    public DataSource dynamicDataSource() {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        // 默认数据源
        dynamicDataSource.setDefaultTargetDataSource(chenDataSource());
        // 配置多数据源
        Map<Object, Object> dsMap = new HashMap();
        dsMap.put("chenDataSource", chenDataSource());
        dsMap.put("quartzds", quartzDSDataSource());

        dynamicDataSource.setTargetDataSources(dsMap);
        return dynamicDataSource;
    }

    /**
     * 配置@Transactional注解事物
     * @return
     */
    @Bean
    public PlatformTransactionManager transactionManager() {
        return new DataSourceTransactionManager(dynamicDataSource());
    }
}

自定义注释

package com.paic.phssp.springtest.dataSource;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface IDataSource {
    String value() default "chenDataSource";
}

AOP从注释中获取数据源key,然后通过AbstractRoutingDataSource.determineTargetDataSource()获取数据源

 

package com.paic.phssp.springtest.dataSource;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * 自定义注解 + AOP的方式实现数据源动态切换。
 * Created by pure on 2018-05-06.
 */
    
@Component
public class DynamicDataSourceAspect {
    
    @Before("@annotation(IDataSource)")
    public void beforeSwitchDS(JoinPoint point) {
        //获得当前访问的class
        Class<?> className = point.getTarget().getClass();
        //获得访问的方法名
        String methodName = point.getSignature().getName();
        //得到方法的参数的类型
        Class[] argClass = ((MethodSignature) point.getSignature()).getParameterTypes();
        String dataSource = DataSourceContextHolder.DEFAULT_DS;
        try {
            // 得到访问的方法对象
            Method method = className.getMethod(methodName, argClass);
            // 判断是否存在@IDataSource注解
            if (method.isAnnotationPresent(IDataSource.class)) {
                IDataSource annotation = method.getAnnotation(IDataSource.class);
                // 取出注解中的数据源名
                dataSource = annotation.value();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 切换数据源
        DataSourceContextHolder.setDB(dataSource);
    }

    @After("@annotation(IDataSource)")
    public void afterSwitchDS(JoinPoint point) {
        //清楚数据源
        DataSourceContextHolder.clearDB();
    }
}

应用:

package com.paic.phssp.springtest.service.impl;

import com.paic.phssp.springtest.dao.UserMapper;
import com.paic.phssp.springtest.dataSource.IDataSource;
import com.paic.phssp.springtest.dto.User;
import com.paic.phssp.springtest.service.IUserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
public class IUserServiceImpl implements IUserService {

    private final Logger log = LoggerFactory.getLogger(getClass());

    @Autowired
    private UserMapper userMapper;

    @Override
    @IDataSource(value="chenDataSource")
    public List<User> findAll() {
        return userMapper.getAll();
    }
}

运行结果:

技术分享图片

 

总结:

上面叨叨了那么多,其实重点:AOP+注解 结构。

 

SpringBoot 多数据源配置

原文:https://www.cnblogs.com/xiaozhuanfeng/p/10461638.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!