springboot自动切换数据源,springboot2.0实现多数据源_1

  springboot自动切换数据源,springboot2.0实现多数据源

  00-1010序工程结构编码实现yml文件主数据源MainDatasourceProperties其他数据源DynamicDatasourceProperties数据源配置类DatasourceConfigurationDatasourceChooseDataSourceContext数据源注释DatasourceScope数据源节DynamicDatasourceAspect方面使用

  00-1010在软件开发的过程中,刚开始的时候,因为无法预估系统后期的访问量和并发量,所以一开始就采用单一架构。后期如果网站流量变大,并发变大,那么我们可能会把架构扩展到微服务架构,每个微服务对应一个数据库。不过这种成本有点大,可能是有些模块用的人多,有些模块用的人不多。如果所有服务都拆分,那就没必要了。如果有些模块被更多的人使用,那么我们可以使用读写分离来减轻压力。这样可以在一定程度上提升系统的用户体验。但是,这只是基于数据库I/O的解决方案。如果系统压力很大,那么必须进行负载平衡。今天,我们将讨论如何实现数据库的读写分离。如果要在代码层面分离数据库的读写,那么核心就是数据源的切换。本文基于AOP实现了数据源的切换。

  

目录
com牛排事务TransactionDemoApplication.java数据源DatasourceChooser.javaDatasourceConfiguration.javaDatasourceContext.javaDatasourceScope.javaDynamicDatasourceAspect.java属性DynamicDatasourceProperties.javaMainDat asourceProperties.javaexecuteplaceorderexecute . Javarestplaceorderapi . Java如果没有指定特定的数据源,那么使用默认的数据源。我们基于声明的方式切换数据源,只需要在特定的接口上添加注释就可以实现数据源的切换。

  

前言

  00-1010主数据源由spring直接配置,其他数据源由用户自定义。这里用了一个map结构来方便解析,在yml文件中可以添加多个数据源,代码逻辑层面没有任何改变。

  spring : data source : type : com . Alibaba . druid . pool . druid data source druid : username : root password : 123456 URL : JDBC : MySQL ://127 . 0 . 0 . 1:3306/db?use unicode=true character encoding=UTF-8 usessl=false server time zone=UTC driver-class-name : com . MySQL . CJ . JDBC . driver dynamic : data source : { slav e 1: { username : root ,password: 123456 ,URL : URL : JDBC 3: MySQL 3360//127 .use unicode=true character te

  rEncoding=UTF-8&useSSL=false&serverTimezone=UTC, driver-class-name: com.mysql.cj.jdbc.Driver }, slave2: { username: root, password: 123456, url: url: jdbc:mysql://127.0.0.1:3306/db?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=UTC, driver-class-name: com.mysql.cj.jdbc.Driver } }

  

主数据源MainDatasourceProperties

对于主数据源,我们单独拿出来放在一个类里面,不过其实也可以放到dynamic里面,只是需要做一定的处理,我们就简单的放几个连接属性。

  

/** * @author 刘牌 * @date 2022/3/220:14 */@Component@ConfigurationProperties(prefix = "spring.datasource.druid")@Datapublic class MainDatasourceProperties { private String username; private String password; private String url; private String driverClassName;}

  

其他数据源DynamicDatasourceProperties

其他数据源使用一个Map来接受yml文件中的数据源配置

  

/** * @author 刘牌 * @date 2022/3/213:47 */@Component@ConfigurationProperties(prefix = "dynamic")@Datapublic class DynamicDatasourceProperties { private Map<String,Map<String,String>> datasource;}

  

数据源配置类DatasourceConfiguration

配置类中主要对DataSource进行配置,主数据源我们按照正常的bean来定义连接属性,而其他数据数据源则使用反射的方式来进行连接属性的配置,因为主数据源一般是不会变动的,但是其他数据源可能会发生变动,可能会添加,这时候如果通过硬编码取配置,那么每增加一个数据源,就需要增加一个配置,显然不太行,所以就使用反射来进行赋值。

  

/** * @author 刘牌 * @date 2022/3/111:34 */@Configuration@AllArgsConstructorpublic class DatasourceConfiguration { final MainDatasourceProperties mainDatasourceProperties; final DynamicDatasourceProperties dynamicDatasourceProperties; @Bean public DataSource datasource(){ Map<Object,Object> datasourceMap = new HashMap<>(); DatasourceChooser datasourceChooser = new DatasourceChooser(); /** * main database */ DruidDataSource mainDataSource = new DruidDataSource(); mainDataSource.setUsername(mainDatasourceProperties.getUsername()); mainDataSource.setPassword(mainDatasourceProperties.getPassword()); mainDataSource.setUrl(mainDatasourceProperties.getUrl()); mainDataSource.setDriverClassName(mainDatasourceProperties.getDriverClassName()); datasourceMap.put("main",mainDataSource); /** * other database */ Map<String, Map<String, String>> sourceMap = dynamicDatasourceProperties.getDatasource(); sourceMap.forEach((datasourceName,datasourceMaps) -> { DruidDataSource dataSource = new DruidDataSource(); datasourceMaps.forEach((K,V) -> { String setField = "set" + K.substring(0, 1).toUpperCase() + K.substring(1); //转换yml文件中带有-符号的属性 String[] strings = setField.split(""); StringBuilder newStr = new StringBuilder(); for (int i = 0; i < strings.length; i++) { if (strings[i].equals("-")) strings[i + 1] = strings[i + 1].toUpperCase(); if (!strings[i].equals("-")) newStr.append(strings[i]); } try { DruidDataSource.class.getMethod(newStr.toString(),String.class).invoke(dataSource,V); } catch (Exception e) { e.printStackTrace(); } }); datasourceMap.put(datasourceName,dataSource); }); //设置目标数据源 datasourceChooser.setTargetDataSources(datasourceMap); //设置默认数据源 datasourceChooser.setDefaultTargetDataSource(mainDataSource); return datasourceChooser; }}
上面使用数据源配置类中使用反射对其他数据源进行连接属性的设置,然后设置目标数据源和默认数据源,里面有一个DatasourceChooser

  

  

DatasourceChooser

DatasourceChooser继承自AbstractRoutingDataSourceAbstractRoutingDataSource可以实现数据源的切换,它里面的determineCurrentLookupKey()方法需要我们返回一个数据源的名称,它会自动给我们匹配上数据源。

  

/** * @author 刘牌 * @date 2022/3/112:21 */public class DatasourceChooser extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DatasourceContext.getDatasource(); }}
如下是AbstractRoutingDataSource的部分源码,我们可以看出数据源是一个Map结构,可以通过数据源名称查找到对应的数据源。

  

package org.springframework.jdbc.datasource.lookup;public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean { @Nullable private Map<Object, DataSource> resolvedDataSources; public AbstractRoutingDataSource() { } protected DataSource determineTargetDataSource() { Object lookupKey = this.determineCurrentLookupKey(); DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey); } @Nullable protected abstract Object determineCurrentLookupKey();}

  

DatasourceContext

DatasourceContext内部是一个ThreadLocal,主要是用来存储每一个线程的数据源名称和获取数据源名称,而数据源的名称我们用过AOP切面来获取。

  

/** * @author 刘牌 * @date 2022/3/112:22 */public class DatasourceContext { private static final ThreadLocal<String> threadLocal = new ThreadLocal<>(); public static void setDatasource(String key){ threadLocal.set(key); } public static String getDatasource(){ return threadLocal.get(); }}

  

数据源注解DatasourceScope

DatasourceScope标准在方法上面,通过scope来指定数据源,不指定默认为主数据源main

  

/** * @author 刘牌 * @date 2022/3/111:22 */@Target({ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface DatasourceScope { String scope() default "main";}

  

数据源切面DynamicDatasourceAspect

我们在访问每一个带有DatasourceScope注解的方法时,都会经过数据源切面DynamicDatasourceAspect,获取到注解上面的scope的值后,通过DatasourceContext设置数据源名称,便可实现对数据源的切换。

  

/** * @author 刘牌 * @date 2022/3/111:34 */@Aspect@Componentpublic class DynamicDatasourceAspect { @Pointcut("@annotation(dataSourceScope)") public void dynamicPointcut(DatasourceScope dataSourceScope){} @Around(value = "dynamicPointcut(dataSourceScope)", argNames = "joinPoint,dataSourceScope") public Object dynamicAround(ProceedingJoinPoint joinPoint , DatasourceScope dataSourceScope) throws Throwable { String scope = dataSourceScope.scope(); DatasourceContext.setDatasource(scope); return joinPoint.proceed(); }}

  

使用

只需要在具体的方法上面标注数据源注解@DatasourceScope,并指定scope的值,便可实现切换,如果不指定,那么就使用主数据源。

  

/** * @author 刘牌 * @date 2022/3/19:49 */@Service@AllArgsConstructorpublic class OrderService { private JdbcTemplate jdbcTemplate; @DatasourceScope(scope = "slave1") public R saveOrder(Integer userId , Integer commodityId){ String sql = "INSERT INTO `order`(user_id,commodity_id) VALUES("+userId+","+commodityId+")"; jdbcTemplate.execute(sql); return R.builder().code(200).msg("save order success").build(); }}
到此这篇关于SpringBoot实现多数据源的切换实践的文章就介绍到这了,更多相关SpringBoot多数据源切换内容请搜索盛行IT以前的文章或继续浏览下面的相关文章希望大家以后多多支持盛行IT!

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: