Springboot多数据源配置

一、了解AbstractRoutingDataSource

Spring boot提供了AbstractRoutingDataSource 根据用户定义的规则选择当前的数据源,这样我们可以在执行查询之前,设置使用的数据源。实现可动态路由的数据源,在每次数据库查询操作前执行。它的抽象方法 determineCurrentLookupKey() 决定使用哪个数据源。

org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource 源码的介绍:
 

大概意思是:
AbstractRoutingDataSource的getConnection() 方法根据查找 lookup key 键对不同目标数据源的调用,通常是通过(但不一定)某些线程绑定的事物上下文来实现。

AbstractRoutingDataSource的多数据源动态切换的核心逻辑是:在程序运行时,把数据源数据源通过 AbstractRoutingDataSource 动态织入到程序中,灵活的进行数据源切换。
基于AbstractRoutingDataSource的多数据源动态切换,可以实现读写分离,这么做缺点也很明显,无法动态的增加数据源。

实现逻辑:

1.定义DynamicDataSource类继承抽象类AbstractRoutingDataSource,并实现了determineCurrentLookupKey()方法。
2.把配置的多个数据源会放在AbstractRoutingDataSource的 targetDataSources和defaultTargetDataSource中,然后通过afterPropertiesSet()方法将数据源分别进行复制到resolvedDataSources和resolvedDefaultDataSource中。
3.调用AbstractRoutingDataSource的getConnection()的方法的时候,先调用determineTargetDataSource()方法返回DataSource在进行getConnection()。

二:具体实现

1.配置依赖

spring常规依赖

    <properties>
		<java.version>1.8</java.version>
		<pagehelper.boot.version>1.2.5</pagehelper.boot.version>
		<druid.version>1.1.14</druid.version>
	</properties>

    <parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.0.3.RELEASE</version>
 	</parent>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>2.1.3</version>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<scope>runtime</scope>
			<optional>true</optional>
		</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>

        <dependency>
		    <groupId>org.apache.commons</groupId>
		    <artifactId>commons-lang3</artifactId>
		</dependency>

		<!--阿里数据库连接池 -->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid-spring-boot-starter</artifactId>
		</dependency>
		<!-- SpringBoot 拦截器 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-aop</artifactId>
		</dependency>
		<!-- pagehelper 分页插件 -->
		<dependency>
			<groupId>com.github.pagehelper</groupId>
			<artifactId>pagehelper-spring-boot-starter</artifactId>
		</dependency>
	</dependencies>

SpringBootApplication.java

取消自动数据源配置

@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })

或者加上

@SpringBootApplication

@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class})

@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
public class SpringBootApplication{

	public static void main(String[] args) {
		SpringApplication.run(SpringBootApplication.class, args);
	}
}

配置文件参数

application.yml(并引入相关数据源的yml文件)

server:
  port: 8080
  servlet:
    # 应用的访问路径
    context-path: /
  tomcat:
    uri-encoding: UTF-8
    threads:
      max: 400
      min-spare: 20

spring:
  profiles:
    # 额外读取datasource配置
    active: datasource

mybatis:
  # 搜索指定包别名
  type-aliases-package: com.lg.**.dto
  # 配置mapper的扫描,找到所有的mapper.xml映射文件
  mapper-locations: classpath:/mybatis/**/*.xml
  # 加载全局的配置文件
  config-location: classpath:mybatis-config.xml

application-datasource.yml--数据源相关配置

# 数据源配置
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    druid:
      # 主数据库
      master:
        jdbc-url: jdbc:mysql://localhost:3306/master?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
        username: root
        password: root
      # 其他数据库(从数据库)
      slave:
        enabled: true
        jdbc-url: jdbc:mysql://localhost:3306/slave?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
        username: root
        password: root
      # 初始连接数
      initial-size: 5
      # 最小连接池数量
      min-idle: 10
      # 最大连接池数量
      max-active: 20
      # 配置获取连接等待超时的时间
      max-wait: 60000
      # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
      time-between-eviction-runs-millis: 60000
      # 配置一个连接在池中最小生存的时间,单位是毫秒
      min-evictable-idle-time-millis: 300000
      # 配置一个连接在池中最大生存的时间,单位是毫秒
      #max-evictable-idle-time-millis: 900000
      # 配置检测连接是否有效
      validation-query: select 1 from dual
      test-while-idle: true
      test-on-borrow: false
      test-on-return: false

通过AbstractRoutingDataSource实现数据源动态切换

Springboot提供了AbstractRoutingDataSource 根据用户定义的规则选择当前的数据源,这样我们可以在执行查询之前,切换到需要的数据源。实现可动态路由的数据源,在每次数据库查询操作前执行。它的抽象方法 determineCurrentLookupKey() 决定使用哪个数据源。
 

/**
 * 
 * 数据源枚举类
 *
 */
public enum DataSourceType {
    /**
     * 主库
     */
    MASTER,
    /**
     * 从库
     */
    SLAVE
}

配置数据源切换注解,在具体类或方法上添加注解,标明要使用的数据库

/**
 * 
 * 数据源切换注解
 * 优先级:先方法,后类,如果方法覆盖了类上的数据源类型,以方法的为准,否则以类上的为准
 *
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
    /**
     * 切换数据源名称
     */
    DataSourceType value() default DataSourceType.SLAVE;
}

配置缓存数据源

package com.db.system.config;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.db.system.utils.DataSourceType;

/**
 * 数据源切换处理
 * @Package: com.db.system.config
 * @ClassName: DynamicDataSourceContextHolder.java
 * @Description:数据源切换处理
 * 
 * @author kaifa008
 * @date 2022年1月
 */
public class DynamicDataSourceContextHolder {
 
    private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class);
    /**
     * 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本,
     *  所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
     */
    private static final ThreadLocal<DataSourceType> CONTEXT_HOLDER = new ThreadLocal<>();
 
    /**
     * 设置数据源的变量
     */
    public static void setDataSourceType(DataSourceType dataSourceType) {
        logger.info("切换到{{}}数据源", dataSourceType);
        CONTEXT_HOLDER.set(dataSourceType);
    }
 
    /**
     * 获得数据源的变量
     */
    public static DataSourceType getDataSourceType() {
        return CONTEXT_HOLDER.get();
    }
 
    /**
     * 清空数据源变量
     */
    public static void clearDataSourceType() {
        CONTEXT_HOLDER.remove();
    }
    
}

继承AbstractRoutingDataSource

package com.db.system.config;

import java.util.Map;

import javax.sql.DataSource;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
 * 动态切换数据源
 * @Package: com.db.system.config
 * @ClassName: DynamicDataSource.java
 * @Description:动态切换数据源
 * 
 * @author kaifa008
 * @date 2022年1月
 */
public class DynamicDataSource extends AbstractRoutingDataSource {

	public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
        super.setDefaultTargetDataSource(defaultTargetDataSource);
        super.setTargetDataSources(targetDataSources);
        // afterPropertiesSet()方法调用时用来将targetDataSources的属性写入resolvedDataSources中的
        super.afterPropertiesSet();
    }
 
    /**
     * 获取数据源的信息
     * @return
     */
    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceContextHolder.getDataSourceType();
    }
}

determineCurrentLookupKey在每次取数据源时,决定当前取哪个数据源。

该方法返回需要使用的DataSource的key值,然后根据这个key从resolvedDataSources这个map里取出对应的DataSource,如果找不到,则用默认的resolvedDefaultDataSource

配置多数据源config

package com.db.system.config;

import java.util.HashMap;
import java.util.Map;

import javax.sql.DataSource;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import com.db.system.utils.DataSourceType;

/**
 * 配置多数据源(Druid)
 * @Package: com.db.system.config
 * @ClassName: DataSourceConfig.java
 * @Description:配置多数据源(Druid)
 * 
 * @author kaifa008
 * @date 2022年1月
 */
@Configuration
public class DataSourceConfig {
 
    /**
	 * 查看配置文件中数据库连接池主从
	 */
    @Value("${dataSource.dbType}")
	private String dbType;
 
    @Bean(name="MASTER")
    @ConfigurationProperties("spring.datasource.druid.master")
    public DataSource masterDataSource() {
        return DataSourceBuilder.create().build();
    }
 
    @Bean(name="SLAVE")
    @ConfigurationProperties("spring.datasource.druid.slave")
    public DataSource slaveDataSource() {
        return DataSourceBuilder.create().build();
    }
 
    @Bean(name = "dynamicDataSource")
    @Primary
    public DynamicDataSource dynamicDataSource() {
        if (StringUtils.isEmpty(dbType)) {
			dbType = "SLAVE";
		}
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DataSourceType.SLAVE, slaveDataSource());
        targetDataSources.put(DataSourceType.MASTER, masterDataSource());
		DataSource defaultTargetDataSource = null;
		switch (dbType) {
			case "SLAVE":
				defaultTargetDataSource = (DataSource) targetDataSources.get(DataSourceType.SLAVE);
				break;
    	   	case "MASTER":
    	   		defaultTargetDataSource = (DataSource) targetDataSources.get(DataSourceType.MASTER);
    	   		break;
			default:
				defaultTargetDataSource = (DataSource) targetDataSources.get(DataSourceType.SLAVE);
				break;
		}
//		DynamicDataSource dynamicDataSource = new DynamicDataSource();
//		dynamicDataSource.setDefaultTargetDataSource(defaultTargetDataSource);
//		dynamicDataSource.setTargetDataSources(targetDataSources);
//		return dynamicDataSource;
        return new DynamicDataSource(defaultTargetDataSource, targetDataSources);
    }
}

配置了多个数据源,并通过读取yml参数配置初始化,可以通过yml优先使用哪个数据源。
DynamicDataSource就是上面覆写AbstractRoutingDataSource的类。

配置aop

package com.db.system.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import com.db.system.config.DynamicDataSourceContextHolder;
import com.db.system.interfaces.DataSource;

/**
 * 数据源注解AOP拦截并切换
 * @Package: com.db.system.aop
 * @ClassName: DataSourceAspect.java
 * @Description:数据源注解AOP拦截并切换
 * 
 * @author kaifa008
 * @date 2022年1月
 */
@Aspect
@Order(1)
@Component
public class DataSourceAspect {
 
//  @Pointcut("@annotation(dataSource) || @within(dataSource)")
    @Pointcut("@annotation(com.db.system.interfaces.DataSource)")
    public void dataSourcePointCut() {
    }
 
    @Around("dataSourcePointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        DataSource dataSource = getDataSource(point);
        if (dataSource != null) {
            DynamicDataSourceContextHolder.setDataSourceType(dataSource.value());
        }
        try {
            return point.proceed();
        } finally {
            DynamicDataSourceContextHolder.clearDataSourceType();
        }
    }
 
    /**
     * 获取需要切换的数据源
     * @param point
     * @return
     */
    public DataSource getDataSource(ProceedingJoinPoint point) {
        MethodSignature signature = (MethodSignature) point.getSignature();
        DataSource dataSource = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class);
        if (dataSource != null) {
            return dataSource;
        }
        return AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class);
    }
}

通过注解配置的切入点,进行注解读取,然后选择对应的数据源。

@Around("dataSourcePointCut()")

或者使用@Before("dataSourcePointCut())")和@After("dataSourcePointCut()")

执行方法前后设置数据源

应用层配置

通过上述配置,多数据源配置完成了。就可以在controller里使用了。

/**
 * 应用层实现
 */
@RestController
@RequestMapping(value = "/")
public class UserController {

    @Autowired
    private UserService userService;

    /**
     * 测试配置文件指定
     */
    @RequestMapping(value = "listAllUser")
    public List<User> listAllUser() {
        return userService.listAllUser();
    }

    /**
     * 测试默认数据源
     */
    @DataSource
    @RequestMapping(value = "listAllUserSlave")
    public List<User> listAllUserSlave() {
        return userService.listAllUser();
    }

    /**
     * 测试注解指定数据源
     */
    @DataSource(value = DataSourceType.MASTER)
    @RequestMapping(value = "listAllUserMaster")
    public List<User> listAllUserMaster() {
        return userService.listAllUser();
    }

    /**
     * 测试方法内指定数据源
     */
    @RequestMapping(value = "listAllUserMaster")
    public List<User> listAllUserMaster() {
        DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.SLAVE);
        List<User> userList = userService.listAllUser();
        DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.MASTER);
        return userService.listAllUser();
    }
}

三:关于事务

AbstractRoutingDataSource 只支持单库事务,也就是说切换数据源要在开启事务之前执行。 spring DataSourceTransactionManager进行事务管理,开启事务,会将数据源缓存到DataSourceTransactionObject对象中进行后续的commit rollback等事务操作。

一般Spring管理事务是放在Service业务层操作的,所以更换数据源的操作要放在这个操作之前进行。也就是切换数据源操作放在Controller层。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值