• SpringBoot多数据源


    使用场景

    在实际开发中,可能遇到多数据源的场景。

    1. 业务复杂(数据量大)
      数据分布在不同的数据库中,对业务数据进行垂直拆分。
      可以拆分为微服务架构,依赖的业务可以通过远程调用的方式来是实现,那么这种方式是不存在多数据源的情况。但是对于性能要求不高,以实现功能为目的,则可以使用多数据源。
      在这里插入图片描述
    2. 读写分离
      为了解决数据库读性能瓶颈(读比写的性能要高,但是写锁会影响阻塞,从而影响读性能)
      主从架构:一台主数据库对外提供增删改业务,其他从数据库进行读操作。
      在这里插入图片描述
    3. 这里还有读取上传的业务。

    自定义多数据源实现类

    在这里插入图片描述
    在这里插入图片描述

    关键类解析

    DynamicDataSource 需要编写这个类并继承 AbstractRoutingDataSource

    /**
     * 继承抽象路由数据源类AbstractRoutingDataSource
     */
    public class DynamicDataSource extends AbstractRoutingDataSource {
        /**
         * 每次查询数据库都会执行该方法
         * @return
         */
        @Override
        protected Object determineCurrentLookupKey() {
        	// 返回AbstractRoutingDataSource类targetDataSources集合的key。
            return null;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    AbstractRoutingDataSource解析

    public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
        @Nullable
        private Map<Object, Object> targetDataSources;
        @Nullable
        private Object defaultTargetDataSource;
        private boolean lenientFallback = true;
        private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
        @Nullable
        private Map<Object, DataSource> resolvedDataSources;
        @Nullable
        private DataSource resolvedDefaultDataSource;
        // 这个类中有四个需要传入的非空属性。其中我们只需要传入上面两个即可,下面的两个会通过调用默认的afterPropertiesSet()方法来赋值。
    
        public void afterPropertiesSet() {
            if (this.targetDataSources == null) {
                throw new IllegalArgumentException("Property 'targetDataSources' is required");
            } else {
                this.resolvedDataSources = new HashMap(this.targetDataSources.size());
                this.targetDataSources.forEach((key, value) -> {
                    Object lookupKey = this.resolveSpecifiedLookupKey(key);
                    DataSource dataSource = this.resolveSpecifiedDataSource(value);
                    this.resolvedDataSources.put(lookupKey, dataSource);
                });
                if (this.defaultTargetDataSource != null) {
                    this.resolvedDefaultDataSource = this.resolveSpecifiedDataSource(this.defaultTargetDataSource);
                }
    
            }
    
    	// 这里省略
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    示例:

    1. 创建SpringBoot程序,并编写配置文件。配置好两个数据源。这里我的叫db1,db2
    server:
        port: 8888
    spring:
        application:
            name: MultiDatasource
        datasource:
            driver-class-name: com.mysql.cj.jdbc.Driver
            name: defaultDataSource
            url: jdbc:mysql://localhost:3306/blue?serverTimezone=UTC
            username: 'root'
            password: '123456'
            type: com.zaxxer.hikari.HikariDataSource
            db1:
                url: jdbc:mysql://localhost:3306/blue?serverTimezone=UTC
                username: 'root'
                password: '123456'
            db2:
                url: jdbc:mysql://localhost:3306/blue?serverTimezone=UTC
                username: 'root'
                password: '123456'
    
    mybatis:
        mapper-locations: classpath:mappers/*xml
        type-aliases-package: com.hx.multiDB.entity
        configuration.map-underscore-to-camel-case: true
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    1. 新建数据源配置类 DataSourceConfig.java(包含了数据源配置)
    package com.hx.multiDB.config;
    
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
    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 javax.sql.DataSource;
    import java.util.HashMap;
    import java.util.Map;
    
    @Configuration
    public class DataSourceConfig {
    
        @Bean(name = "db1")
        @ConfigurationProperties("spring.datasource.db1")
        public DataSource db1DataSource() {
            return DataSourceBuilder.create().build();
        }
    
        @Bean
        @ConfigurationProperties("spring.datasource.db2")
        // 配置当存在enable属性值,且为true时才注入容器
        @ConditionalOnProperty(
                prefix = "spring.datasource.db2",
                name = "enable",
                havingValue = "true"
        )
        public DataSource db2() {
            return DataSourceBuilder.create().build();
        }
    
        /**
         * 通过AOP在不同的数据库之间进行切换。
         *
         * @param db1
         * @return
         */
        @Primary    //告诉spring这个是主数据源
        @Bean
        // 注意这个类名不可以
        public DynamicDataSource dynamicDB(@Qualifier("db1") DataSource db1) {
            DynamicDataSource dynamicDataSource = new DynamicDataSource();
            Map<Object, Object> targetDataSources = new HashMap<>();
            // 这里有两种方法把上面的配置放入map集合中,1.以参数传入,2.调用方法
            targetDataSources.put("db1", db1);
            targetDataSources.put("db2", db2());
            dynamicDataSource.setDefaultTargetDataSource(db1);
            dynamicDataSource.setTargetDataSources(targetDataSources);
            dynamicDataSource.afterPropertiesSet();
            return dynamicDataSource;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    1. 新建动态数据源类 DynamicDataSource.java(继承AbstractRoutingDataSource)
    package com.hx.multiDB.config;
    
    import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
    import org.springframework.stereotype.Component;
    
    import java.util.Map;
    
    /**
     * 继承抽象路由数据源类AbstractRoutingDataSource
     * 这里使用的是AbstractRoutingDataSource来切换配置,
     * 我们也可以使用mybatis中的SqlSessionTemplate,
     * 继承他的getSqlSessionFactory来切换数据源
     */
    public class DynamicDataSource extends AbstractRoutingDataSource {
        /**
         * 每次查询数据库都会执行该方法
         *
         * @return
         */
        @Override
        protected Object determineCurrentLookupKey() {
            return DynamicDataSourceHandler.getDB();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    1. 创建动态数据源处理类(包括保存数据源的容器,设置、获取、清空数据源方法)
    package com.hx.multiDB.config;
    
    import lombok.extern.slf4j.Slf4j;
    
    /**
     * 动态数据源处理器
     */
    @Slf4j
    public class DynamicDataSourceHandler {
        // 必须要线程变量的集合。不能用线程不安全的,否则在查询的时候并发切换,会导致错误。
        private static final ThreadLocal<String> DATASOURCE_HOLDER = new ThreadLocal<>();
    
        public static void setDB(String dbName){
           log.info("切换数据源为:{}",dbName);
            DATASOURCE_HOLDER.set(dbName);
        }
        public static String getDB(){
            return DATASOURCE_HOLDER.get();
        }
        // 每次使用完毕都要移除,否则会导致内存泄露
        public static void clear(){
            DATASOURCE_HOLDER.remove();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    利用Aop和注解来自动切换数据源

    1. 新建数据源注解 DataSource.java
    package com.hx.multiDB.config;
    
    import java.lang.annotation.*;
    
    @Target({ElementType.METHOD, ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface DataSource {
        String value();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    1. 创建数据源AOP DataSourceAop.java
    package com.hx.multiDB.config;
    
    import lombok.Data;
    import lombok.extern.slf4j.Slf4j;
    import org.aopalliance.intercept.Joinpoint;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.Signature;
    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.stereotype.Component;
    
    import java.lang.annotation.Annotation;
    import java.lang.reflect.Method;
    
    @Aspect
    @Component
    @Slf4j
    public class DataSourceAop {
        // 通过参数来拿到注解
    //    @Around("@annotation(dataSource)")
    //    public Object around( DataSource dataSource){
    //        dataSource.value();
    //    }
        // 通过切点对象来获取注解
    //    @Around("@annotation(com.hx.multiDB.config.DataSource)")
        // 切dao中的所有方法。
        @Around("execution(* com.hx.multiDB.mapper.*Mapper*.*(..))")
        public Object around(ProceedingJoinPoint joinpoint) {
            log.info("进入环绕通知...");
            MethodSignature signature = (MethodSignature) joinpoint.getSignature();
            Method method = signature.getMethod();
            // 获取方法上的注解
            DataSource annotation = method.getAnnotation(DataSource.class);
            if(annotation == null){
                // 通过签名拿到类
                Class type = signature.getDeclaringType();
                // 拿到类上的注解
                Annotation methodAnno = type.getAnnotation(DataSource.class);
                if(methodAnno instanceof DataSource){
                    annotation = (DataSource) methodAnno;
                }
            }
            if (annotation != null) {
                String value = annotation.value();
                // 设置数据源
                DynamicDataSourceHandler.setDB(value);
            }
            try {
                return joinpoint.proceed();
            } catch (Throwable throwable) {
                throwable.printStackTrace();
                return null;
            }finally {
                // 清空,避免内存泄露
                DynamicDataSourceHandler.clear();
                log.info("清空数据源Holder...");
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

    测试

    1. 编写TestMapper1.java和TestMapper2.java
    /**
     * 实际开发可能是读写分离等场景。
     */
    @Mapper
    @Repository
    public interface TestMapper1 {
        @Select(" SELECT * FROM TEST")
        List<Test> getAll();
    }
    /***** 以下是TestMapper2.java *****/
    @Mapper
    @Repository
    public interface TestMapper2 {
        @Select(" SELECT * FROM TEST2 ")
        List<Test> getAll();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    1. 新建测试 TestController.java
    package com.hx.multiDB.controller;
    
    import com.hx.multiDB.config.DataSource;
    import com.hx.multiDB.mapper.TestMapper1;
    import com.hx.multiDB.mapper.TestMapper2;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    @RequestMapping(path="/test",method={RequestMethod.GET,RequestMethod.POST})
    public class TestController {
        @Autowired
        private TestMapper1 mapper1;
        @Autowired
        private TestMapper2 mapper2;
    
        @GetMapping("/select1")
        public Object select1(){
            return mapper1.getAll();
        }
        @GetMapping("/select2")
        public Object select2(){
            return mapper2.getAll();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    TreadLocal原理

    官方描述: ThreadLocal类用来提供线程内部的局部变量,这种变量在多线程环境下访问(通过get和set方法访问)时,能保证各个线程的变量相对独立于其他线程内的变量。ThreadLocal实例通常是private static类型的,用于关联线程和线程的上下文。

    作用: 提供线程内的局部变量,不同的线程之间不会相互干扰,这种变量在线程的生命周期内起作用,减少一个线程内多个函数或组件之间一些公共变量传递的复杂度。

    总结:
    线程并发:在多线程并发的场景下使用
    传递数据:我们可以通过ThreadLocal在同一线程,不同组件中传递公共变量
    线程隔离:每个线程的变量都是独立的,不会互相影响

    synchronizedThreadLocal
    原理同步机制采用以时间换空间的方式,只提供了一份变量,让不同的线程排队访问ThreadLocal采用以空间换时间的方式,为每一个线程都提供了一份变量的副本,从而实现同时访问而相互不干扰
    侧重点多个线程之间访问资源的同步多线程中让每个线程之间的数据相互隔离

    ThreadLocal设计对比JDK8前后
    在这里插入图片描述
    在这里插入图片描述

    // JDK8的ThreadLocal下的Entry类通过继承WeakReference采用弱引用。
    static class Entry extends WeakReference<ThreadLocal<?>> {
    }
    
    • 1
    • 2
    • 3

    Memory overflow: 内存溢出,没有足够的内存提供申请者使用
    Memory leak: 内存泄漏,是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度缓慢甚至系统前遗等严重后果。内存泄漏的堆积终将导致内存溢出
    java中的引用有4种类型:强、软、弱、虚。当前这个问题主要涉及到强引用和弱引用
    StrongReference: 强引用,就是我们最常见的普通对象引用,只要还有强引用指向一个对象,就能证明对象还"活若”,垃圾回收器就不会回收这种对象,
    WeakReference: 弱引用,垃圾回收器一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。

    致谢:B站UP主【我觉得吧__】 视频地址:852196951
  • 相关阅读:
    ChatGPT绘图指南:DALL.E3玩法大全(一)
    asp.net core如何获取客户端IP地址
    阿里云安全恶意程序检测(速通一)
    全球与中国吸油烟机行业市场规模调研及未来前瞻报告2022-2028年
    项目管理PRINCE2核心知识点整理
    建设数字经济引领型城市 CDEC2022中国数字智能生态大会广州举行
    CentOS Install Passenger for ROR
    Flutter组件渲染集合的几种方式之详解与实战举例(更新)
    卷妹带你回顾Java基础(一)每日更新Day4
    第1章-数据结构与算法是什么
  • 原文地址:https://blog.csdn.net/qq_40366738/article/details/126862345