SpringBoot_第七章(读写分离)

这篇具有很好参考价值的文章主要介绍了SpringBoot_第七章(读写分离)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

1:MybatisPlus(读写分离)

1.1:首先创建三个数据库1主2从

1.2:代码实例

1.3:优缺点分析

2:SpringBoot路由数据源(读写分离)

2.1:实现原理

2.2:代码实现

2.3:测试代码 

2.4:优缺点分析


这里列举了两种读写分离实现方案,如下

1:MybatisPlus(读写分离)

1.1:首先创建三个数据库1主2从

表名是user表

SpringBoot_第七章(读写分离),框架_SpringBoot,java

SpringBoot_第七章(读写分离),框架_SpringBoot,java

SpringBoot_第七章(读写分离),框架_SpringBoot,java

1.2:代码实例

1:导入pom


        <!--MybatisPlus的jar 3.0基于jdk8-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.3.1</version>
        </dependency>

        <!-- mybatisPlus多数据源依赖  实现读写分离-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
            <version>3.6.1</version>
        </dependency>

        <!-- mysql的依赖-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.33</version>
        </dependency>

        <!--直接使用druid的starter 连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.16</version>
        </dependency>

2:配置spring的主从

server:
  port: 8082

spring:
  datasource:
    dynamic:
      primary: master #设置默认的数据源或者数据源组,默认值即为master
      strict: false #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
      datasource:
        master:
          url: jdbc:mysql://localhost:3306/W1?useUnicode=true&characterEncoding=utf-8&useSSL=false
          username: root
          password: 123456
          driver-class-name: com.mysql.cj.jdbc.Driver
          type: com.alibaba.druid.pool.DruidDataSource #配置德鲁伊数据源
          druid:
            initial-size: 10 #连接池初始化大小
            min-idle: 10 #最小空闲连接数
            max-active: 20 #最大连接数

        slave_1:
          url: jdbc:mysql://localhost:3306/W1R1?useUnicode=true&characterEncoding=utf-8&useSSL=false
          username: root
          password: 123456
          driver-class-name: com.mysql.cj.jdbc.Driver
          type: com.alibaba.druid.pool.DruidDataSource #配置德鲁伊数据源
          druid:
            initial-size: 10 #连接池初始化大小
            min-idle: 10 #最小空闲连接数
            max-active: 20 #最大连接数
        slave_2:
          url: jdbc:mysql://localhost:3306/W1R2?useUnicode=true&characterEncoding=utf-8&useSSL=false
          username: root
          password: 123456
          driver-class-name: com.mysql.cj.jdbc.Driver
          type: com.alibaba.druid.pool.DruidDataSource #配置德鲁伊数据源
          druid:
            initial-size: 10 #连接池初始化大小
            min-idle: 10 #最小空闲连接数
            max-active: 20 #最大连接数


        #......省略
        #以上会配置一个默认库master,一个组slave下有两个子库slave_1,slave_2


3:代码实例 @DS("slave") 注解用来切换数据源

/**
* @description 针对表【User】的数据库操作Service实现
* @createDate 2023-11-01 17:17:57
*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService{


    @Resource
    UserMapper userMapper;
    
    //多个从库 负载均衡轮训查询 也可以注解到类上
    //@DS("slave")
    @DS("slave")
    public User selectRW_S(Integer id) {
        return userMapper.selectById(id);
    }
    //主库查询数据
    //@DS("master")
    @DS("master")
    public User selectRW_M(Integer id) {
        return userMapper.selectById(id);
    }

    //无注解,不指定数据, 默认查询主库
    @Override
    public User selectRW(Integer id) {
        return userMapper.selectById(id);
    }
}

controller代码根据请求不同访问不同的数据源

@RestController
public class UserControllerRW {

    @Resource
    UserServiceImpl userService;

    //查询主库 @DS("master")
    @GetMapping(value = "selectRW1")
    public User selectRW_M(@RequestParam(name = "id",required = true) Integer id){
        User user = userService.selectRW_M(id);
        System.out.println(user);
        return user;
    }

    //查询从库 @DS("slave") 轮训查询
    @GetMapping(value = "selectRW2")
    public User selectRW_S(@RequestParam(name = "id",required = true) Integer id){
        User user = userService.selectRW_S(id);
        System.out.println(user);
        return user;
    }

    //无注解,不指定数据, 默认查询主库
    @GetMapping(value = "selectRW3")
    public User selectRW(@RequestParam(name = "id",required = true) Integer id){
        User user = userService.selectRW(id);
        System.out.println(user);
        return user;
    }
}

查询结果截图

SpringBoot_第七章(读写分离),框架_SpringBoot,java

SpringBoot_第七章(读写分离),框架_SpringBoot,java

SpringBoot_第七章(读写分离),框架_SpringBoot,java

1.3:优缺点分析

dynamic-datasource的jar包的官方文档

基础必读(免费) · dynamic-datasource · 看云

本框架只做切换数据的事情,不限制你的具体操作,从库也可以增删改查,读写分离只是他的一个小功能,分库分表要自己实现

  • 支持 数据源分组 ,适用于多种场景 纯粹多库 读写分离 一主多从 混合模式。
  • 支持数据库敏感配置信息 加密(可自定义) ENC()。
  • 支持每个数据库独立初始化表结构schema和数据库database。
  • 支持无数据源启动,支持懒加载数据源(需要的时候再创建连接)。
  • 支持 自定义注解 ,需继承DS(3.2.0+)。
  • 提供并简化对Druid,HikariCp,BeeCp,Dbcp2的快速集成。
  • 提供对Mybatis-Plus,Quartz,ShardingJdbc,P6sy,Jndi等组件的集成方案。
  • 提供 自定义数据源来源 方案(如全从数据库加载)。
  • 提供项目启动后 动态增加移除数据源 方案。
  • 提供Mybatis环境下的 纯读写分离 方案。
  • 提供使用 spel动态参数 解析数据源方案。内置spel,session,header,支持自定义。
  • 支持 多层数据源嵌套切换 。(ServiceA >>> ServiceB >>> ServiceC)。
  • 提供 基于seata的分布式事务方案 。
  • 提供 本地多数据源事务方案。

2:SpringBoot路由数据源(读写分离)

2.1:实现原理

实现方案是通过在spring容器中注入多个数据源,通过不同的key来找到指定的数据源,AbstractRoutingDataSource的源码知道,AbstractRoutingDataSource里边通过Map可以过个数据源,通过key查找。

2.2:代码实现

1:maven依赖 

 <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

     
        <!--mybatis依赖-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>3.0.2</version>
        </dependency>

        <!--aop依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>


        <!-- mysql的依赖-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.33</version>
        </dependency>

        <!--直接使用druid的starter 连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.16</version>
        </dependency>
        
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
        </dependency>

    </dependencies>

2:配置数据源的1主2从的application.properties

#读写分离数据源配置
#读写分离数据源配置
#配置主数据库
spring.datasource.master.jdbc-url=jdbc:mysql://localhost:3306/W1?useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.master.username=root
spring.datasource.master.password=123456
spring.datasource.master.driver-class-name=com.mysql.cj.jdbc.Driver

#配置读数据库1
spring.datasource.read1.jdbc-url=jdbc:mysql://localhost:3306/W1R1?useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.read1.username=root
spring.datasource.read1.password=123456
spring.datasource.read1.driver-class-name=com.mysql.cj.jdbc.Driver


#配置读数据库2
spring.datasource.read2.jdbc-url=jdbc:mysql://localhost:3306/W1R2?useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.read2.username=root
spring.datasource.read2.password=123456
spring.datasource.read2.driver-class-name=com.mysql.cj.jdbc.Driver

3:代码注入多个数据源

@Configuration
public class MyDataSourceConfig {

    /**
     * 主库
     * @return
     */
    @Bean(value = "master")
    @ConfigurationProperties(prefix = "spring.datasource.master")
    public DataSource masterDataSource(){
        return DataSourceBuilder.create().build();
    }

    /**
     * 从库1
     * @return
     */
    @Bean(value = "read1")
    @ConfigurationProperties(prefix = "spring.datasource.read1")
    public DataSource read1DataSource(){
        return DataSourceBuilder.create().build();
    }


    /**
     * 从库2
     * @return
     */
    @Bean(value = "read2")
    @ConfigurationProperties(prefix = "spring.datasource.read2")
    public DataSource read2DataSource(){
        return DataSourceBuilder.create().build();
    }


    /**
     * 配置4个数据源 1个路由数据源 3个自定义数据源
     * 自定义的路由数据源
     * @return
     */
    @Bean(value = "myRoutingDataSource")
    public DataSource myRoutingDataSource(@Qualifier(value="master")DataSource master,
                                        @Qualifier(value="read1")DataSource read1,
                                        @Qualifier(value="read2") DataSource read2){

        //自定义路由数据源
        MyRoutingDataSource routingDataSource=new MyRoutingDataSource();
        //目标数据源 master  read1 read2
        Map<Object, Object> targetDataSources=new HashMap<>();
        targetDataSources.put("master",master);
        targetDataSources.put("read1",read1);
        targetDataSources.put("read2",read2);
        //放入默认数据源是 master
        routingDataSource.setDefaultTargetDataSource(master);
        routingDataSource.setTargetDataSources(targetDataSources);
        return routingDataSource;
    }


}

 4:数据源切换代码实现

public class DBContextHolder {

    private static final ThreadLocal<String> contextHolder = new ThreadLocal();
    private static final AtomicInteger count = new AtomicInteger(1);

    //ThreadLocal的get、set方法设置数据源
    public static String get() {
        return contextHolder.get();
    }
    public static void set(String dataSource) {
        contextHolder.set(dataSource);
    }

    public static void remove() {
        contextHolder.remove();
    }

    /**
     * 获取 主数据库
     */
    public static void master() {
        set("master");
        System.out.println("切换到master数据源");
    }

    /**
     * 获取从数据库 随机获取 或者随机获取
     */
    public static void read() {
//        //轮询数据源进行读操作
//        Random random=new Random();
//        int index = random.nextInt(10);
//        System.out.println("随机数:"+index);
//        if(index>5){
//            set("read1");
//            System.out.println("切换到read1数据源");
//        }else {
//            set("read2");
//            System.out.println("切换到read2数据源");
//        }

        int index = count.getAndIncrement()%2;
        System.out.println("模数:" + index);
        System.out.println("随机数:" + count);

        //轮询数据源进行读操作
        if (count.get() > 10) {
            count.set(1);
        }
        if (index == 0) {
            set("read1");
            System.out.println("切换到slave1数据源");
        } else {
            set("read2");
            System.out.println("切换到slave2数据源");
        }

    }

    public static void main(String[] args) {
        //轮询数据源进行读操作
        Random random = new Random();
        int index = random.nextInt(10);
        System.out.println(index);
    }

}

5:获取路由数据源

/**
 * 自定义路由数据源 继承AbstractRoutingDataSource
 */
public class MyRoutingDataSource extends AbstractRoutingDataSource {

    /**
     *  通过指定的key来确认当前的数据源方法
     *
     *  目标数据源 master  read1  read2
     *         Map<Object, Object> targetDataSources=new HashMap<>();
     *         targetDataSources.put("master",master);
     *         targetDataSources.put("read1",read1);
     *         targetDataSources.put("read2",read2);
     *
     *
     *  根据Aop 在执行不同方法之前  设置不同的数据源的key
     */

    @Override
    protected Object determineCurrentLookupKey() {
        return DBContextHolder.get();
    }
}

 6:mybatis的数据源配置

/**
 * mybatis的数据源配置
 * 由于Spring容器中现在有4个数据源,所以我们需要为事务管理器和MyBatis手动指定一个明确的数据源。
 */
@Configuration
public class MyBatisConfig {

    @Resource(name = "myRoutingDataSource")
    private DataSource myRoutingDataSource;

    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(myRoutingDataSource);
        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));
        return sqlSessionFactoryBean.getObject();
    }

    @Bean
    public PlatformTransactionManager platformTransactionManager() {
        return new DataSourceTransactionManager(myRoutingDataSource);
    }
}

7:AOP切入处理,在service方法中通过切面提前设置数据源的key

@Aspect
@Component
public class DataSourceAop {

    /**
     * 不存在自定义的Master注解
     * 所有service包下的的所有select和get方法 
     * 设置为读从库数据
     */
    @Pointcut("!@annotation(com.example.springboot08_rw1.annotation.Master) " +
            "&& (execution(* com.example.springboot08_rw1.service..*.select*(..)) " +
            "|| execution(* com.example.springboot08_rw1.service..*.get*(..)))")
    public void readPointcut() {

    }

    /**
     * 存在Master注解或者add insert等方法 去主库操作
     */
    @Pointcut("@annotation(com.example.springboot08_rw1.annotation.Master) " +
            "|| execution(* com.example.springboot08_rw1.service..*.insert*(..)) " +
            "|| execution(* com.example.springboot08_rw1.service..*.add*(..)) " +
            "|| execution(* com.example.springboot08_rw1.service..*.update*(..)) " +
            "|| execution(* com.example.springboot08_rw1.service..*.edit*(..)) " +
            "|| execution(* com.example.springboot08_rw1.service..*.delete*(..)) " +
            "|| execution(* com.example.springboot08_rw1.service..*.remove*(..))")
    public void writePointcut() {
    }


    @Before("readPointcut()")
    public void read() {
        DBContextHolder.read();
    }


    @Before("writePointcut()")
    public void write() {
        DBContextHolder.master();
    }

//    /**
//     * 另一种写法:if...else...  判断哪些需要读从数据库,其余的走主数据库
//     */
//    @Before("execution(* com.example.springboot08_rw1.service.UserServiceImpl.select*(..))")
//    public void before(JoinPoint jp) {
//        String methodName = jp.getSignature().getName();
//        System.out.println("方法名字:"+methodName);
        if (StringUtils.startsWithAny(methodName, "get", "select", "find")) {
        }else {
            DBContextHolder.master();
        }
//
//        DBContextHolder.read();
//
//    }

}




//自定义注解

public @interface Master {
}

2.3:测试代码 

service代码

@Service
public class UserServiceImpl implements  UserService {
    @Resource
    UserMapper userMapper;
    
    /**
     * 添加自定义注解  @Master 主库查询
     */
    @Master
    public User selectById_Master(Integer id) {
        return userMapper.selectById(1);
    }

    /**
     *  不使用注解  走从库查询
     */
    public User selectById_MoRen(Integer id) {
        return userMapper.selectById(1);
    }

    /**
     * 不使用注解 add方法 走主库
     */
    public User add(Integer id) {
        return userMapper.selectById(1);
    }
}

controller层的代码测试

@RestController
public class UserController {

    @Autowired
    UserServiceImpl userService;

    //两个从库切换查询
    @GetMapping(value = "selectUser")
    public User select(){
       return userService.selectById_MoRen(1);
    }
    //使用注解 查询主库
    @GetMapping(value = "selectUser1")
    public User select1(){
        return userService.selectById_Master(1);
    }
    //方法是add 查询主库
    @GetMapping(value = "selectUser2")
    public User select2(){
        return userService.add(1);
    }
}

页面效果

http://localhost:8082/selectUser两个从切换查询

http://localhost:8082/selectUser1主库查询

http://localhost:8082/selectUser2主库查询

2.4:优缺点分析

SpringBoot读写分离_springboot 读写分离-CSDN博客

代码比较复杂,需要制定好spring的数据源切换的基础原理,分库分表代码进一步负载话文章来源地址https://www.toymoban.com/news/detail-738437.html

到了这里,关于SpringBoot_第七章(读写分离)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 第七章 函数矩阵

    和矩阵函数不同的是,函数矩阵本质上是一个矩阵,是以函数作为元素的矩阵。 矩阵函数本质上是一个矩阵,是以矩阵作为自变量的函数。 函数矩阵和数字矩阵的运算法则完全相同。 不过矩阵的元素 a i j ( x ) a_{ij}(x) a ij ​ ( x ) 需要是闭区间 [ a , b ] [a,b] [ a , b ] 上的实函数

    2024年02月04日
    浏览(58)
  • 第七章 图论

    第七章 图论 一、数据结构定义 图的邻接矩阵存储法 图的邻接表存储法 把所有节点存储为节点数组,每个节点里有自己的数据和一个边指针,这个边指针相当于一个链表的头指针,这个链表里存放所有与这个节点相连的边,边里存放该边指向的节点编号和下一条边指针 图的

    2024年02月14日
    浏览(89)
  • 数据结构第七章

    图(Graph)G由两个集合V和E组成,记为G=(V, E),其中V是顶点的有穷非空集合,E是V中顶点偶对的有穷集合,这些顶点偶对称为边。V(G)和E(G)通常分别表示图G的顶点集合和边集合,E(G)可以为空集。若EG)为空,则图G只有顶点而没有边。 子图:假设有两个图G=(V,E)和G1=(V1,E1);如果V1

    2024年02月03日
    浏览(63)
  • 第七章 测试

    7.1.1 选择程序设计语言 1. 计算机程序设计语言基本上可以分为汇编语言和高级语言 2. 从应用特点看,高级语言可分为基础语言、结构化语言、专用语言 01 有理想的模块化机制; 02 可读性好的控制结构和数据结构; 03 便于调试和提高软件可靠性; 04 编译程序发现程序错误的

    2024年02月08日
    浏览(77)
  • [JavaScript] 第七章 对象

    🌹作者主页:青花锁 🌹简介:Java领域优质创作者🏆、Java微服务架构公号作者😄 🌹简历模板、学习资料、面试题库、技术互助 🌹文末获取联系方式 📝 [Java项目实战] 介绍Java组件安装、使用;手写框架等 [Aws服务器实战] Aws Linux服务器上操作nginx、git、JDK、Vue等 [Java微服务

    2024年02月02日
    浏览(129)
  • 第七章 面向对象编程(基础)

    (1)类是抽象的,概念的,代表一类事物,比如人类、猫类... 即它是数据类型。 (2)对象是具体的,实际的,代表一个具体事物,即实例。 (3)类是对象的模板,对象是类的一个个体,对应一个实例。 属性是类的一个组成部分,一般是基本数据类型,也可是引用类型(对

    2024年02月06日
    浏览(84)
  • OpenCV:第七章、图像变换

    目录 第七章:图像变换 7.1、基于OpenCV的边缘检测 7.1.1、一般步骤 1、滤波 2、增强 3、检测 7.1.2、canny算子 1、canny算子简介 2、canny边缘检测的步骤 7.2、霍夫变换  7.2.2、OpenCV中的霍夫线变换 7.2.3、霍夫线变换原理        7.2.4、标准霍夫变换:HoughLines()函数    7.2.5、累计概率

    2024年02月03日
    浏览(68)
  • Flink第七章:状态编程

    Flink第一章:环境搭建 Flink第二章:基本操作. Flink第三章:基本操作(二) Flink第四章:水位线和窗口 Flink第五章:处理函数 Flink第六章:多流操作 Flink第七章:状态编程 这次我们来学习Flink中的状态学习部分,创建以下scala文件 这个文件里有几个常用的状态创建 按键分区中值状态编程案

    2024年02月06日
    浏览(66)
  • C国演义 [第七章]

    力扣链接 给两个整数数组 nums1 和 nums2 ,返回 两个数组中 公共的 、长度最长的子数组的长度 。 示例 1: 输入:nums1 = [1,2,3,2,1], nums2 = [3,2,1,4,7] 输出:3 解释:长度最长的公共子数组是 [3,2,1] 。 示例 2: 输入:nums1 = [0,0,0,0,0], nums2 = [0,0,0,0,0] 输出:5 提示: 1 = nums1.length, num

    2024年02月10日
    浏览(44)
  • 第七章 正则表达式

    目录 1.1. 概念: 1.2. 基本正则表达式 1.2.1. 常见元字符 1.2.2. POSIX字符类 1.2.3. 示例 1.3. 扩展正则表达式 1.3.1. 概念 1.3.2. 示例 在进行程序设计的过程中,用户会不可避免地遇到处理某些文本的情况。有的时候,用户还需要查找符合某些比较复杂规则的字符串。对于这些情况,如

    2024年03月17日
    浏览(85)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包