文档章节

Spring Boot 2 AOP方式实现多数据源切换

325G
 325G
发布于 06/20 09:52
字数 1328
阅读 68
收藏 5

支持的数据源类型为druid和spring boot 2的默认数据源hikariCP ,如果需要支持其他类型数据源可自行添加。

实现思路为通过解析数据源配置文件创建数据源,然后使用Spring提供的AbstractRoutingDataSource类设置路由数据源,最后通过AOP方式实现数据源的切换。

1. 解析数据源配置文件

    创建配置文件解析类,我使用的数据源配置文件格式为Yaml所以创建了默认的解析类为读取Yaml配置文件

public interface DataSourceConfigurationResolver {

	static final String MASTER_MARK = "MASTER";

	Map<String, Object> getDataSourceProperty() throws DataSourceConfigException;
}
public abstract class AbstractFileDataSourceConfigurationResolver implements DataSourceConfigurationResolver{
	
	protected static final String FILE_PERFIX = "datasource-";
	protected static final String FILE_MASTER_MARK = "master";
	protected static final String FILE_SLAVE_MARK = "slave";
	
	@Override
	public Map<String, Object> getDataSourceProperty() throws DataSourceConfigException{
		
		Map<String, Object> dataSourceMap = new HashMap<String, Object>();
		dataSourceMap.put(MASTER_MARK, obatainMasterDataSourceProperty());
		dataSourceMap.putAll(obatainSlaveDataSourceProperty());
		
		return dataSourceMap;
		
	}

	protected abstract Object obatainMasterDataSourceProperty() throws DataSourceConfigException;
	protected abstract Map<String, Object> obatainSlaveDataSourceProperty() throws DataSourceConfigException;
	
}
public abstract class AbstractYamlFileDataSourceConfigurationResolver extends AbstractFileDataSourceConfigurationResolver{

	public static final String FILE_SUFFIX = ".yml";
	public static final String DEFAULT_FILE_PATH = "/";
	
	@Override
	protected Object obatainMasterDataSourceProperty() throws DataSourceConfigException {
		
		File file = new File(getMasterDataSourceFilePath());
		return resolve(file);
	} 

	@Override
	protected Map<String, Object> obatainSlaveDataSourceProperty() throws DataSourceConfigException {
		Map<String, Object> dataSourcePropertyMap = new HashMap<String, Object>();
		File dir = new File(getDataSourceFileDir());
		File[] files = dir.listFiles();
		for(File file : files) {
			if(!file.isDirectory() && validYamlDataSourceFile(file.getName())) {
				dataSourcePropertyMap.put(getSlaveName(file.getName()), resolve(file));
			}
		}
		return dataSourcePropertyMap;
	}
	
	protected abstract Object resolve(File file) throws DataSourceConfigException;
	
	private String getMasterDataSourceFilePath() {
		return getDataSourceFileDir() + File.separator + FILE_PERFIX + FILE_MASTER_MARK + FILE_SUFFIX;
	}
	
	private String getDataSourceFileDir() {
		return this.getClass().getClassLoader().getResource("").getPath();
	}
	
	private boolean validYamlDataSourceFile(String fileName) {
		return fileName.startsWith(FILE_PERFIX + FILE_SLAVE_MARK) && fileName.endsWith(FILE_SUFFIX);
	}
	
	private String getSlaveName(String fileName) {
		return fileName.substring(FILE_PERFIX.length(), fileName.indexOf(FILE_SUFFIX));
	}
}
@Component
public class DefaultDataSourceConfigurationResolver extends AbstractYamlFileDataSourceConfigurationResolver{

	private Yaml yaml = new Yaml();
	
	@Override
	protected Properties resolve(File file) throws DataSourceConfigException {
		Map<?, ?> dataSourceConfigurationMap = null;
		Properties result = new Properties();
		 try {
			 Map<?, ?> configurationMap = (Map<?, ?>) yaml.load(new FileInputStream(file));
			 if(!configurationMap.containsKey("datasource"))
				 throw new DataSourceConfigException("解析数据源配置文件失败,未发现key[ datasource ],file[ " + file.getName() + " ]"); 
			 
			 dataSourceConfigurationMap = (Map<?, ?>) configurationMap.get("datasource");
		} catch (FileNotFoundException e) {
			throw new DataSourceConfigException(e);
		}
		dataSourceConfigurationMap.forEach((k, v) ->{
			 result.put(k, v);
		});
		return result;
	}

}
public class DataSourceConfigException extends Exception{

	private static final long serialVersionUID = 6924248500337718207L;
	
	public DataSourceConfigException(String msg, Throwable e) {
		super(msg, e);
	}
	
	public DataSourceConfigException(Throwable e) {
		super(e);
	}
	
	public DataSourceConfigException(String msg) {
		super(msg);
	}

}

    配置文件默认存储路径为src/main/resources目录以datasource-开头 .yaml结尾,文件名后段标识主数据源标识和从数据源标识,如:datasource-master.yml、datasource-slave-1.yml、datasource-slave-2.yml。

    以下是 Durid v1.1.10数据源配置项示例

# 数据源配置 Durid v1.1.10
datasource:
  driverClassName: com.mysql.jdbc.Driver
  # 数据库链接配置
  url: jdbc:mysql://localhost:3306/sc?characterEncoding=utf8
  username: root
  password: root
  # 初始化大小,最小,最大
  initialSize: '5'
  minIdle: '5'
  maxctive: '50'
  # 获取连接等待超时的时间
  maxait: '60000'
  # 连接在池中最小生存的时间
  minEvictableIdleTimeMillis: '300000'
  validationQuery: SELECT 1 FROM DUA
  # 申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效.建议配置为true,不影响性能,并且保证安全性
  testWhileIdle: 'true'
  # 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
  testOnBorrow: 'false'
  # 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
  testOnReturn: 'false'
  # 打开PSCache,并且指定每个连接上PSCache的大小。PSCache对支持游标的数据库性能提升巨大,mysql下建议关闭
  poolPreparedStatements: 'false'
  # 通过别名的方式配置扩展插件,常用的插件有:监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall
  filters: stat,wall,slf4j

2. 设置数据源

    数据源设置使用了Spring提供AbstractRoutingDataSource类来实现,该类是一个数据源路由类使用Map持有多个数据源在获取Connection时可以根据自定义策略选择数据源,由于AbstractRoutingDataSource是抽象类我们需要自定义类继承该类,并重写determineCurrentLookupKey方法制定数据源key的获取方式。

public class DynamicDataSource extends AbstractRoutingDataSource{

	@Override
	protected Object determineCurrentLookupKey() {
		return DataSourceContextHolder.getDataSource();
	}

}

    DataSourceContextHolder类中持有 slaveDataSources,从数据源选择使用了简单的随机选择方式

public class DataSourceContextHolder {

	private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
	private static volatile List<String> slaveDataSources = new ArrayList<>();
	private static Random random = new Random();
	
	public static void setDataSource(String type) {
		contextHolder.set(type);
	}

	public static String getDataSource() {
		return contextHolder.get();
	}
	
	public static void removeDataSource() {
		contextHolder.remove();
	}
	
	public static void setSlaveDataSource(String dataSourceName) {
		slaveDataSources.add(dataSourceName);
	}
	public static String getSlaveDataSource() {
		return slaveDataSources.get(random.nextInt(slaveDataSources.size()));
	}
	
}

    定义Conf类添加数据源类型选择和多数据源开关

@Configuration
@PropertySource("classpath:conf.yml")
public class Conf {
	
	@Value("${use-multi-datasource}")
	public Boolean useMultiDatasource;
	@Value("${datasource-type}")
	public String datasourceType;
}
# 数据源类型 DRUID HIKARICP
datasource-type: DRUID
# 是否开启多数据源
use-multi-datasource: false

    声明数据源Bean,注意@Primary标签使当前Bean成为主Bean

    @Autowired
	private DataSourceConfigurationResolver dataSourceConfigurationResolver;
	
	@Autowired
	private Conf conf;

    @Bean
	@Primary
	public DataSource dataSource() throws Exception {
		Map<String, Object> propMap = dataSourceConfigurationResolver.getDataSourceProperty();
		Map<Object, Object> targetDataSources = new HashMap<>();
		DataSource defalultDataSource = DataSourceFactory.getDataSource((Properties) propMap.get(DataSourceConfigurationResolver.MASTER_MARK), conf.datasourceType);
		if(!conf.useMultiDatasource) {
			return defalultDataSource;
		}
		for(Entry<String, Object> propEntry : propMap.entrySet()) {
			String databaseType = propEntry.getKey();
			if(!DataSourceConfigurationResolver.MASTER_MARK.equals(databaseType))
				DataSourceContextHolder.setSlaveDataSource(databaseType);
			
			targetDataSources.put(databaseType, DataSourceFactory.getDataSource((Properties) propEntry.getValue(), conf.datasourceType));
		}
		
		DynamicDataSource dataSource = new DynamicDataSource();
		dataSource.setTargetDataSources(targetDataSources);
		dataSource.setDefaultTargetDataSource(defalultDataSource);

		return dataSource;
	}
public class DataSourceFactory {

	public static DataSource getDataSource(Properties properties, String type) throws Exception {
		
		if("DRUID".equals(type)) {
			return DruidDataSourceFactory.createDataSource(properties);
		}else if("HIKARICP".equals(type)) {
			return new HikariDataSource(new HikariConfig(properties));
		}
		
		return DruidDataSourceFactory.createDataSource(properties); 
		
	}
}

3. 设置数据源切面

    数据源切面设置在服务层可以通过方法名判断需要切换为哪个数据源

@Aspect
@Component
@Order(1)
public class DataSourceAspect {

	@Autowired
	private Conf conf;
	
    @Pointcut("execution(* priv.jieying.service.impl.*.*(..))")
    public void declareJointPointExpression() {
    }

    @Before("declareJointPointExpression()")
    public void setDataSourceKey(JoinPoint point) { 
    	
    	if(conf.useMultiDatasource) {
    		if(StringUtils.startsWithAny(point.getSignature().getName(), "insert", "save", "update", "modify", "delete", "remove")) {
        		DataSourceContextHolder.setDataSource(DataSourceConfigurationResolver.MASTER_MARK);
        	}else {
        		DataSourceContextHolder.setDataSource(DataSourceContextHolder.getSlaveDataSource());
        	}
    	} 
    }

}

以上示例代码已上传到码云 https://gitee.com/sc325/spring-boot-multi-datasource

© 著作权归作者所有

325G

325G

粉丝 3
博文 8
码字总数 4562
作品 0
朝阳
程序员
私信 提问
加载中

评论(1)

不愿透露姓名的Mr成
不愿透露姓名的Mr成
结合事务试试
JAVA中使用代码创建多数据源,并实现动态切换(一)

2017-06-06 11:31:57补充:近日,在本文的基础之上,扩展了下,使用atomikos来管理事务,保证多数据源操作时,事务一致性。(https://my.oschina.net/simpleton/blog/916108) 另外,感谢朋友...

十月阳光
2017/03/27
0
0
苞米豆-多数据源快速启动器 V1.4.0 发布

https://gitee.com/baomidou/dynamic-datasource-spring-boot-starter 欢迎关注项目,提出您的宝贵建议。 v1.4.0 升级日志 支持了在类上注解,方法上同时有注解方法优先。 支持的遇到事物强制...

小锅盖
2018/07/17
686
4
Spring Boot MyBatis 动态数据源切换、多数据源,读写分离

项目地址 https://github.com/helloworlde/SpringBoot-DynamicDataSource 本项目使用 Spring Boot 和 MyBatis 实现多数据源,动态数据源的切换;有多种不同的实现方式,在学习的过程中发现没...

呜呜呜啦啦啦
2017/12/21
0
0
Spring boot中mongodb的使用

mongodb的增删改查 Spring Boot对各种流行的数据源都进行了封装,当然也包括了mongodb,下面给大家介绍如何在spring boot中使用mongodb: 1、pom包配置 pom包里面添加spring-boot-starter-dat...

glen_xu
2018/06/11
0
0
Spring进阶(六):can not find proxy: set exposeproxy property on advised to make it available

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/sunhuaqiang1/article/details/82594886 Spring进阶(六):can not find proxy: set exposeproxy property ...

孙华强
2018/09/10
0
0

没有更多内容

加载失败,请刷新页面

加载更多

日志相关---日志配置和过滤器

一、log4j日志简介 1.1、 Loggers 级别和介绍 Loggers组件在此系统中被分为八个级别:ALL、TRANCE、DEBUG、INFO、WARN、ERROR和FATAL、OFF。这八个级别是有顺序的, ##off表示关闭ALL < T...

spinachgit
刚刚
0
0
六个面试题层层剖析——LongAddr原子类

并发编程面试题 (1)LongAddr的结构是怎样的? (2)当前线程应该访问Cell数组里面的哪一个Cell元素? (3)如何初始化Cell数组? (4)Cell数组如何扩容? (5)线程访问分配的Cell元素有冲...

须臾之余
2分钟前
0
0
MySQL-入门(二)

本部分主要是MySQL的常用函数和高级用法。 一、MySQL排序 排序关键字:order by 排序字段。后面写上要排序字段,排序字段可以有多个,多个采用逗号间隔,order by默认采用升序(asc)排序,可...

潜行-L
12分钟前
0
0
BAM转VCF的方法对比

1 使用GATK HaplotypeCaller #java -jar gatk.jar HaplotypeCaller --native-pair-hmm-threads 4 -R xx.fa -I xx.bam -O xx.vcf --native-pair-hmm-threads用来设置多线程,默认为4线程 2 sa......

悲催的古灵武士
21分钟前
0
0
软件架构设计原则之“KISS”的总结使用

今天聊一聊软件架构设计中的 KISS 原则。 对! 就是亲嘴的那个 “KISS”! 一定要多练习。 ... ... ... ... 作为一个程序员我是推荐理解为“亲嘴”的,可以很好的解决单身问题,但作为一个架...

Owen_Jia
23分钟前
0
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部