首页 > 数据库 >使用MySQL驱动方式实现读写分离

使用MySQL驱动方式实现读写分离

时间:2024-04-07 22:55:40浏览次数:12  
标签:jdbc import 读写 MySQL SQLException dataSource mysql 驱动

前言

MySQL 在 5.1.X 版本之后增加了对 multi-host 的支持,我们可以使用它来实现读写分离。

正常的 jdbc 连接格式为

jdbc:mysql://ip:3306/testdb?characterEncoding=UTF-8

multi-host 的 jdbc 连接格式为

jdbc:mysql:replication://ip:3306,ip:3307,ip:3308/testdb?useUnicode=true&characterEncoding=UTF-8&autoReconnect=false&ha.loadBalanceStrategy=random

第一个地址当作主库,后面的都当作从库。通过 Connection 中的 readonly 属性来控制读写,如果是写就操作主库,如果是读就操作从库。

注意:这种方式需要主从库的数据库名称、用户名、密码都一致

代码示例

我们可以搭建一个主从,也可以使用两个数据库来模拟主从,两个数据库的账号密码都一致。这里我们使用后一种方式。

5.1.42 版本需要使用 com.mysql.jdbc.ReplicationDriver 这个驱动类,高版本直接使用默认的驱动 com.mysql.cj.jdbc.Driver 就可以了(ReplicationDriver 这个类已经被删除了)。

import com.zaxxer.hikari.HikariDataSource;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class TestMysqlReplication {
    public static void main(String[] args) throws SQLException {
//        testMaster();
//        testSlave();
        testMasterSlaveSwitch();
    }

    /**
     * 查询主从切换
     * @throws SQLException
     */
    private static void testMasterSlaveSwitch() throws SQLException {
        DataSource dataSource = masterSlaveDataSource();
        Connection connection = dataSource.getConnection();
        connection.setReadOnly(true);
        List<Map<String, Object>> queryResult = getQueryResult(connection);
        System.out.println(queryResult);
        int updated = updateSql(connection);
        System.out.println(updated);
    }

    /**
     * 查询主库
     * @throws SQLException
     */
    private static void testMaster() throws SQLException {
        DataSource dataSource = masterDataSource();
        List<Map<String, Object>> queryResult = getQueryResult(dataSource.getConnection());
        System.out.println(queryResult);
    }

    /**
     * 查询从库
     * @throws SQLException
     */
    private static void testSlave() throws SQLException {
        DataSource dataSource = slaveDataSource();
        List<Map<String, Object>> queryResult = getQueryResult(dataSource.getConnection());
        System.out.println(queryResult);
    }

    private static DataSource masterDataSource() {
        HikariDataSource dataSource = new HikariDataSource();
        dataSource.setJdbcUrl("jdbc:mysql://ip:3310/testdb");
        dataSource.setUsername("xxx");
        dataSource.setPassword("xxx");
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        return dataSource;
    }

    private static DataSource slaveDataSource() {
        HikariDataSource dataSource = new HikariDataSource();
        dataSource.setJdbcUrl("jdbc:mysql://ip:3306/testdb");
        dataSource.setUsername("xxx");
        dataSource.setPassword("xxx");
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        return dataSource;
    }

    private static DataSource masterSlaveDataSource() {
        HikariDataSource dataSource = new HikariDataSource();
        dataSource.setJdbcUrl("jdbc:mysql:replication://ip:3310,ip:3306/testdb?ha.loadBalanceStrategy=random");
        dataSource.setUsername("xxx");
        dataSource.setPassword("xxx");
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        return dataSource;
    }

    private static List<Map<String, Object>> getQueryResult(Connection connection) throws SQLException {
        PreparedStatement ps = connection.prepareStatement("select * from tb_product");
        ResultSet resultSet = ps.executeQuery();
        List<Map<String, Object>> result = new ArrayList<>();
        while (resultSet.next()) {
            Map<String, Object> map = new HashMap<>();
            map.put("id", resultSet.getInt("id"));
            map.put("name", resultSet.getString("name"));
            map.put("stock", resultSet.getInt("stock"));
            result.add(map);
        }
        return result;
    }

    private static int updateSql(Connection connection) throws SQLException {
        PreparedStatement ps = connection.prepareStatement("update tb_product set stock=50 where id=1");
        return ps.executeUpdate();
    }
}
  1. 如果我们没设置 readonly=true,那么查到的数据为 [{name=小米手机, id=1, stock=38}],执行更新,实际操作的是主库
  2. 如果我们设置了 readonly=true,那么查到的数据为 [{name=华为手机, id=1, stock=38}],这个时候不能执行更新,会报错

原理分析

  1. NonRegisteringDriver 的 connect() 方法根据我们的 url 来解析出具体是什么类型的连接,连接类型可以查看 com.mysql.cj.conf.ConnectionUrl.Type 这个类。
  2. 根据 url 得到 ReplicationConnectionUrl 类型,在它的构造器逻辑中可以看到,取第一个地址为主库,后面所有地址当作从库。
  3. 继续根据 url 得到 Connection 的实际类型为 ReplicationConnectionProxy,它内部的 currentConnection 默认为 sourceConnection。
  4. 在 setReadOnly(true) 时,currentConnection 切换为 replicasConnection,实际类型为 LoadBalancedConnectionProxy。
  5. 根据 url 中配置的负载均衡策略 ha.loadBalanceStrategy 来决定使用哪个从库,默认策略为 random,实现类为 RandomBalanceStrategy。

Spring中控制

在Spring中,通过 @Transactional 注解的 readOnly 字段来控制是否走从库读。

  1. TransactionInterceptor 的 invoke() 方法拦截所有包含 @Transactional 注解的方法。
  2. 继续进入 invokeWithinTransaction() 方法,调用 createTransactionIfNecessary() 创建一个事务对象。
  3. 通过 PlatformTransactionManager(实际为 JdbcTransactionManager) 的 getTransaction() 方法获取事务状态对象。
  4. 调用 startTransaction() 方法内的 doBegin() 方法来开启事务。
  5. 调用 prepareConnectionForTransaction() 方法对连接做一些预处理,其中就包括设置 connection 的 readonly 字段,后续生效就是 MySQL 驱动来控制了。

总结

Spring 中通过 动态切换数据源 也可以实现读写分离,对比如下

  1. Spring 方式可扩展,适用于多种数据库,而 MySQL 驱动方式只适用于MySQL
  2. MySQL 驱动方式必须主从库用户名、密码一致,而 Spring 更灵活
  3. Spring 需要更多的配置,而 MySQL 驱动方式配置简单

参考

”MySQL官方驱动“主从分离的神秘面纱(扫盲篇)
mysql-read-write-splitting
Mysql使用ReplicationDriver驱动实现读写分离

标签:jdbc,import,读写,MySQL,SQLException,dataSource,mysql,驱动
From: https://www.cnblogs.com/strongmore/p/18049483

相关文章

  • Springboot计算机毕业设计财务报销微信小程序【附源码】开题+论文+mysql+程序+部署
    本系统(程序+源码)带文档lw万字以上 文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着移动互联网技术的飞速发展,微信小程序作为一种新型的应用形态,以其便捷、高效的特点受到了广大用户的青睐。在高等教育领域,财务管理是学校运营中不......
  • Springboot计算机毕业设计博物馆预约小程序【附源码】开题+论文+mysql+程序+部署
    本系统(程序+源码)带文档lw万字以上 文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景在信息化、数字化日益发展的今天,博物馆作为传承历史文化的重要场所,其管理和服务方式也在不断革新。传统的博物馆参观方式往往受限于开放时间、参观人......
  • 【数据库】MySQL数据库学习涵盖的多个方面
    数据库基础概念:数据库的概念和分类:了解什么是数据库以及常见的数据库类型。关系型数据库管理系统(RDBMS):理解RDBMS的概念及其在数据库管理中的作用。MySQL安装与配置:安装MySQL:学习如何在不同操作系统上安装MySQL数据库服务器。配置MySQL:包括设置root密码、配置网络连接、调整......
  • STM32CubeMX+MDK通过I2S接口进行音频输入输出(全双工读写一个DMA回调)
    一、前言目前有一个关于通过STM32F411CEUx的I2S总线接口控制SSS1700芯片进行音频输入输出的研究。SSS1700是具有片上振荡器的3S高度集成的USB音频控制器芯片。SSS1700功能支持96KHz24位采样率,带外部音频编解码器(24位/96KHzI2S输入和输出)并具有内置立体声16/24......
  • 探究MySQL8.0驱动的加载
    探究MySQL8.0驱动的加载大家在连接mysql的时候,启动项目,会警告你推荐使用com.mysql.cj.jdbc.Driver而不是com.mysql.jdbc.Driver那么这两者到底有什么区别呢本质区别:com.mysql.jdbc.Driver是mysql-connector-java5中的,需要手动加载驱动com.mysql.cj.jdbc.Driver是mysql......
  • 探究MySQL8.0驱动的加载
    探究MySQL8.0驱动的加载大家在连接mysql的时候,启动项目,会警告你推荐使用com.mysql.cj.jdbc.Driver而不是com.mysql.jdbc.Driver那么这两者到底有什么区别呢本质区别:com.mysql.jdbc.Driver是mysql-connector-java5中的,需要手动加载驱动com.mysql.cj.jdbc.Driver是mysql-......
  • AI编程005/ 逆向生成mysql的建表语句
    1/通过insertinto语句生成建表语句有些时候我们能获取到表的insert语句,但是没有表结构。我们可以借助AI工具,让其逆向生成mysql的建表语句。提示词如下:根据下面的SQL语句,逆向生存mysql的建表语句,每个字段都加上中文注释。'''INSERTINTOsys_user(user_id,dept_id,us......
  • 基于JSP+Mysql+HTml+Css宾馆酒店管理系统设计与实现
     博主介绍:黄菊华老师《Vue.js入门与商城开发实战》《微信小程序商城开发》图书作者,CSDN博客专家,在线教育专家,CSDN钻石讲师;专注大学生毕业设计教育和辅导。所有项目都配有从入门到精通的基础知识视频课程,学习后应对毕业设计答辩。项目配有对应开发文档、开题报告、任务书、P......
  • python计算机毕设【附源码】汉服文化管理系统(django+mysql+论文)
    本系统(程序+源码)带文档lw万字以上  文末可获取本课题的源码和程序系统程序文件列表系统的选题背景和意义选题背景:汉服,作为中国古代汉族传统服饰的总称,承载了丰富的历史文化遗产和审美价值。近年来,随着国民文化自信心的提升和传统文化复兴的浪潮,汉服文化逐渐走进了公众的......
  • windows下使用mysqldump备份数据库并上传到阿里云OSS
    使用mysqldump备份表powershell下使用|Out-file-Encodingutf8设置字符格式.\mysqldump.exe--single-transaction--user=root--password=123456--host127.0.0.1--port3306--default-character-set=utf8--hex-blob--routines--events"database_name"|Out......