用博客记录点滴……

spring boot mybatis 多数据源配置

package com.xynet.statistics.config.dataresources;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
 * 动态数据源
 * Copyright © 2019 xynet Tech Ltd. All rights reserved
 * @author: sund
 * @date: 2019年3月23日 下午4:35:39
 * @remark:
 */
public class DynamicDataSource extends AbstractRoutingDataSource {

    /**
     * 代码中的determineCurrentLookupKey方法取得一个字符串, 该字符串将与配置文件中的相应字符串进行匹配以定位数据源
     */
    @Override
    protected Object determineCurrentLookupKey() {
        /**
         * DynamicDataSourceContextHolder代码中使用setDataSourceType
         * 设置当前的数据源,在路由类中使用getDataSourceType进行获取,
         * 交给AbstractRoutingDataSource进行注入使用
         */
        return DynamicDataSourceContextHolder.getDataSourceType();
    }
}
package com.xynet.statistics.config.dataresources;

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.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

/**
 * 数据源切面
 * 
 * @Order(-5)保证该AOP在@Transactional之前执行 order 越小优先级越高
 * Copyright © 2019 xynet Tech Ltd. All rights reserved
 * @author: sund
 * @date: 2019年3月23日 下午4:24:49
 * @remark:
 */
@Aspect
@Order(-5)
@Component
public class DynamicDataSourceAspect {

    /**
     * @Before("@annotation(ds)") @Before:在方法执行之前进行执行: @annotation(
     * targetDataSource): 会拦截注解targetDataSource的方法,否则不拦截;
     * 
     * @param point
     * @param targetDataSource
     * @throws Throwable
     */
    @Before("@annotation(targetDataSource)")
    public void changeDataSource(JoinPoint point, TargetDataSource targetDataSource) throws Throwable {
        // 获取当前的指定的数据源;
        String dsId = targetDataSource.value();
        // 如果不在我们注入的所有的数据源范围之内,那么输出警告信息,系统自动使用默认的数据源。
        if (!DynamicDataSourceContextHolder.containsDataSource(dsId)) {
            System.out.println("数据源【" + targetDataSource.value() + "】不存在,使用默认数据源:" + point.getSignature());
        } else {
            System.out.println("Use DataSource:" + targetDataSource.value() + ":" + point.getSignature());
            // 找到的话,那么设置到动态数据源上下文中指定的数据源
            DynamicDataSourceContextHolder.setDataSourceType(targetDataSource.value());
        }
    }

    @After("@annotation(targetDataSource)")
    public void restoreDataSource(JoinPoint point, TargetDataSource targetDataSource) {
        System.out.println("Revert DataSource:" + targetDataSource.value() + ":" + point.getSignature());
        // 方法执行完毕之后,销毁当前数据源信息,进行垃圾回收
        DynamicDataSourceContextHolder.clearDataSourceType();
    }
}
package com.xynet.statistics.config.dataresources;

import java.util.ArrayList;
import java.util.List;

/**
 * 动态数据源上下文
 * Copyright © 2019 xynet Tech Ltd. All rights reserved
 * @author: sund
 * @date: 2019年3月23日 下午4:32:45
 * @remark:
 */
public class DynamicDataSourceContextHolder {

    /**
     * 当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,
     * 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
     */
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();

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

    /**
     * 使用setDataSourceType设置当前的
     * @param dataSourceType
     */
    public static void setDataSourceType(String dataSourceType) {
        contextHolder.set(dataSourceType);
    }

    public static String getDataSourceType() {
        return contextHolder.get();
    }

    public static void clearDataSourceType() {
        contextHolder.remove();
    }

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

}
package com.xynet.statistics.config.dataresources;

import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.bind.RelaxedDataBinder;
import org.springframework.boot.bind.RelaxedPropertyResolver;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotationMetadata;

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

/**
 * 动态数据源注册 Copyright © 2019 xynet Tech Ltd. All rights reserved
 * 
 * @author: sund
 * @date: 2019年3月23日 下午4:34:37
 * @remark:要在启动类上增加注解  @Import({DynamicDataSourceRegister.class})
 */
public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware {

    private static final Object DATASOURCE_TYPE_DEFAULT = "org.apache.tomcat.jdbc.pool.DataSource";
    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) {
        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);
    }

    /**
     * 加载更多据源配置.
     * 
     * @param env
     */
    private void initCustomDataSources(Environment env) {
        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);
        }
    }

    public DataSource buildDataSource(Map<String, Object> dsMap) {
        Object type = dsMap.get("type");
        if (type == null) {
            type = DATASOURCE_TYPE_DEFAULT;
        }
        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;
    }

    private void dataBinder(DataSource dataSource, Environment env) {
        RelaxedDataBinder dataBinder = new RelaxedDataBinder(dataSource);
        dataBinder.setConversionService(conversionService);
        dataBinder.setIgnoreNestedProperties(false);
        dataBinder.setIgnoreInvalidFields(false);
        dataBinder.setIgnoreUnknownFields(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) {
        Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
        // 将主数据源添加到更多数据源中
        targetDataSources.put("dataSource", defaultDataSource);
        DynamicDataSourceContextHolder.dataSourceIds.add("dataSource");
        // 添加更多数据源
        targetDataSources.putAll(customDataSources);
        for (String key : customDataSources.keySet()) {
            DynamicDataSourceContextHolder.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);
        System.out.println("============注册数据源成功==============");
    }
}
package com.xynet.statistics.config.dataresources;

import java.lang.annotation.*;

/**
 * 自定义注解,数据源指定
 * Copyright © 2019 xynet Tech Ltd. All rights reserved
 * @author: sund
 * @date: 2019年3月23日 下午4:37:16
 * @remark:
 */
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDataSource {
    String value();
}

将上面五个类建立好后在启动类中加入@Import({DynamicDataSourceRegister.class}),这个很关键不然加载不了多数据源,只会调用默认数据源

@EnableDiscoveryClient
@EnableHystrix
@EnableEurekaClient
@SpringBootApplication
@EnableCircuitBreaker
@ServletComponentScan
@EnableRedisHttpSession
@EnableTransactionManagement 
@EnableFeignClients
@EnableScheduling
@MapperScan(basePackages = "com.xynet.statistics.dao")
@Import({DynamicDataSourceRegister.class})
public class XynetServiceStatisticsApplication {

    public static void main(String[] args) {
        SpringApplication.run(XynetServiceStatisticsApplication.class, args);
    }
    
    @Bean
    public AsyncTaskExecutor paraTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setThreadNamePrefix("statistics-paraTaskExecutor-Executor");
        executor.setCorePoolSize(24);
        executor.setQueueCapacity(100);
        executor.setMaxPoolSize(500);
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());

        executor.initialize();
        /*
         * // 设置拒绝策略 executor.setRejectedExecutionHandler(new
         * RejectedExecutionHandler() {
         * 
         * @Override public void rejectedExecution(Runnable r,
         * ThreadPoolExecutor executor) { // ..... } }); // 使用预定义的异常处理类
         * executor.setRejectedExecutionHandler(new
         * ThreadPoolExecutor.CallerRunsPolicy());
         */
        return executor;
    }
}

数据源切换注解要加到service上不要加到Mapper中上否则不会生效


@Service
public class BigDataServiceImpl implements BigDataService {

    
@Override @TargetDataSource(
"slave1") public List<T_jq_jqxx> selectJqbhByShbh(String shbh) { return t_jq_jqxxMapper.selectJqbhByShbh(shbh); }

application-dev.yaml 配置文件如下:

spring:
  profiles: dev
  #mysql
  datasource:      
      type: com.alibaba.druid.pool.DruidDataSource 
      initialSize: 5  
      minIdle: 5  
      maxActive: 20  
      maxWait: 60000  
      timeBetweenEvictionRunsMillis: 60000  
      minEvictableIdleTimeMillis: 300000  
      validationQuery: SELECT NOW()  
      testWhileIdle: true  
      testOnBorrow: true  
      testOnReturn: false  
      poolPreparedStatements: true  
      maxPoolPreparedStatementPerConnectionSize: 20  
      filters: stat,log4j  
      connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 
      driverClassName: com.mysql.jdbc.Driver
      username: root
      password: 1234
      url: jdbc:mysql://192.168.1.253:3306/xy-platform?characterEncoding=utf-8&serverTimezone=GMT%2B8&useSSL=false 

  #jpa
  jpa: 
      show-sql: true
      open-in-view: true  
            
#redis
  redis:
    database: 15
    host: 192.168.1.253
    password:  
    port: 6379
    pool:
      min-idle: 1
      max-idle: 8
      max-active: 100
      max-wait: 1    
    timeout: 10000 

custom:
    datasource:  
      names: slave1,slave2
      slave1:
        type: com.alibaba.druid.pool.DruidDataSource
        driverClassName: com.mysql.jdbc.Driver
        username: root
        password: 1234
        url: jdbc:mysql://192.168.1.253:3306/test?characterEncoding=utf-8&serverTimezone=GMT%2B8&useSSL=false
      slave2:    
        type: com.alibaba.druid.pool.DruidDataSource
        driverClassName: com.mysql.jdbc.Driver
        username: root
        password: 1234
        url: jdbc:mysql://192.168.1.253:3306/sy?characterEncoding=utf-8&serverTimezone=GMT%2B8&useSSL=false
        
 #log
logging:
    level:
      root: INFO
      com.xynet: DEBUG
    file: logs/service-statistics.log
      
#server
server:
  port: 8081  

app:
    fileHome: C:\
    tempFileHome: tmp/
    fileDownloadUrl: C:\

auth:
    #url: http://localhost:7011/remote/authRemoteService
    url: http://192.168.1.253:8899/authentication/remote/authRemoteService
       
eureka:
  client:
    #service-url.defaultZone: http://localhost:8761/eureka      
    service-url.defaultZone: http://admin:123@192.168.1.253:7010/eureka 
        
    enabled: true  
    registerWithEureka: true
    fetchRegistry: true  
    healthcheck.enabled: true
    
  instance:
    lease-renewal-interval-in-seconds: 5
    lease-expiration-duration-in-seconds: 5
    prefer-ip-address: true    
    instance-id: ${spring.cloud.client.ipAddress}:${server.port}      
    
    
    

最上附上项目结构图:标红的地方为要修改的地方

 

posted @ 2019-03-23 17:19  aegisada  阅读(619)  评论(0编辑  收藏  举报