springboot多数据源
2021/4/22 18:56:40
本文主要是介绍springboot多数据源,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
自定义多数据源
DynamicDataSourceRegister(将数据源注入到ioc容器中)
public class DynamicDataSourceRegister implements EnvironmentAware , ImportBeanDefinitionRegistrar { private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceRegister.class); /** * 配置上下文(也可以理解为配置文件的获取工具) */ private Environment evn; /** * 别名 */ private final static ConfigurationPropertyNameAliases aliases = new ConfigurationPropertyNameAliases(); /** * 由于部分数据源配置不同,所以在此处添加别名,避免切换数据源出现某些参数无法注入的情况 */ static { aliases.addAliases("url", new String[]{"jdbc-url"}); aliases.addAliases("username", new String[]{"user"}); } /** * 存储我们注册的数据源 */ private Map<String, DataSource> customDataSources = new HashMap<String, DataSource>(); /** * 参数绑定工具 springboot2.0新推出 */ private Binder binder; /** * ImportBeanDefinitionRegistrar接口的实现方法,通过该方法可以按照自己的方式注册bean * * @param annotationMetadata * @param beanDefinitionRegistry */ @Override public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) { // 获取所有数据源配置 Map config, defauleDataSourceProperties; defauleDataSourceProperties = binder.bind("spring.datasource.master", Map.class).get(); // 获取数据源类型 String typeStr = evn.getProperty("spring.datasource.master.type"); // 获取数据源类型 Class<? extends DataSource> clazz = getDataSourceType(typeStr); // 绑定默认数据源参数 也就是主数据源 DataSource consumerDatasource, defaultDatasource = bind(clazz, defauleDataSourceProperties); DynamicDataSourceContextHolder.dataSourceIds.add("master"); logger.info("注册默认数据源成功"); // 获取其他数据源配置 List<Map> configs = binder.bind("spring.datasource.cluster", Bindable.listOf(Map.class)).get(); // 遍历从数据源 for (int i = 0; i < configs.size(); i++) { config = configs.get(i); clazz = getDataSourceType((String) config.get("type")); defauleDataSourceProperties = config; // 绑定参数 consumerDatasource = bind(clazz, defauleDataSourceProperties); // 获取数据源的key,以便通过该key可以定位到数据源 String key = config.get("key").toString(); customDataSources.put(key, consumerDatasource); // 数据源上下文,用于管理数据源与记录已经注册的数据源key DynamicDataSourceContextHolder.dataSourceIds.add(key); logger.info("注册数据源{}成功", key); } // bean定义类 GenericBeanDefinition define = new GenericBeanDefinition(); // 设置bean的类型,此处DynamicRoutingDataSource是继承AbstractRoutingDataSource的实现类 define.setBeanClass(DynamicRoutingDataSource.class); // 需要注入的参数 MutablePropertyValues mpv = define.getPropertyValues(); // 添加默认数据源,避免key不存在的情况没有数据源可用 mpv.add("defaultTargetDataSource", defaultDatasource); // 添加其他数据源 mpv.add("targetDataSources", customDataSources); // 将该bean注册为datasource,不使用springboot自动生成的datasource beanDefinitionRegistry.registerBeanDefinition("datasource", define); logger.info("注册数据源成功,一共注册{}个数据源", customDataSources.keySet().size() + 1); } /** * 通过字符串获取数据源class对象 * * @param typeStr * @return */ private Class<? extends DataSource> getDataSourceType(String typeStr) { Class<? extends DataSource> type; try { if (StringUtils.hasLength(typeStr)) { // 字符串不为空则通过反射获取class对象 type = (Class<? extends DataSource>) Class.forName(typeStr); } else { // 默认为hikariCP数据源,与springboot默认数据源保持一致 type = HikariDataSource.class; } return type; } catch (Exception e) { throw new IllegalArgumentException("can not resolve class with type: " + typeStr); //无法通过反射获取class对象的情况则抛出异常,该情况一般是写错了,所以此次抛出一个runtimeexception } } /** * 绑定参数,以下三个方法都是参考DataSourceBuilder的bind方法实现的,目的是尽量保证我们自己添加的数据源构造过程与springboot保持一致 * * @param result * @param properties */ private void bind(DataSource result, Map properties) { ConfigurationPropertySource source = new MapConfigurationPropertySource(properties); Binder binder = new Binder(new ConfigurationPropertySource[]{source.withAliases(aliases)}); // 将参数绑定到对象 binder.bind(ConfigurationPropertyName.EMPTY, Bindable.ofInstance(result)); } private <T extends DataSource> T bind(Class<T> clazz, Map properties) { ConfigurationPropertySource source = new MapConfigurationPropertySource(properties); Binder binder = new Binder(new ConfigurationPropertySource[]{source.withAliases(aliases)}); // 通过类型绑定参数并获得实例对象 return binder.bind(ConfigurationPropertyName.EMPTY, Bindable.of(clazz)).get(); } /** * @param clazz * @param sourcePath 参数路径,对应配置文件中的值,如: spring.datasource * @param <T> * @return */ private <T extends DataSource> T bind(Class<T> clazz, String sourcePath) { Map properties = binder.bind(sourcePath, Map.class).get(); return bind(clazz, properties); } /** * EnvironmentAware接口的实现方法,通过aware的方式注入,此处是environment对象 * * @param environment */ @Override public void setEnvironment(Environment environment) { logger.info("开始注册数据源"); this.evn = environment; // 绑定配置器 binder = Binder.get(evn); }
DynamicDataSourceContextHolder(调用该方法来保存数据源到线程中)
public class DynamicDataSourceContextHolder { private static Logger logger = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class); /** * 存储已经注册的数据源的key */ public static List<String> dataSourceIds = new ArrayList<>(); /** * 线程级别的私有变量 */ private static final ThreadLocal<String> HOLDER = new ThreadLocal<>(); public static String getDataSourceRouterKey () { return HOLDER.get(); } public static void setDataSourceRouterKey (String dataSourceRouterKey) { logger.info("切换至{}数据源", dataSourceRouterKey); HOLDER.set(dataSourceRouterKey); } /** * 设置数据源之前一定要先移除 */ public static void removeDataSourceRouterKey () { HOLDER.remove(); } /** * 判断指定DataSrouce当前是否存在 * * @param dataSourceId * @return */ public static boolean containsDataSource(String dataSourceId){ return dataSourceIds.contains(dataSourceId); }
DynamicRoutingDataSource(切换数据源的核心类)
public class DynamicRoutingDataSource extends AbstractRoutingDataSource { private static Logger logger = LoggerFactory.getLogger(DynamicRoutingDataSource.class); @Override protected Object determineCurrentLookupKey() { String dataSourceName = DynamicDataSourceContextHolder.getDataSourceRouterKey(); logger.info("当前数据源是:{}", dataSourceName); return DynamicDataSourceContextHolder.getDataSourceRouterKey(); } }
DynamicDataSourceAspect(切面声明在加有自定义注解的方法上将该切面注入)
@Aspect @Component @EnableAspectJAutoProxy public class DynamicDataSourceAspect { private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class); /** * aop前置通知,在标有注解@DataSource方法执行之前调用此方法 * 此方法作用是切换成注解中指定数据源,如果不指定,使用默认数据源master * @param point 连接点(业务方法),该对象是aop自动注入进来的 * @param ds 自定义的注解类型 * @throws Throwable */ @Before("@annotation(ds)") public void changeDataSource(JoinPoint point, DataSource ds) throws Throwable { //获取注解的值,默认是master String dsId = ds.value(); if (DynamicDataSourceContextHolder.dataSourceIds.contains(dsId)) { logger.debug("Use DataSource :{} >", dsId, point.getSignature()); DynamicDataSourceContextHolder.setDataSourceRouterKey(dsId); } else { logger.info("数据源[{}]不存在,使用默认数据源 >{}", dsId, point.getSignature()); DynamicDataSourceContextHolder.setDataSourceRouterKey(dsId); } } /** * aop后置通知,在标有注解@DataSource方法执行之后调用此方法 * 此方法作用重置数据源 * @param point * @param ds */ @After("@annotation(ds)") public void restoreDataSource(JoinPoint point, DataSource ds) { logger.debug("Revert DataSource : " + ds.value() + " > " + point.getSignature()); DynamicDataSourceContextHolder.removeDataSourceRouterKey(); }
自定义注解
/** * 切换数据注解 可以用于类或者方法级别 方法级别优先级 > 类级别 */ @Target({ElementType.METHOD, ElementType.TYPE, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface DataSource { String value() default "master"; //该值即key值 }
mybatis-plus集成多数据源
引入dynamic-datasource-spring-boot-starter。
<dependency> <groupId>com.baomidou</groupId> <artifactId>dynamic-datasource-spring-boot-starter</artifactId> <version>3.3.2</version> </dependency>
配置数据源
spring: datasource: dynamic: primary: master #设置默认的数据源或者数据源组,默认值即为master strict: false #设置严格模式,默认false不启动. 启动后在未匹配到指定数据源时候会抛出异常,不启动则使用默认数据源. datasource: master: url: jdbc:mysql://localhost:3306/master?useUnicode=true&characterEncoding=utf-8 username: root password: root driver-class-name: com.mysql.jdbc.Driver # 3.2.0开始支持SPI可省略此配置 slave_1: url: jdbc:mysql://localhost:3306/slave1?useUnicode=true&characterEncoding=utf-8 username: root password: root driver-class-name: com.mysql.jdbc.Driver slave_2: url: jdbc:mysql://localhost:3306/slave2?useUnicode=true&characterEncoding=utf-8 # 内置加密,使用请查看详细文档 username: root password: root driver-class-name: com.mysql.jdbc.Driver schema: db/schema.sql # 配置则生效,自动初始化表结构 data: db/data.sql # 配置则生效,自动初始化数据 continue-on-error: true # 默认true,初始化失败是否继续 separator: ";" # sql默认分号分隔符
使用 @DS 切换数据源。
@DS("slave") public User queryById(Integer id){ return userService.getById(id); }
没有@DS 默认数据源 @DS("dsName") dsName可以为组名也可以为具体某个库的名称 如果只写组名,会跟根据算法轮流执行
这篇关于springboot多数据源的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-26Mybatis官方生成器资料详解与应用教程
- 2024-11-26Mybatis一级缓存资料详解与实战教程
- 2024-11-26Mybatis一级缓存资料详解:新手快速入门
- 2024-11-26SpringBoot3+JDK17搭建后端资料详尽教程
- 2024-11-26Springboot单体架构搭建资料:新手入门教程
- 2024-11-26Springboot单体架构搭建资料详解与实战教程
- 2024-11-26Springboot框架资料:新手入门教程
- 2024-11-26Springboot企业级开发资料入门教程
- 2024-11-26SpringBoot企业级开发资料详解与实战教程
- 2024-11-26Springboot微服务资料:新手入门全攻略