IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 大数据 -> Springboot环境中多个DataSource基于自定义注解进行切换使用过程 -> 正文阅读

[大数据]Springboot环境中多个DataSource基于自定义注解进行切换使用过程

前面配置了mysql数据库的主从复制模式,在数据库上实现了master-slave配置,通过这种方式可以实现一主一从,或者一主多从,从而提升系统的高可用。
这是数据库层面的实现。在数据库实现了主从模式之后,我们需要考率的问题就是,在我们的应用代码中,如何将不同的数据库操作按需要分配到不同的数据库去执行。

1.需要的依赖

    implementation 'org.springframework.boot:spring-boot-starter'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-aop'
    implementation 'mysql:mysql-connector-java:8.0.25'
    implementation 'com.zaxxer:HikariCP:4.0.3'
    

2.yml配置

在application.yml文件中,数据源相关配置如下:

# 自定义的动态数据源配置
custom:
  datasource:
    - key: master
      type: com.zaxxer.hikari.HikariDataSource
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://192.168.161.114:3306/gts?useSSL=false&autoReconnect=true&characterEncoding=UTF-8&serverTimezone=UTC
      username: gts
      password: mysql
      default: true
    - key: slave1
      type: com.zaxxer.hikari.HikariDataSource
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://192.168.161.115:3306/gts?useSSL=false&autoReconnect=true&characterEncoding=UTF-8&serverTimezone=UTC
      username: gts
      password: mysql
    - key: slave2
      type: com.zaxxer.hikari.HikariDataSource
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://192.168.161.114:3306/gts?useSSL=false&autoReconnect=true&characterEncoding=UTF-8&serverTimezone=UTC
      username: gts
      password: mysql 

在custom.datasource,定义了一组数据源配置。master配置到主库,slave1喝slave2分贝表示多组从库。本文用主库来表示从库。

3.动态数据源配置

数据源定义:

package com.dhb.gts.javacourse.week7.dynamic;

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

public class DynamicDataSource extends AbstractRoutingDataSource {

	@Override
	protected Object determineCurrentLookupKey() {
		return DynamicDataSourceContextHolder.getDataSourceType();
	}
}

动态数据源切面配置:

package com.dhb.gts.javacourse.week7.dynamic;

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.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Aspect
@Order(-1)// 保证该AOP在@Transactional之前执行
@Component
@Slf4j
public class DynamicDataSourceAspect {
	@Before("@annotation(ds)")
	public void changeDataSource(JoinPoint point, TargetDataSource ds) throws Throwable {
		String dsId = ds.name();
		if (!DynamicDataSourceContextHolder.containsDataSource(dsId)) {
			log.error("数据源[{}]不存在,使用默认数据源 > {}", ds.name(), point.getSignature());
		}else {
			log.info("Use DataSource : {} > {}", dsId, point.getSignature());
			DynamicDataSourceContextHolder.setDataSourceType(dsId);
		}
	}
	
	@After("@annotation(ds)")
	public void restoreDataSource(JoinPoint point, TargetDataSource ds) {
		log.debug("Revert DataSource : {} > {}", ds.name(), point.getSignature());
		DynamicDataSourceContextHolder.clearDataSourceType();
	}
}

数据源切换处理

package com.dhb.gts.javacourse.week7.dynamic;

import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.List;

@Slf4j
public class DynamicDataSourceContextHolder {

	private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

	public static List<String> dataSourceIds = new ArrayList<>();

	public static void setDataSourceType(String dataSourceType) {
		contextHolder.set(dataSourceType);
		log.info("dataSourceType set is {}",dataSourceType);
	}

	public static String getDataSourceType() {
		return contextHolder.get();
	}

	public static void clearDataSourceType() {
		contextHolder.remove();
	}

	/**
	 * 判断指定DataSrouce当前是否存在
	 */
	public static boolean containsDataSource(String dataSourceId){
		return dataSourceIds.contains(dataSourceId);
	}
}

数据源注册类

package com.dhb.gts.javacourse.week7.dynamic;

import com.google.common.base.Strings;
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.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.boot.context.properties.source.ConfigurationPropertyNameAliases;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
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.List;
import java.util.Map;

@Slf4j
public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware {

	// 如配置文件中未指定数据源类型,使用该默认值
	private static final Object DATASOURCE_TYPE_DEFAULT = "com.zaxxer.hikari.HikariDataSource";
	/**
	 * 数据源参数配置别名
	 */
	private final static ConfigurationPropertyNameAliases aliases = new ConfigurationPropertyNameAliases();

	static {
		//由于部分数据源配置不同,所以在此处添加别名,避免切换数据源出现某些参数无法注入的情况
		aliases.addAliases("url", "jdbc-url");
		aliases.addAliases("username", "user");
	}

	/**
	 * 参数绑定工具
	 */
	private Binder binder;
	/**
	 * 配置上下文(也可以理解为配置文件的获取工具)
	 */
	private Environment env;
	// 默认数据源
	private DataSource defaultDataSource;
	/**
	 * 自定义数据源
	 */
	private Map<String, DataSource> customDataSources = new HashMap<>();

	public DataSource buildDataSource(Map<String, Object> dsMap) {
		try {
			Object type = dsMap.get("type");
			if (type == null) {
				// 默认DataSource
				type = DATASOURCE_TYPE_DEFAULT;
			}
			Class<? extends DataSource> dataSourceType;
			dataSourceType = (Class<? extends DataSource>) Class.forName((String) type);
			String driverClassName = dsMap.get("driver-class-name").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 (Throwable e) {
			log.error("buildDataSource failed!", e);
		}
		return null;
	}

	@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);
		// 创建DynamicDataSource
		DynamicDataSourceContextHolder.dataSourceIds.addAll(customDataSources.keySet());
		//bean定义类
		GenericBeanDefinition define = new GenericBeanDefinition();
		//设置bean的类型,此处DynamicDataSource是继承AbstractRoutingDataSource的实现类
		define.setBeanClass(DynamicDataSource.class);
		//需要注入的参数,类似spring配置文件中的<property/>
		MutablePropertyValues mpv = define.getPropertyValues();
		//添加默认数据源,避免key不存在的情况没有数据源可用
		mpv.add("defaultTargetDataSource", defaultDataSource);
		//添加其他数据源
		mpv.add("targetDataSources", targetDataSources);
		//将该bean注册为datasource,不使用spring-boot自动生成的datasource
		registry.registerBeanDefinition("datasource", define);
		log.info("Dynamic DataSource Registry success !");
	}

	@Override
	public void setEnvironment(Environment environment) {
		this.env = environment;
		//绑定配置器
		binder = Binder.get(env);
		initCustomDataSources();
	}


	private void initCustomDataSources() {
		// 读取配置文件获取更多数据源,也可以通过defaultDataSource读取数据库获取更多数据源
		List<Map> configs = binder.bind("custom.datasource", Bindable.listOf(Map.class)).get();
		String dsPrefix;
		DataSource custom;
		for (Map config : configs) {
			String key = config.get("key").toString();
			if (!Strings.isNullOrEmpty(key)) {
				dsPrefix = key;
			} else {
				dsPrefix = "default";
			}
			custom = buildDataSource(config);
			customDataSources.put(dsPrefix, custom);
			dataBinder(custom, config);
			//如果 default标识为true,则将其设置为defaultDataSource
			if (null != config.get("default") && "true".equals(config.get("default").toString())) {
				defaultDataSource = custom;
			}
		}
		//如果default数据源没有,将master设置为default的数据源。
		if (null == defaultDataSource) {
			defaultDataSource = customDataSources.get("master");
		}
	}

	private void dataBinder(DataSource dataSource, Map properties) {
		ConfigurationPropertySource source = new MapConfigurationPropertySource(properties);
		Binder binderEx = new Binder(source.withAliases(aliases));
		//将参数绑定到对象
		binderEx.bind(ConfigurationPropertyName.EMPTY, Bindable.ofInstance(dataSource));
	}

}

定义一个注解,在使用数据源的时候通过注解进行配置:

package com.dhb.gts.javacourse.week7.dynamic;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

// 自定义一个注解,在方法上使用,用于指定使用哪个数据源
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDataSource {
	String name();
}

4.使用说明

4.1 启动类配置

需要import定义的DynamicDataSourceRegister。
@Import({DynamicDataSourceRegister.class})
另外需要开启切面。
@EnableAspectJAutoProxy(proxyTargetClass=true)

package com.dhb.gts.javacourse.week7;

import com.dhb.gts.javacourse.week7.dynamic.DynamicDataSourceRegister;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.Import;

@SpringBootApplication(scanBasePackages  = {"com.dhb.gts.javacourse.fluent.dao","com.dhb.gts.javacourse.week7"} )
@MapperScan(basePackages = {"com.dhb.gts.javacourse.fluent.mapper"})
@Import({DynamicDataSourceRegister.class})
@EnableAspectJAutoProxy(proxyTargetClass=true)
public class Starter {

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

4.2 Service层注解的使用

现在将自定义的注解,配置到Service层即可使用:

	@Async
	@TargetDataSource(name = "master")
	public ListenableFuture<OrderSummaryEntity> asyncQueryOrderById(int order_id){
		OrderSummaryEntity entity = orderSummaryDao.selectById(order_id);
		return new AsyncResult<>(entity);
	}

	@TargetDataSource(name = "slave1")
	public OrderSummaryEntity queryOrderById(int order_id){
		return orderSummaryDao.selectById(order_id);
	}

通过Controller进行调用:

	@RequestMapping("/asyncQueryByKey")
	public String asyncQueryByKey(String key) {
		Stopwatch stopwatch = Stopwatch.createStarted();
		Integer orde_id = Integer.parseInt(key);
		OrderSummaryEntity entity = null;
		try {
			entity = orderService.asyncQueryOrderById(orde_id).get();
		}catch (Exception e) {
			e.printStackTrace();
		}
		stopwatch.stop();
		log.info("通过key查询,走索引耗时:" + stopwatch);
		return JSON.toJSONString(entity);
	}
	
		@RequestMapping("/queryByKey")
	public String queryByKey(String key) {
		Stopwatch stopwatch = Stopwatch.createStarted();
		Integer orde_id = Integer.parseInt(key);
		OrderSummaryEntity entity = orderService.queryOrderById(orde_id);
		stopwatch.stop();
		log.info("通过key查询,走索引耗时:" + stopwatch);
		return JSON.toJSONString(entity);
	}

asyncQueryByKey使用master请求

queryByKey 通过slave数据源查询

查询日志:

2021-09-18 19:13:03.406  INFO 18368 --- [async-service-1] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2021-09-18 19:13:03.504  INFO 18368 --- [async-service-1] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2021-09-18 19:13:03.560  INFO 18368 --- [async-service-1] c.d.g.j.week7.service.OrderService       : 通过线程池插入完成,共耗时:196.5 ms
2021-09-18 19:13:39.022  INFO 18368 --- [async-service-2] c.d.g.j.w.d.DynamicDataSourceAspect      : Use DataSource : master > ListenableFuture com.dhb.gts.javacourse.week7.service.OrderService.asyncQueryOrderById(int)
2021-09-18 19:13:39.022  INFO 18368 --- [async-service-2] d.g.j.w.d.DynamicDataSourceContextHolder : dataSourceType set is master
2021-09-18 19:13:39.031  INFO 18368 --- [nio-8084-exec-4] c.d.g.j.w.controller.OrderController     : 通过key查询,走索引耗时:14.56 ms
2021-09-18 19:15:17.534  INFO 18368 --- [nio-8084-exec-5] c.d.g.j.w.d.DynamicDataSourceAspect      : Use DataSource : slave1 > OrderSummaryEntity com.dhb.gts.javacourse.week7.service.OrderService.queryOrderById(int)
2021-09-18 19:15:17.535  INFO 18368 --- [nio-8084-exec-5] d.g.j.w.d.DynamicDataSourceContextHolder : dataSourceType set is slave1
2021-09-18 19:15:17.535  INFO 18368 --- [nio-8084-exec-5] com.zaxxer.hikari.HikariDataSource       : HikariPool-2 - Starting...
2021-09-18 19:15:17.553  INFO 18368 --- [nio-8084-exec-5] com.zaxxer.hikari.HikariDataSource       : HikariPool-2 - Start completed.
2021-09-18 19:15:17.556  INFO 18368 --- [nio-8084-exec-5] c.d.g.j.w.controller.OrderController     : 通过key查询,走索引耗时:21.28 ms
  大数据 最新文章
实现Kafka至少消费一次
亚马逊云科技:还在苦于ETL?Zero ETL的时代
初探MapReduce
【SpringBoot框架篇】32.基于注解+redis实现
Elasticsearch:如何减少 Elasticsearch 集
Go redis操作
Redis面试题
专题五 Redis高并发场景
基于GBase8s和Calcite的多数据源查询
Redis——底层数据结构原理
上一篇文章      下一篇文章      查看所有文章
加:2021-09-19 08:02:49  更:2021-09-19 08:05:19 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 20:10:49-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码