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}
最上附上项目结构图:标红的地方为要修改的地方