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 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> SpringBoot多数据源 -> 正文阅读

[Java知识库]SpringBoot多数据源

使用场景

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

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

自定义多数据源实现类

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

关键类解析

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

/**
 * 继承抽象路由数据源类AbstractRoutingDataSource
 */
public class DynamicDataSource extends AbstractRoutingDataSource {
    /**
     * 每次查询数据库都会执行该方法
     * @return
     */
    @Override
    protected Object determineCurrentLookupKey() {
    	// 返回AbstractRoutingDataSource类targetDataSources集合的key。
        return null;
    }
}

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. 创建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. 新建数据源配置类 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. 新建动态数据源类 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. 创建动态数据源处理类(包括保存数据源的容器,设置、获取、清空数据源方法)
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();
    }
}

利用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. 创建数据源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. 编写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. 新建测试 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();
    }
}

TreadLocal原理

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

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

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

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

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

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

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

致谢:B站UP主【我觉得吧__】 视频地址:852196951
  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-09-24 20:42:44  更:2022-09-24 20:44:09 
 
开发: 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 8:54:49-

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