Springboot之多數據源的配置使用
- 2019 年 10 月 8 日
- 筆記
引入
現在的企業服務逐漸地呈現出數據的指數級增長趨勢,無論從數據庫的選型還是搭建,大多數的團隊都開始考慮多樣化的數據庫來支撐存儲服務。例如分佈式數據庫、Nosql數據庫、內存數據庫、關係型數據庫等等。再到後端開發來說,服務的增多,必定需要考慮到多數據源的切換使用來兼容服務之間的調用。為解決這一難題,今天就來分享一個關於多數據源的切換使用配置。
使用前提:
- JDK8+
- Springboot
- IDEA
- Mysql5.5+
- lombok
02
具體配置
使用spring AOP的思想,在進入orm之前,進行DataSource的Type切換。 添加maven依賴,我使用的是springboot-2.0.5版本。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.10</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency>
創建application.yml
server: port: 8080 spring: profiles: active: dev application: name: @pom.artifactId@ jpa: generate-ddl: false show-sql: true hibernate: ddl-auto: none datasource: default: url: jdbc:mysql://localhost:3306/bboyhan?characterEncoding=UTF-8&useUnicode=true&useSSL=false username: root password: root driver: com.mysql.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource names: tb1,tb2 tb1: url: jdbc:mysql://localhost:3306/tb1?characterEncoding=UTF-8&useUnicode=true&useSSL=false username: root password: root driver: com.mysql.jdbc.Driver tb2: url: jdbc:mysql://localhost:3306/tb2?characterEncoding=UTF-8&useUnicode=true&useSSL=false username: root password: root driver: com.mysql.jdbc.Driver
創建註解類@DbName
import java.lang.annotation.*; /** * @Auther: bboyHan * @Date: 2019/1/22 18:12 * @Description: */ @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface DbName { String value(); }
創建aop切面類ChooseDbAspect
import com.jkzx.common.annotation.DbName; import com.jkzx.common.config.dbsource.DynamicDataSourceContextHolder; import lombok.extern.slf4j.Slf4j; 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.annotation.Pointcut; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; /** * @Auther: bboyHan * @Date: 2019/1/22 18:13 * @Description: */ @Component @Order(-1)//保證在@Transactional之前執行 @Aspect @Slf4j public class ChooseDbAspect { @Pointcut("@annotation(com.bboyhan.annotation.DbName)") public void chooseDbPointCut(){} //改變數據源 @Before("@annotation(dbName)") public void changeDataSource(JoinPoint joinPoint, DbName dbName) { String dbid = dbName.value(); if (!DynamicDataSourceContextHolder.isContainsDataSource(dbid)) { //joinPoint.getSignature() :獲取連接點的方法簽名對象 log.error("數據源 " + dbid + " 不存在使用默認的數據源 -> " + joinPoint.getSignature()); } else { log.debug("使用數據源:" + dbid); DynamicDataSourceContextHolder.setDataSourceType(dbid); } } @After("@annotation(dbName)") public void clearDataSource(JoinPoint joinPoint, DbName dbName) { log.debug("清除數據源 " + dbName.value() + " ! - start"); DynamicDataSourceContextHolder.clearDataSourceType(); } }
使用ThreadLocal創建一個線程安全的類
import lombok.extern.slf4j.Slf4j; import java.util.ArrayList; import java.util.List; /** * @Auther: bboyHan * @Date: 2019/1/22 18:13 * @Description: */ @Slf4j public class DynamicDataSourceContextHolder { //存放當前線程使用的數據源類型信息 private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>(); //存放數據源id public static List<String> dataSourceIds = new ArrayList<String>(); //設置數據源 public static void setDataSourceType(String dataSourceType) { contextHolder.set(dataSourceType); } //獲取數據源 public static String getDataSourceType() { return contextHolder.get(); } //清除數據源 public static void clearDataSourceType() { contextHolder.remove(); log.info("清除數據源:{}", contextHolder.get() + " - end"); } //判斷當前數據源是否存在 public static boolean isContainsDataSource(String dataSourceId) { return dataSourceIds.contains(dataSourceId); } }
創建一個Register實現ImportBeanDefinitionRegistrar, EnvironmentAware
import lombok.extern.slf4j.Slf4j; import org.springframework.beans.MutablePropertyValues; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.GenericBeanDefinition; import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.context.EnvironmentAware; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; import org.springframework.core.env.Environment; import org.springframework.core.type.AnnotationMetadata; import javax.sql.DataSource; import java.util.HashMap; import java.util.Map; /** * @Auther: bboyHan * @Date: 2019/1/22 18:19 * @Description: */ @Slf4j public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware { //指定默認數據源(springboot2.0默認數據源是hikari,在這裡我使用DruidDataSource) private static final String DATASOURCE_TYPE_DEFAULT = "com.alibaba.druid.pool.DruidDataSource"; //默認數據源 private DataSource defaultDataSource; //用戶自定義數據源 private Map<String, DataSource> slaveDataSources = new HashMap<>(); @Override public void setEnvironment(Environment environment) { initDefaultDataSource(environment); initMutilDataSources(environment); } private void initDefaultDataSource(Environment env) { // 讀取主數據源,解析yml文件 Map<String, Object> dsMap = new HashMap<>(); dsMap.put("driver", env.getProperty("spring.datasource.default.driver")); dsMap.put("url", env.getProperty("spring.datasource.default.url")); dsMap.put("username", env.getProperty("spring.datasource.default.username")); dsMap.put("password", env.getProperty("spring.datasource.default.password")); dsMap.put("type", env.getProperty("spring.datasource.default.type")); defaultDataSource = buildDataSource(dsMap); } private void initMutilDataSources(Environment env) { // 讀取配置文件獲取更多數據源 String dsPrefixs = env.getProperty("spring.datasource.names"); for (String dsPrefix : dsPrefixs.split(",")) { // 多個數據源 Map<String, Object> dsMap = new HashMap<>(); dsMap.put("driver", env.getProperty("spring.datasource." + dsPrefix + ".driver")); dsMap.put("url", env.getProperty("spring.datasource." + dsPrefix + ".url")); dsMap.put("username", env.getProperty("spring.datasource." + dsPrefix + ".username")); dsMap.put("password", env.getProperty("spring.datasource." + dsPrefix + ".password")); DataSource ds = buildDataSource(dsMap); slaveDataSources.put(dsPrefix, ds); } } @Override public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) { Map<Object, Object> targetDataSources = new HashMap<Object, Object>(); //添加默認數據源 targetDataSources.put("dataSource", this.defaultDataSource); DynamicDataSourceContextHolder.dataSourceIds.add("dataSource"); //添加其他數據源 targetDataSources.putAll(slaveDataSources); DynamicDataSourceContextHolder.dataSourceIds.addAll(slaveDataSources.keySet()); //創建DynamicDataSource GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); beanDefinition.setBeanClass(DynamicDataSource.class); beanDefinition.setSynthetic(true); MutablePropertyValues mpv = beanDefinition.getPropertyValues(); mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource); mpv.addPropertyValue("targetDataSources", targetDataSources); //註冊 - BeanDefinitionRegistry beanDefinitionRegistry.registerBeanDefinition("dataSource", beanDefinition); log.info("Dynamic DataSource Registry"); } public DataSource buildDataSource(Map<String, Object> dataSourceMap) { try { Object type = dataSourceMap.get("type"); if (type == null) { type = DATASOURCE_TYPE_DEFAULT;// 默認DataSource } Class<? extends DataSource> dataSourceType; dataSourceType = (Class<? extends DataSource>) Class.forName((String) type); String driverClassName = dataSourceMap.get("driver").toString(); String url = dataSourceMap.get("url").toString(); String username = dataSourceMap.get("username").toString(); String password = dataSourceMap.get("password").toString(); // 自定義DataSource配置 DataSourceBuilder factory = DataSourceBuilder.create().driverClassName(driverClassName).url(url) .username(username).password(password).type(dataSourceType); return factory.build(); } catch (ClassNotFoundException e) { e.printStackTrace(); } return null; } }
創建一個DynamicDataSource重寫AbstractRoutingDataSource的determineCurrentLookupKey()方法
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; /** * @Auther: bboyHan * @Date: 2019/1/22 18:20 * @Description: */ public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DynamicDataSourceContextHolder.getDataSourceType(); } }
0
3
測試
略,如果不明白的地方,可以識別文末二維碼加小編諮詢。
文章部分資源來自於網絡,在此鳴謝。