Spring Boot配置多資料來源並實現Druid自動切換
阿新 • • 發佈:2018-12-14
SpringBoot多資料來源切換,先上配置檔案:
1.pom:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.river</groupId> <artifactId>goalintl</artifactId> <version>1.0-SNAPSHOT</version> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.4.RELEASE</version> </parent> <dependencies> <!--spring-boot--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--spring-boot-freemarker--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-freemarker</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <!----> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.6</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.0</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus</artifactId> <version>2.1.1</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatisplus-spring-boot-starter</artifactId> <version>1.0.4</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> </dependencies> </project>
2.application.yaml
spring: freemarker: cache: false charset: utf-8 enabled: true datasource: type: com.alibaba.druid.pool.DruidDataSource primary: driverClassName: com.mysql.jdbc.Driver username: root password: root url: jdbc:mysql://localhost:3306/as type: com.alibaba.druid.pool.DruidDataSource local: driver-class-name: com.mysql.jdbc.Driver username: root password: root url: jdbc:mysql://localhost:3306/test type: com.alibaba.druid.pool.DruidDataSource prod: driver-class-name: com.mysql.jdbc.Driver username: root password: root url: jdbc:mysql://localhost:3306/guns type: com.alibaba.druid.pool.DruidDataSource server: port: 8085 diy.user: id: 12 logging: file: /log.txt level: trace mybatis-plus: mapper-locations: classpath:mapper/*Mapper.xml typeAliasesPackage: global-config: id-type: 3 refresh-mapper: true capital-mode: true field-strategy: 2 db-column-underline: false
3.configuration
package com.river.datasource; import com.alibaba.druid.pool.DruidDataSource; import com.river.common.ContextConst; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.PlatformTransactionManager; import javax.sql.DataSource; import java.util.HashMap; @Configuration public class MutiplyDataSource { @Bean(name = "dataSourcePrimary") @ConfigurationProperties(prefix = "spring.datasource.primary") public DataSource primaryDataSource(){ return new DruidDataSource(); } @Bean(name = "dataSourceLocal") @ConfigurationProperties(prefix = "spring.datasource.local") public DataSource localDataSource(){ return new DruidDataSource(); } @Bean(name = "dataSourceProd") @ConfigurationProperties(prefix = "spring.datasource.prod") public DataSource prodDataSource(){ return new DruidDataSource(); } @Primary @Bean(name = "dynamicDataSource") public DataSource dynamicDataSource() { DynamicDataSource dynamicDataSource = new DynamicDataSource(); //配置預設資料來源 dynamicDataSource.setDefaultTargetDataSource(primaryDataSource()); //配置多資料來源 HashMap<Object, Object> dataSourceMap = new HashMap(); dataSourceMap.put(ContextConst.DataSourceType.PRIMARY.name(),primaryDataSource()); dataSourceMap.put(ContextConst.DataSourceType.LOCAL.name(),localDataSource()); dataSourceMap.put(ContextConst.DataSourceType.PROD.name(),prodDataSource()); dynamicDataSource.setTargetDataSources(dataSourceMap); return dynamicDataSource; } /** * 配置@Transactional註解事務 * @return */ @Bean public PlatformTransactionManager transactionManager() { return new DataSourceTransactionManager(dynamicDataSource()); } }
4.資料來源持有類
package com.river.datasource;
import lombok.extern.log4j.Log4j;
@Log4j
public class DataSourceContextHolder {
private static final String DEFAULT_DATASOURCE = "PRIMARY_DATASOURCE";
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
public static void setDataSource(String dbType){
log.info("切換到["+dbType+"]資料來源");
contextHolder.set(dbType);
}
public static String getDataSource(){
return contextHolder.get();
}
public static void clearDataSource(){
contextHolder.remove();
}
}
5.定義切換資料來源的註解和切面
package com.river.annotation;
import com.river.common.ContextConst;
import java.lang.annotation.*;
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
ContextConst.DataSourceType value() default ContextConst.DataSourceType.PRIMARY;
}
package com.river.aspect;
import com.river.annotation.DataSource;
import com.river.common.ContextConst;
import com.river.datasource.DataSourceContextHolder;
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.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Component
@Aspect
public class DynamicDataSourceAspect {
@Before("execution(* com.river.service..*.*(..))")
public void before(JoinPoint point){
try {
DataSource annotationOfClass = point.getTarget().getClass().getAnnotation(DataSource.class);
String methodName = point.getSignature().getName();
Class[] parameterTypes = ((MethodSignature) point.getSignature()).getParameterTypes();
Method method = point.getTarget().getClass().getMethod(methodName, parameterTypes);
DataSource methodAnnotation = method.getAnnotation(DataSource.class);
methodAnnotation = methodAnnotation == null ? annotationOfClass:methodAnnotation;
ContextConst.DataSourceType dataSourceType = methodAnnotation != null && methodAnnotation.value() !=null ? methodAnnotation.value() :ContextConst.DataSourceType.PRIMARY ;
DataSourceContextHolder.setDataSource(dataSourceType.name());
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
@After("execution(* com.river.service..*.*(..))")
public void after(JoinPoint point){
DataSourceContextHolder.clearDataSource();
}
}
package com.river.common;
public interface ContextConst {
enum DataSourceType{
PRIMARY,LOCAL,PROD,TEST
}
}
6.資料來源路由實現類
package com.river.datasource;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource{
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSource();
}
}
7.使用時,通過註解指定資料來源
package com.river.service.impl;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import com.river.annotation.DataSource;
import com.river.common.ContextConst;
import com.river.entity.User;
import com.river.mapper.PrimaryUserMapper;
import com.river.service.ParmaryUserService;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class ParmaryUserServiceImpl extends ServiceImpl<PrimaryUserMapper,User> implements ParmaryUserService{
@Autowired
private PrimaryUserMapper primaryUserMapper;
@Override
public List<User> sel(){
return primaryUserMapper.selectList(null);
}
@DataSource(ContextConst.DataSourceType.PROD)
@Override
public List<User> sell() {
return primaryUserMapper.selectList(null);
}
@DataSource(ContextConst.DataSourceType.LOCAL)
@Override
public List<User> selle() {
return primaryUserMapper.selectList(null);
}
}
7.另外,補充一下其他部分程式碼;
entity:
@Data
@TableName("ACT_USER")
public class User {
@TableId
@TableField
private Integer id;
@TableField("USERNAME")
private String username;
@TableField("PASSWORD")
private String password;
@TableField("ROLE_ID")
private Integer roleId;
}
mapper:
package com.river.mapper;
import com.baomidou.mybatisplus.mapper.BaseMapper;
import com.river.entity.User;
public interface PrimaryUserMapper extends BaseMapper<User>{
}
controller:
@RestController
public class DataController {
@Autowired
private ParmaryUserService parmaryUserService;
@RequestMapping("login")
public List<User> login(Integer type){
switch (type){
case 1:
return parmaryUserService.sel();
case 2:
return parmaryUserService.sell();
default:
return parmaryUserService.selle();
}
}
}
入口類:
package com.river;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.BeansException;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
@MapperScan("com.river.mapper")
//排除DataSource自動配置類,否則會預設自動配置,不會使用我們自定義的DataSource,並且啟動報錯
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class GoalintlApplication implements CommandLineRunner,ApplicationContextAware{
public static void main(String[] args) {
SpringApplicationBuilder springApplicationBuilder = new SpringApplicationBuilder(GoalintlApplication.class);
springApplicationBuilder.profiles("dev").logStartupInfo(true).run(args);
}
@Override
public void run(String... args) throws Exception {
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
}
}
說明一下實現思路:
springboot有提供AbstractRoutingDataSource#determineCurrentLookupKey抽象方法去指定資料來源,我們要做的就是實現切換資料來源的邏輯;通過AOP在呼叫資料庫之前切換資料來源;
本來在切面內做了一個快取,記錄上一次使用的資料來源,如果這一次使用相同的就不用切換了,但是發現初始化資料連線才是消耗大的,後面切換資料來源其實就是去指定用哪個資料庫連線而已,不再消耗資源了;
下面的程式碼顯示了切換資料來源時只是通過key去拿對應的dataSource,而相關的dataSource在第一次呼叫時就初始化一次就可以了;
protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
Object lookupKey = determineCurrentLookupKey();
DataSource dataSource = this.resolvedDataSources.get(lookupKey);
if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
dataSource = this.resolvedDefaultDataSource;
}
if (dataSource == null) {
throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
}
return dataSource;
}
類似文章很多,這裡自己實現了一把;