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}),這個很關鍵否則加載不了多數據源,只會調用默認數據源java
@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中上不然不會生效mysql
@Service
public class BigDataServiceImpl implements BigDataService {redis
@Override @TargetDataSource("slave1") public List<T_jq_jqxx> selectJqbhByShbh(String shbh) { return t_jq_jqxxMapper.selectJqbhByShbh(shbh); }
}
application-dev.yaml 配置文件以下:spring
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}
最上附上項目結構圖:標紅的地方爲要修改的地方sql