SpringCloud之基于SpringCloud+MybatisPlus+Config实现多个数据源的动态切换

版权声明:本文为博主原创文章,欢迎转载,转载请注明作者、原文超链接 ,博主地址:https://blog.csdn.net/qq_31122833。 https://blog.csdn.net/qq_31122833/article/details/81386609

一、首先,项目基于SpringCloud,配置文件在Git上(包括数据源的配置信息)。

二、开始基于原有项目进行重构

1、写一个动态数据源上下文.代码如下:

/**
 * 动态数据源上下文
 */
public class DbContextHolder {
    private static final ThreadLocal contextHolder = new ThreadLocal<>();

    /*
     * 管理所有的数据源id;
     * 主要是为了判断数据源是否存在;
     */
    public static List<String> dataSourceIds = new ArrayList<String>();

    /**
     * 设置数据源
     * @param dbTypeEnum
     */
    public static void setDbType(String dbTypeEnum) {
        contextHolder.set(dbTypeEnum);
    }

    /**
     * 取得当前数据源
     * @return
     */
    public static String getDbType() {
        return (String)contextHolder.get();
    }

    /**
     * 清除上下文数据
     */
    public static void clearDbType() {
        contextHolder.remove();
    }

    /**
     * 判断指定DataSrouce当前是否存在
     * @param dataSourceId
     * @return
     */
    public static boolean containsDataSource(String dataSourceId){
        return dataSourceIds.contains(dataSourceId);
    }
}

2、再写一个数据源路由类,代码如下:

/**
 * 数据源路由类
 */
public class DynamicDataSource extends AbstractRoutingDataSource {
    /**
     * 取得当前使用哪个数据源
     * @return
     */
    @Override
    protected Object determineCurrentLookupKey() {
        return DbContextHolder.getDbType();
    }
}

3、写一个自定义注解,用来传递需要哪个数据源:

/**
 * 在方法上使用,用于指定使用哪个数据源
 */
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDataSource {
    String value();
}

4、写一个AOP,只要有注解的就根据参数切换数据源:

@Component
@Order(-10)//保证该AOP在@Transactional之前执行
@Aspect
public class DBTypeAOP {

    @Pointcut("@annotation(TargetDataSource)")
    public void dbType() {
    }
    @Before("dbType()")
    public void before(JoinPoint joinPoint) throws Throwable {
        //获取当前的指定的数据源;
        System.out.println("---------------before---------------");
        TargetDataSource targetDataSource = ((MethodSignature)joinPoint.getSignature()).getMethod().getAnnotation(TargetDataSource.class);
        String dsId = targetDataSource.value();
        //如果不在我们注入的所有的数据源范围之内,那么输出警告信息,系统自动使用默认的数据源。
        if (!DbContextHolder.containsDataSource(dsId)) {
            System.err.println("数据源[{}]不存在,使用默认数据源 > {}"+targetDataSource.value()+joinPoint.getSignature());
        } else {
            System.out.println("Use DataSource : {} > {}"+targetDataSource.value()+joinPoint.getSignature());
            //找到的话,那么设置到动态数据源上下文中。
            DbContextHolder.setDbType(targetDataSource.value());
        }
    }

    @After("dbType()")
    public void after(JoinPoint joinPoint) {
        System.out.println("--------------after----------------");
        TargetDataSource targetDataSource = ((MethodSignature)joinPoint.getSignature()).getMethod().getAnnotation(TargetDataSource.class);
        System.out.println("Revert DataSource : {} > {}"+targetDataSource.value()+joinPoint.getSignature());
        //方法执行完毕之后,销毁当前数据源信息,进行垃圾回收。
        DbContextHolder.clearDbType();
    }
}

5、使用ImportBeanDefinitionRegistrar进行注册我们的多数据源:

/**
 * 动态数据源注册;
 */
public class DynamicDataSourceRegister  implements ImportBeanDefinitionRegistrar, EnvironmentAware {

    //如配置文件中未指定数据源类型,使用该默认值
    private static final Object DATASOURCE_TYPE_DEFAULT = "com.alibaba.druid.pool.DruidDataSource";

    private ConversionService conversionService = new DefaultConversionService();

    private PropertyValues dataSourcePropertyValues;

    // 默认数据源
    private DataSource defaultDataSource;

    private Map<String, DataSource> customDataSources = new HashMap<String, DataSource>();

    /**
     * 加载多数据源配置
     */
    @Override
    public void setEnvironment(Environment environment) {
       System.out.println("DynamicDataSourceRegister.setEnvironment()");
       initDefaultDataSource(environment);
        initCustomDataSources(environment);
    }

    /**
     * 加载主数据源配置.
     * @param env
     */
    private void initDefaultDataSource(Environment env){
       // 读取主数据源
       RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(env, "spring.datasource.");
       Map<String, Object> dsMap = new HashMap<String, Object>();
       dsMap.put("type", propertyResolver.getProperty("type"));
        dsMap.put("driverClassName", propertyResolver.getProperty("driverClassName"));
        dsMap.put("url", propertyResolver.getProperty("url"));
        dsMap.put("username", propertyResolver.getProperty("username"));
        dsMap.put("password", propertyResolver.getProperty("password"));
        //创建数据源;
        defaultDataSource = buildDataSource(dsMap);
        dataBinder(defaultDataSource, env);
    }

    /**
     * 初始化更多数据源
     */
    private void initCustomDataSources(Environment env) {
        // 读取配置文件获取更多数据源,也可以通过defaultDataSource读取数据库获取更多数据源
        RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(env, "custom.datasource.");
        String dsPrefixs = propertyResolver.getProperty("names");
        for (String dsPrefix : dsPrefixs.split(",")) {// 多个数据源
            Map<String, Object> dsMap = propertyResolver.getSubProperties(dsPrefix + ".");
            DataSource ds = buildDataSource(dsMap);
            customDataSources.put(dsPrefix, ds);
            dataBinder(ds, env);
        }
    }

    /**
     * 创建datasource.
     * @param dsMap
     * @return
     */
    @SuppressWarnings("unchecked")
    public DataSource buildDataSource(Map<String, Object> dsMap) {
        Object type = dsMap.get("type");
        if (type == null){
            type = DATASOURCE_TYPE_DEFAULT;// 默认DataSource
        }
        Class<? extends DataSource> dataSourceType;
        try {
            dataSourceType = (Class<? extends DataSource>) Class.forName((String) type);
            String driverClassName = dsMap.get("driverClassName").toString();
            String url = dsMap.get("url").toString();
            String username = dsMap.get("username").toString();
            String password = dsMap.get("password").toString();
            DataSourceBuilder factory =   DataSourceBuilder.create().driverClassName(driverClassName).url(url).username(username).password(password).type(dataSourceType);
            return factory.build();
        } catch (ClassNotFoundException e) {
           e.printStackTrace();
        }
        return null;
    }

    /**
     * 为DataSource绑定更多数据
     * @param dataSource
     * @param env
     */
    private void dataBinder(DataSource dataSource, Environment env){
       RelaxedDataBinder dataBinder = new RelaxedDataBinder(dataSource);
       dataBinder.setConversionService(conversionService);
       dataBinder.setIgnoreNestedProperties(false);//false
        dataBinder.setIgnoreInvalidFields(false);//false
        dataBinder.setIgnoreUnknownFields(true);//true
        if(dataSourcePropertyValues == null){
            Map<String, Object> rpr = new RelaxedPropertyResolver(env, "spring.datasource").getSubProperties(".");
            Map<String, Object> values = new HashMap<>(rpr);
            // 排除已经设置的属性
            values.remove("type");
            values.remove("driverClassName");
            values.remove("url");
            values.remove("username");
            values.remove("password");
            dataSourcePropertyValues = new MutablePropertyValues(values);
        }
        dataBinder.bind(dataSourcePropertyValues);
    }
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
       System.out.println("DynamicDataSourceRegister.registerBeanDefinitions()");
       Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
       // 将主数据源添加到更多数据源中
        targetDataSources.put("dataSource", defaultDataSource);
        DbContextHolder.dataSourceIds.add("dataSource");
        // 添加更多数据源
        targetDataSources.putAll(customDataSources);
        for (String key : customDataSources.keySet()) {
            DbContextHolder.dataSourceIds.add(key);
        }
        // 创建DynamicDataSource
        GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
        beanDefinition.setBeanClass(DynamicDataSource.class);
        beanDefinition.setSynthetic(true);
        MutablePropertyValues mpv = beanDefinition.getPropertyValues();
        //添加属性:AbstractRoutingDataSource.defaultTargetDataSource
        mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource);
        mpv.addPropertyValue("targetDataSources", targetDataSources);
        registry.registerBeanDefinition("dataSource", beanDefinition);
    }
}

三、最后,我们在Application.java这个启动类中,使用@import注册DynamicDataSourceRegister:

@EnableDiscoveryClient
@SpringBootApplication
//注册
@Import({DynamicDataSourceRegister.class})
public class CircleOfFriendsApplication {

	public static void main(String[] args) {
		SpringApplication.run(CircleOfFriendsApplication.class, args);
	}
}

四、运行:

执行的话,只需在你原有的方法上加上我们第3步写的自定义注解@TargetDataSource("数据源1")

扫描二维码关注公众号,回复: 3998933 查看本文章

例如:

五、效果:

---------------before---------------
Use DataSource : {} > {}ds1 com.luda.springcloud.client.controller.CircleOfFriendsController.queryCircleOfFriends(String,String,String)
2018-08-03 12:19:06.942  INFO [spring-cloud-circleOfFriends,e1c88ebca78edb0d,f27d8e29d1a698a3,false] 6708 --- [  XNIO-2 task-1] com.alibaba.druid.pool.DruidDataSource   : {dataSource-2} inited
--------------after----------------
 Time:5 ms - ID:com.luda.springcloud.client.mapper.CircleOfFriendsMapper.selectPage
 Execute SQL:SELECT id,`createUser`,content,imgUrl,createTime,ip FROM circle_of_friends ORDER BY createTime DESC LIMIT 0,10

Revert DataSource : {} > {}ds1  com.luda.springcloud.client.controller.CircleOfFriendsController.queryCircleOfFriends(String,String,String)

进入了AOP,并且获取我需要的数据源,在方法完毕后,进行清除操作。

给我点个赞吧!拜托了^-^

猜你喜欢

转载自blog.csdn.net/qq_31122833/article/details/81386609
今日推荐