Java:95-分库分表技术之ShardingJDBC

分库分表技术之ShardingJDBC

ShardingJDBC:
回顾上一章的分库分表方式:
分库分表的目的就是将我们的单库的数据控制在合理范围内,从而提高数据库的性能
垂直拆分(按照结构分):
垂直分表:将一张宽表(字段很多的表), 按照字段的访问频次进行拆分,就是按照表单结构进行拆分
垂直分库:根据不同的业务,将表进行分类,拆分到不同的数据库,这些库可以部署在不同的服 务器,分摊访问压力
水平拆分(按照数据行分):
水平分库:将一张表的数据 (按照数据行) 分到多个不同的数据库,每个库的表结构相同
每个库都只有这张表的部分数据,当单表的数据量过大,如果继续使用水平分库
那么数据库的实例 就会不断增加,当然也可以不增加,但是不增加这样做,会导致某些数据库性能低于其他数据库
因为他的数据多,最后也必然需要对他进行处理
所以我们通常只会一个主机对应一个分库,而不是一个主机对应多个(当然,我们可以使用不增加,因为数据量不会太大)
所以在超大的数据下,一般是一个主机对应一个分库,那么再次的分库分表
不利于系统的运维,因为实例太多了,这时候就要采用水平分表
水平分表:将一张表的数据 (按照数据行),分配到同一个数据库的其他数据库的多张表中,每个表都只有一部分数据
什么时候用分库分表:
在系统设计阶段,就要完成垂直分库和垂直分表,在数据量不断上升,数据库性能无法满足需求的时 候
首先要考虑的是缓存、 读写分离、索引技术等方案,如果数据量不断增加,并且持续增长再考虑 水平分库 水平分表
分库分表带来的问题:
关系型数据库在单机单库的情况下,比较容易出现性能瓶颈问题
分库分表可以有效的解决这方面的问 题,但是同时也会产生一些 比较棘手的问题
事务一致性问题:
当我们需要更新的内容同时分布在不同的库时,不可避免的会产生跨库的事务问题
原来在一个数据库操作,本地事务就可以进行控制,分库之后 一个请求可能要访问多个数据库
如何保证事务的一致性,目前还 没有简单的解决方案
跨节点关联的问题:
在分库之后,原来在一个库中的一些表,被分散到多个库,并且这些数据库可能还不在一台服务器,无法关联查询
解决这种关联查询,需要我们在代码层面进行控制,将关联查询拆开执行,然后再将获取到的结果进行拼装
分页排序查询的问题:
分库并行查询时,如果用到了分页,每个库返回的结果集本身是无序的,只有将多个库中的数据先查出来
然 后再根据排序字段在内存中进行排序,如果查询结果过大也是十分消耗资源的
主键避重问题 :
在分库分表的环境中,表中的数据存储在不同的数据库,主键自增无法保证ID不重复,需要单独设计全局主 键
公共表的问题:
不同的数据库,都需要从公共表中获取数据,可以在每一个库都创建这个公共表
所有对公共表的更新操作,都同时发送到所有分库执行
ShardingJDBC可以帮助我们解决这个问题
ShardingJDBC 简介 :
什么是ShardingJDBC:
ShardingSphere是一套开源的分布式数据库中间件解决方案组成的生态圈
它由Sharding-JDBC、Sharding-Proxy和Sharding-Sidecar(计划中)这3款相互独立的产品组成
我们只关注 Sharding-JDBC即可
官方地址:https://shardingsphere.apache.org/document/current/cn/overview/
Sharding-JDBC 定位为轻量级Java框架,在Java的JDBC层提供的额外服务
它使用客户端直连数据库,以jar包形式提供服务
无需额外部署和依赖,可理解为增强版的JDBC驱动,完全兼容JDBC和各种ORM框架的使用
适用于任何基于Java的ORM框架
如:JPA, Hibernate, Mybatis, Spring JDBC Template或直接使 用JDBC
基于任何第三方的数据库连接池,如:DBCP, C3P0, Druid等
基本支持任意实现JDBC规范的数据库,目前支持MySQL,Oracle,SQLServer和PostgreSQ

在这里插入图片描述

上图展示了Sharding-Jdbc的工作方式,使用Sharding-Jdbc前需要人工对数据库进行分库分表
在应 用程序中加入Sharding-Jdbc的Jar包,应用程序通过Sharding-Jdbc操作分库分表后的数据库和数据表
由于Sharding-Jdbc是对Jdbc驱动的增强,使用Sharding-Jdbc就像使用Jdbc驱动一样
在应用程序中是 无需指定具体要操作的分库和分表的
Sharding-JDBC主要功能 :
数据分片,读写分离
通过Sharding-JDBC,应用可以透明的使用jdbc访问已经分库分表、读写分离的多个数据源
而不用关 心数据源的数量以及数据如何分布
Sharding-JDBC与MyCat的区别 :
1:mycat是一个中间件的第三方应用,sharding-jdbc是一个jar包
2:使用mycat时不需要修改代码,而使用sharding-jdbc时需要修改代码
3:Mycat 是基于 Proxy,它复写了 MySQL 协议,将 Mycat Server 伪装成一个 MySQL 数据库
而 Sharding-JDBC 是基于 JDBC 的扩展,是以 jar 包的形式提供轻量级服务的
Mycat(proxy中间件层):

在这里插入图片描述

Sharding-jdbc(应用层):

在这里插入图片描述

Sharding-JDBC入门使用:
搭建基础环境:
需求说明:
创建数据库lg_order,模拟将订单表进行水平拆分,创建两张表pay_order_1 与 pay_order_2
这两 张表是订单表拆分后的表,我们通过Sharding-Jdbc向订单表(也就是这两张表)插入数据,按照一定的分片规则
如主键 为偶数的落入pay_order_1表 ,为奇数的落入pay_order_2表,再通过Sharding-Jdbc 进行查询
创建数据库:
在本地创建即可,不用操作Linux的数据库
CREATE DATABASE lg_order CHARACTER SET "utf8";
-- "utf8"可以不加引号,反正不区分,相当于一个数(添加数据时需要加,那里是区分的)

USE lg_order;

DROP TABLE IF EXISTS pay_order_1;

CREATE TABLE pay_order_1 (
 order_id BIGINT(20) PRIMARY KEY AUTO_INCREMENT ,
 user_id INT(11) ,
 product_name VARCHAR(128),
 COUNT INT(11)
);

DROP TABLE IF EXISTS pay_order_2;

CREATE TABLE pay_order_2 (
 order_id BIGINT(20) PRIMARY KEY AUTO_INCREMENT ,
 user_id INT(11) ,
 product_name VARCHAR(128),
 COUNT INT(11)
);
创建SpringBoot项目引入maven依赖 :
sharding-jdbc以jar包形式提供服务,所以要先引入maven依赖
<dependency>
      <!--如果没有配置,那么不会操作对应的配置的作用,他的作用可以理解为,也就相当于操作执行(修改)sql的操作-->
         <groupId>org.apache.shardingsphere</groupId>
         <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
         <version>4.0.0-RC1</version>
</dependency>
当然,你可以使用这个项目,地址如下:
链接:https://pan.baidu.com/s/1MzVCvI7HxkFq3b24HKEo2A
提取码:alsk
我们就以这个项目为主了
这里需要注意,后面的规则是最后操作的,也就是说,当我们操作mybatis的相关操作时(比如mybatis的注解,或者mp等等)
首先是他们先操作完毕,然后才会操作规则,大多数的分开分表操作,基本都是最后操作的,所以这里注意即可
分片规则配置(水平分表):
使用sharding-jdbc 对数据库中水平拆分的表进行操作
通过sharding-jdbc对分库分表的规则进行配置
配置内容包括:数据源、主键生成策略、分片策略等
application.properties:
基础配置:
#定义名称
spring.application.name = sharding-jdbc-simple 
#定义起始路径
server.servlet.context-path = /sharding-jdbc
#通常用来解决中文乱码,在88章博客有具体说明
spring.http.encoding.enabled = true
spring.http.encoding.charset = UTF-8
spring.http.encoding.force = true
#解决bean重复问题,在89章博客有具体说明
spring.main.allow-bean-definition-overriding = true
#开启驼峰命名匹配映射
mybatis.configuration.map-underscore-to-camel-case = true
数据源:
# 定义数据源名称,若指定多个,一般以逗号隔开,来指定多个,比如ds0,ds1,这是为了操作多个不同的地址
#逗号两边都可以加空格(逗号基本只能是英文,基本上所有的框架都是如此,除非有特别),操作多个最好需要手动的指定
#否则执行(或者说启动,我们称为执行报错)可能会报错,因为其他的数据源,可能没有该表(如果都有表,那么可以不指定),即他还是操作全部的
#其中,操作全部时,对应的自增的值,是统一给的,也就是说,雪花算法的值
#只会给对应的指向(该指向可以是多个,比如全部)
#但要注意,真正执行的报错基本只有程序的问题,因为其他基本需要启动才可
#后面说明的报错基本都是如此
#这里注意即可
spring.shardingsphere.datasource.names = db1
# 定义数据源类型,会发现,在后面加上了db1,表示是该数据源的设置,如果不加,那么就是操作默认的设置
# 这里代表该连接操作连接池了,而不是单独的连接(默认就是单独的连接)
spring.shardingsphere.datasource.db1.type = com.alibaba.druid.pool.DruidDataSource
#定义数据源操作的数据库地址
spring.shardingsphere.datasource.db1.driver-class-name = com.mysql.jdbc.Driver
spring.shardingsphere.datasource.db1.url = jdbc:mysql://localhost:3306/lg_order?characterEncoding=UTF-8&useSSL=false
spring.shardingsphere.datasource.db1.username = root
spring.shardingsphere.datasource.db1.password = 123456

#一般只有Sharding-JDBC可以设置指定多个,普通的基本没有指定
#像没有操作的配置,也就是相当于定义变量,而不会被操作,虽然上面的基本都是操作的
#当然,这里的配置优先于普通的数据源配置,除非对应的依赖没有了

#这里就需要提一下了,如果不指定数据源名称,那么ShardingJDBC启动会报错,因为他必须操作一个
#而如果要使得默认的配置操作,那么必须删除依赖
#才可操作普通的连接,而不会经过ShardingJDBC(因为他必须操作一个)
#而删除依赖,自然对应的配置就是设置值,而不会操作,所以这时不删除也行
配置数据节点:
#配置数据节点,指定节点的信息,或者说,指定哪个表,这里就指定了db1数据源的对应表
#而没有指定的,不会操作默认的,而是会报错,即他基本只能操作指定的
#tables.pay_order的pay_order相当于逻辑表的意思,因为这里可以删除,所以这里就不说明
spring.shardingsphere.sharding.tables.pay_order.actual-data-nodes = db1.pay_order_$->{1..2}
#{1..2},代表1到2之间,包括1和2,但是他并不是真正的发送,他只是指定而已,即这一行可以删除

#这里在后面会解释其具体作用,现在略过即可
表达式:db1.pay_order_$->{1…2}
$ 会被 大括号中的 {1…2} 所替换
即表示会有两种选择:db1.pay_order_1 和 db1.pay_order_2
配置主键生成策略(简称自动生成,后面会多次提到这个"自动生成"):
#指定pay_order表 (我们可以看成逻辑表)的主键(order_id),且生成策略为 SNOWFLAKE
#即给该字段生成值,并不是非要是主键,只要是字段即可(必须都是小写,这是规定,因为参与了数据)
#但这里与分片键不同,他的字段会先放在具体位置(一般往后面加)
#然后通过算法生成一个数,并放入,如果操作的分片对应的表存在该字段
#即操作逻辑表对应的表,否则替换回来,操作实际表(就是该逻辑表名的表),这里要注意
spring.shardingsphere.sharding.tables.pay_order.key-generator.column=order_id
#SNOWFLAKE:雪花(即雪花算法,来生成不重复的主键,即操作唯一主键)
spring.shardingsphere.sharding.tables.pay_order.key-generator.type=SNOWFLAKE
#上面两个都需要存在,即他们是一起的,少一个就不能执行,且他们两个操作的逻辑表也要相同,否则报错
#因为需要一个指定字段,一个来添加数据,自然需要相同的地方,即逻辑表
#其中pay_order代表逻辑表的意思,与MyCat一样的操作,我们操作该表,那么就会给对应的表
#所以在后面添加中,我们操作的就是该表,就算该表不存在
使用shardingJDBC提供的主键生成策略,全局主键
为避免主键重复,生成主键采用 SNOWFLAKE 分布式ID生成算法
配置分片算法(简称分片,后面也会多次提到这个"分片"):
#指定pay_order表的分片策略(也可以认为是分表策略,后面会说明分库策略)
#分片策略包括分片键和分片算法,这里操作主要的,所以之前的
#spring.shardingsphere.sharding.tables.pay_order.actual-data-nodes = db1.pay_order_$->{1..2}
#这个,可以删除,因为语句就已经指定了逻辑表了,他并没有什么作用,自己测试就知道了
spring.shardingsphere.sharding.tables.pay_order.table-strategy.inline.sharding-column= order_id
spring.shardingsphere.sharding.tables.pay_order.table-strategy.inline.algorithm-expression = pay_order_$->{order_id % 2 + 1}
#上面的{order_id % 2 + 1}里面的字段order_id(基本只能都是小写)是使用
#spring.shardingsphere.sharding.tables.pay_order.table-strategy.inline.sharding-column= order_id
#这个order_id字段的(该字段可以忽略大小写)
#因为指定表的字段的(在sql语句到达之前,自然可以知道表的字段,因为我们定义了,如添加语句里面的要指定的字段名,这里一般使用自动生成操作的字段)
#所以可以忽略大小写,所以,如果不匹配,自然会报错,这是计算的原因,当然你可以不使用,直接指定表
#如果指定的字段不存在,那么就不会操作执行(不会放行了)
#即默认操作真实表了(不会替换了)
#虽然这里(注意是这里,操作了真实的逻辑表名)一般使得返回的数据基本为空,或者不操作执行(即可能报错,没有该表)
#但是使用中的{order_id % 2 + 1}的order_id,必须都是小写,否则执行报错,因为是规定的,因为他参与计算
#即基本需要相同,即他代表取得order_id的值(所以他们是一起的,因为要取,那么逻辑表自然也需要一起)
#该值由于在执行sql之前,自然是操作我们变量的值(传递的变量值)
#因为与表的对应的字段对应,所以相当于操作变量的值,你交换@Param注解的值,就知道了
#通常需要整型的,否则会报错,修改类型就知道了
#但是由于需要自动生成,不难知道,他的操作是在自动生成之后的
#即自动生成操作完之后,这里的分片才会操作

#这里和上面的主键生成策略一样,他们是一起的,即不能少一个,以及逻辑表要相同,否则也会报错

#注意:如果这里和上面的主键生成策略的逻辑表没有操作,且他们的配置正确,实际上是分片的键不存在导致的如下
#那么就是操作你指定的表,即不是逻辑表了(即是实际表,地址那里的表)
#否则只要你操作的表与逻辑表名相同,那么就是操作逻辑表,而不是真的表
#所以逻辑表设置了,那么对应的地址的pay_order就基本操作不了了

#最后注意:如果这里和上面的主键生成策略,只要少了一个(实际上主键生成少了,逻辑表也可以操作,只要你有该字段)
#那么逻辑表就不会操作,即不会操作逻辑表
#那么pay_order就是操作真实的表,而不是逻辑表
分表策略表达式:pay_order_$-> {order_id % 2 + 1}
{order_id % 2 + 1} 结果是偶数 操作 pay_order_1表
{order_id % 2 + 1} 结果是奇数 操作 pay_order_2表
打开SQL日志:
# 打开sql输出日志,即会出现sql语句的打印,否则不会出现
spring.shardingsphere.props.sql.show = true
步骤总结:
1:定义数据源
2:指定pay_order 表的数据分布情况, 分布在 pay_order_1 和 pay_order_2
3:指定pay_order 表的主键生成策略为SNOWFLAKE,是一种分布式自增算法,保证id全局唯一
4:定义pay_order分片策略,order_id为偶数的数据下沉到pay_order_1,为奇数下沉到在pay_order_2
即对应的application.properties文件如下:
#定义名称
spring.application.name = sharding-jdbc-simple 
#定义起始路径
server.servlet.context-path = /sharding-jdbc
#通常用来解决中文乱码,在88章博客有具体说明
spring.http.encoding.enabled = true
spring.http.encoding.charset = UTF-8
spring.http.encoding.force = true
#解决bean重复问题,在89章博客有具体说明
spring.main.allow-bean-definition-overriding = true
#开启驼峰命名匹配映射
mybatis.configuration.map-underscore-to-camel-case = true

# 定义数据源名称
spring.shardingsphere.datasource.names = db1
#如果出现相同的属性,那么在后面的覆盖前面的,虽然同时存在会报错
#但运行时或者启动时(测试类的执行,相当于先启动,然后执行,即运行)
#并不会,因为他只是检查报错而已
#虽然properties文件里可以这样
#但是,在yaml或者yml文件里面,会直接的报错
#可能他们有判断吧(因为虽然他们具体配置的意思相同,但是后缀还是不同的,可能是根据后缀判断的)
#使得启动报错,自然执行也就会报错

# 定义数据源类型
spring.shardingsphere.datasource.db1.type = com.alibaba.druid.pool.DruidDataSource
#定义数据源操作的数据库地址
#除了下面的这一个配置,其他四个基本都要写,否则报错
#用户名和密码没有默认的值,所以与普通的连接是不同的(普通的可以省略,但这里不可以)
#且规定要指定一个连接池,否则不行(报错)
#因为在后面基本只有连接池可以操作多个连接
#普通的获取和释放太浪费资源(对于后期来说的,前期连接池资源高,因为先分配的)或者性能了(获取和释放自然需要性能)
#性能意味着用更少的资源做更多的事情或者也可以说是运行速度的快慢(大多数这样认为,实际上内存也算)等等,所以资源的多少与性能并没有直接的关系
#可能成反比或者正比,因为可能会出现使用多的资源,性能反而下降(或者提升),而使用少的资源,性能却提高(或者下降),比如因为算法的原因
#但使用资源,自然也会使用一些性能
#而不是和连接池一样,只操作固定的资源,而不用获取和释放,即提高性能
#所以一般性能指这个程序所需要的内存和时间的多少,越少性能越好
#所以这里Sharding-JDBC就规定需要一个连接池,否则报错,即是防止以后开发出现的问题
spring.shardingsphere.datasource.db1.driver-class-name = com.mysql.jdbc.Driver

spring.shardingsphere.datasource.db1.url = jdbc:mysql://localhost:3306/lg_order?characterEncoding=UTF-8&useSSL=false
spring.shardingsphere.datasource.db1.username = root
spring.shardingsphere.datasource.db1.password = 123456

#配置数据节点,指定节点的信息
#spring.shardingsphere.sharding.tables.pay_orderr.actual-data-nodes = db1.pay_order_$->{1..2}

#指定pay_order表 (逻辑表)的主键生成策略为 SNOWFLAKE
spring.shardingsphere.sharding.tables.pay_order.key-generator.column=order_id
spring.shardingsphere.sharding.tables.pay_order.key-generator.type=SNOWFLAKE

#指定pay_order表的分片策略,分片策略包括分片键和分片算法
spring.shardingsphere.sharding.tables.pay_order.table-strategy.inline.sharding-column= order_id
spring.shardingsphere.sharding.tables.pay_order.table-strategy.inline.algorithm-expression = pay_order_$->{order_id % 2 + 1}

# 打开sql输出日志,不写则默认为false
#基本只会操作Sharding-Jdbc的日志(定义的名称,基本只有Sharding-Jdbc可以定义名称)
#而不会操作普通的数据源的日志
spring.shardingsphere.props.sql.show = true
编写程序 :
新增订单:
在dao层(包)下创建PayOrderDao接口:
package com.lagou.dao;

import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Component;

/**
 *
 */
//@Component 没有用,因为接口成不了实现类,虽然并不会报错,但是注入会报错,因为没有容器
@Mapper
//扫描,这样就不用@MapperScan扫描包下面的所有接口了,虽然也可以使用
//这里需要提一下,虽然他会扫描子包,但最好指定具体位置
//因为扫描多余的接口(只能是接口,因为是操作接口的,否则会报错,即得不到容器),占用资源
public interface PayOrderDao {

    /**
     * 新增订单,操作的表名与逻辑表名相同,那么就是操作逻辑表,而不是实际表(地址那里的表)
     * 在添加里面的字段,并不能无视关键字,只是count并不是真的关键字,所以可以操作
     * */
    @Insert("insert into pay_order(user_id,product_name,COUNT) values(#{user_id},#{product_name},#{count})") 
    
    int insertPayOrder(@Param("user_id") int user_id, @Param("product_name") String product_name, @Param("count") int count);

}

测试类PayOrderDaoTest(项目其他的代码注释或者删掉即可):
package com.lagou.dao;

import com.lagou.RunBoot;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = RunBoot.class) //@SpringBootTest指定一个启动类来操作,因为可能有多个启动类
//而有多个启动类的情况下,如果不指定,则直接的写@SpringBootTest,那么执行代码时,会报错,但我们指定就不会了
//即@SpringBootTest只能操作一个启动类
//因为@SpringBootApplication注解,所以知道有几个启动类,这是Sprong Boot的方式,这里就不做说明了
public class PayOrderDaoTest {

    @Autowired
    PayOrderDao payOrderDao;

    @Test
    public void testInsertPayOrder(){

        payOrderDao.insertPayOrder(101,"华为手机",10);
       

    }
}

执行后,发现,第二个表有数据了,为什么不是第一个,因为雪花算法
每次第一个操作雪花算法的值,基本是单数,所以也基本只能操作第二个表了
你可以改变对应的主键,来使得更改操作对象
那么有个问题:
为什么上面的分片策略和主键生成策略需要都存在,才可操作逻辑表呢
因为分片策略需要他指定的字段,在前面的配置中
因为该字段是与分片的字段相同,所以他们需要一起(这里要注意,即前面的解释是有这个前提条件的)
如果你没有值,我们怎么操作分片呢,所以如果我们自己添加对应的字段来实现,导致自动生成主键没有操作
那么他自动生成主键的配置就可以删除(不删除又不会操作),因为有字段了,自然可以操作分片
所以这时删除任然可以操作逻辑表,即如果分片的字段没有设置值或者不操作(删除其配置就是不操作),那么就不会操作逻辑表
但他们自动生成的配置一起的还是一起的,如果不一起还是会报错,这是配置的问题,与是否操作无关
至此初步的介绍完毕
最后注意:对应的分片的字段,最好是数字,由于他取得的是我们字段的值,在给sql之前
自然是取得我们输入的类型变量的值(传递的变量值),来进行操作
所以需要整型,否则执行也会报错,你可以在方法那里修改类型就知道了
总结:
Sharding-Jdbc通过一系列的配置,在你发送sql到数据库之前,进行拦截解析修改(在程序里的,而不是在中间件里面操作)
自然也会注意表名,而通过判断是否逻辑表,若不是,则放过去执行,否则,则操作自动生成
如果没有字段,且是添加语句,那么就操作自动生成,否则不操作自动生成
操作自动生成会将操作的自动生成的值给该字段位置(sql语句的字段位置,需要用来执行的,操作的算法得到的值)
操作完后,然后再取得操作分片的字段
这时也会判断,如果指定的分片键,即字段不存在,那么不会替换,相当于没有操作逻辑表了
如果存在,那么就会经过计算,得到表名,并覆盖或者替换原来的表名
至此从而形成了一个新的sql,然后放过去执行,以此类推,每次的执行都是如此的操作,这就是配置文件的大致说明(我的理解)
由于雪花算法,每次第一个操作雪花算法的值,基本是单数,所以我们操作多次的执行
即多次的操作雪花算法,使得得到的值不一直是单数
接下来我们修改testInsertPayOrder方法:
 @Test
    public void testInsertPayOrder(){

        for(int i = 0; i<10;i++) {


            payOrderDao.insertPayOrder(100, "华为手机", 10);
        }


    }
至此可以发现,得到的值不会是单数了,即两个表都进行了操作添加数据,且基本每个表是5个
如果设置为9,那么第二个表是5个,第一个表是4个,即雪花算法还是有一定的规律的,单数个基本是单数,双数个基本是双数
即雪花算法在运行的程序中,还是有内置的计数个数的,且也基本保持自增(根据表以及他本身或者百度来得到的信息)
可以看到奇数个正好小于对应的加1的偶数个的操作的自动添加的值,即的确是自增的,虽然他操作了分片不在一个表中
至此我们完成了水平分表了
根据Id查询订单:
在PayOrderDao接口里加上如下:
/**
     * 查询订单
     * 通过注解,自然是相当于操作xml里面的,但也只是相当于,他是有自己的写法的,而没有xml的写法
     * 如果需要xml的写法
     * 需要加上<script>标签,所以这里面的操作可以写上xml的写法了
     * */

  @Select({"<script>" +
          "select " +
           " * " +
          " from pay_order p" +
          " where p.order_id in " +
          "<foreach collection='orderIds' item='id' open='(' separator=',' close=')'>" +
          " #{id} " +
          "</foreach>"+
          "</script>"})
  List<Map> findOrderByIds(@Param("orderIds") List<Long> orderIds);

在测试类PayOrderDaoTest里加上如下:
  @Test
    public void testFindOrderByIds(){

        List<Long> ids = new ArrayList<>();
        ids.add(786542047438831616L);
        ids.add(786542046918737921L);
        //添加两个id,分别是两个表的其中一个

        List<Map> mapList = payOrderDao.findOrderByIds(ids);
        System.out.println(mapList);
        //返回得到的结果,他是合并的,上面的值只是in里面的值,实际上查询时,会根据里面的值
        //来区分表名
        //这样的区分有很多种情况,只要能够操作多个该字段的,无论增删改查
        //基本都可以操作多个表(有的情况下,比如values(2,2,2,2),(1,2,2,2),使得增加操作两个表)
        //但使用上面的情况,可能会出现一个问题,没有定义的字段,那么对应的配置使用谁呢
        //实际上如果没有定义字段,会将逻辑表当成真的表,来从数据源获取该表的所有字段
        //依次放在里面(必然添加操作要指定的字段名)
        //即如果没有该表,那么会报错,所以这里就是操作字段的来源,但也要知道,我们只是取得字段
        //执行时,如果分片的表字段不对应或者语句不对应也会报错的
        //当然,也自然不要违背语句,否则执行必然不成功的
        //这是sql的执行原因,直接在sql那里报错了,自然程序就会报错
        //但通常只有查询可以操作多个表,而增删改基本操作不了(或者很难操作)
        //通常区分的表名,一般以小的结果为主(取前面的计算方式,{order_id % 2 +1})
        //然后排序(比如是pay_order_1,pay_order_2)
        //得到的结果若相同,那么一般是操作丢弃或者覆盖
        //自然也就是pay_order_1先执行了
        //即依次替换(小的开始)这些表名(这两个表名)执行sql语句(而不是划分in里面的值,看日志就知道了)
        //即没有sql语句的in里面的值都是上面两个,而不会划分是哪个表的
        //最后将所以得到的结果合并,并返回给我们
        //我的测试是这样
        
        //通过我对日志的发现,实际上是表的执行顺序导致的list集合顺序的
        //他会先执行第一个表(pay_order_1,小的开始)
        //自然是第一个表的数据在前,当然对于一个表,自然是小的在前(因为表的主键存在,所以是升序的)
        
        //你可以将上面的786542046918737921添加到第一个表中就知道了
        //添加后记得刷新一遍
        //可能你添加的值还没有完全的转换(因为超出了转换之前的限制,这是sql的原样,不需要理会)
    }
可以看到,他也操作了逻辑表及其对应的操作,因为我们有Sharding-Jdbc进行拦截修改
当然,因为不是添加,自然不会操作自动生成(自动生成一般他只会操作添加语句)
上面我大致的说明了ShardingJDBC的执行步骤,现在具体说明一下
ShardingJDBC执行流程:
当ShardingJDBC接收到发送的SQL之后,会执行下面的步骤,最终返回执行结果

在这里插入图片描述

1:SQL解析:编写SQL查询的是逻辑表,执行时 ShardingJDBC 要解析SQL,解析的目的是为了找到需 要改写的位置
2:SQL路由:SQL的路由是指将对逻辑表的操作,映射到对应的数据节点的过程
ShardingJDBC会获取分片键判断是否正确,正确就执行分片策略(算法)来找到真实的表
3:SQL改写:程序员面向的是逻辑表编写SQL,并不能直接在真实的数据库中执行
SQL改写用于将逻辑SQL改为在真实的数据库中可以正确执行的SQL
4:SQL执行:通过配置规则 pay_order_$->{order_id % 2 + 1}
可以知道当 order_id 为偶数时,应该向 pay_order_1表中插入数据,为奇数时向 pay_order_2表插入数据
5:将所有真正执行sql的结果进行汇总合并,然后返回
通常查询是汇总的,取对应的所有表,比如in里面的值,而增删改基本都是选择一个
Sharding-JDBC分库分表:
水平分表:
把一张表的数据按照一定规则,分配到同一个数据库的多张表中
每个表只有这个表的部分数据,在Sharding-JDBC入门使用中,我们已经完成了水平分表的操作
水平分库:
水平分库是把同一个表的数据按一定规则拆到不同的数据库中,每个库可以放在不同的服务器上
接 下来看一下如何使用Sharding-JDBC实现水平分库
首先在前面,我们创建两个数据库,这里我们操作同一个主机,分别是lg_order_1,lg_order_2,与上面的数据相同
分片规则配置:
现在是两个数据库,所以要配置两份数据源信息
# 定义多个数据源,当定义多个时,如果没有操作逻辑,那么一般他会给每个数据源都进行操作
#也就是说,在没有操作逻辑表的实际表,实际上也是操作所有的数据源
#所以如果其中一个数据源没有能执行该sql的,那么会报错,当然,并不是分开执行
#即他们是先确定是否有,才会执行,当确定没有,就会报错

spring.shardingsphere.datasource.names = db1,db2

spring.shardingsphere.datasource.db1.type = com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.db1.driver-class-name = com.mysql.jdbc.Driver
spring.shardingsphere.datasource.db1.url = jdbc:mysql://localhost:3306/lg_order_1?characterEncoding=UTF-8&useSSL=false
spring.shardingsphere.datasource.db1.username = root
spring.shardingsphere.datasource.db1.password = 123456

spring.shardingsphere.datasource.db2.type = com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.db2.driver-class-name = com.mysql.jdbc.Driver
spring.shardingsphere.datasource.db2.url = jdbc:mysql://localhost:3306/lg_order_2?characterEncoding=UTF-8&useSSL=false
spring.shardingsphere.datasource.db2.username = root
spring.shardingsphere.datasource.db2.password = 123456
通过配置对数据库的分片策略,来指定数据库进行操作:
# 分库策略,以user_id为分片键,分片策略为user_id % 2 + 1,user_id为偶数操作db1数据源,否则操作db2
spring.shardingsphere.sharding.tables.pay_order.database-strategy.inline.sharding-column = user_id
#该user_id也是忽略大小写的,也是判断是否有该字段,通常操作我们的定义的
#由于我们语句是定义好的,自然可以在操作连接之前来判断是否存在,并操作其值(定义的),类似于与前面的分片键

#由于当指定多个时,那么他就需要指定其中一个了,因为没有多个之前
#他默认操作指定的那一个,所以操作多个,需要下面的依赖,来确定使用或者说指定谁,否则的话,默认操作多个
#实际上当考虑多个数据源时,如果不指定,通常他们都进行操作,如果出现了会报错的注释解释,那么一般是结合当前代码的,这里要注意
#实际上是操作全部,而不是会使得报错
spring.shardingsphere.sharding.tables.pay_order.database-strategy.inline.algorithm-expression = db$->{user_id % 2 + 1}
#使用的也是上面一个,不匹配也会报错,这是计算的原因,当然你可以不使用,直接指定库(虽然上面的user_id没有操作,但也需要写上,因为是一起的,即操作了需要确定是否存在该字段的操作)

#上面两个也是一起的,且逻辑表也要相同,否则报错(基本都是启动报错,也可以说是执行报错)
#真正的执行报错(即前面的执行报错只是启动报错的称呼),基本只会出现程序的问题
#当然,如果数据源名称不存在,也会报错(这里就是真正的执行报错,因为只有在执行时
#他才会操作数据源,而该数据源并不存在
#即操作初始化的和我们使用的,分别是启动报错(执行报错),和真正的执行报错
#以后为了不必要的解释,我们统一认为报错

#之前的是:
spring.shardingsphere.sharding.tables.pay_order.table-strategy.inline.algorithm-expression = pay_order_$->{order_id % 2 +1}

#会发现有区别的:
#spring.shardingsphere.sharding.tables.pay_order.database-strategy
#spring.shardingsphere.sharding.tables.pay_order.table-strategy
#一个是database-strategy,一个是table-strategy,前面操作数据源,后面操作分片(即操作表)
分库分表的策略:
分库策略,目的是将一个逻辑表,映射到多个数据源:
# 分库找的是数据库 db$->{user_id % 2 + 1}
spring.shardingsphere.sharding.tables.逻辑表名称.database-strategy.分片策略.分片策略属性名 = 分片策略表达式
分表策略,如何将一个逻辑表,映射为多个实际表:
#分表 找的是具体的表 pay_order_$->{order_id % 2 + 1}
spring.shardingsphere.sharding.tables.逻辑表名称.table-strategy.分片策略.algorithm-expression = 分片策略表达式
Sharding-JDBC支持以下几种分片策略(这里了解即可):
standard:标准分片策略
complex:符合分片策略
inline:行表达式分片策略,使用Groovy的表达式,通常这个使用的最多(前面我们就使用了)
hint:Hint分片策略,对应HintShardingStrategy
none:不分片策略,对应NoneShardingStrategy,不分片的策略
具体信息请查阅官方文档:https://shardingsphere.apache.org
插入测试:
修改测试类PayOrderDaoTest的testInsertPayOrder方法:
@Test
    public void testInsertPayOrder() {

        for (int i = 0; i < 10; i++) {
            payOrderDao.insertPayOrder(1, "华为手机", 9);
            //你可以测试这个, payOrderDao.insertPayOrder(100+i, "华为手机", 9);
            //查看结果,可以发现会在db1和db2中都有操作
            //由于雪花算法是先奇数后偶数,那么在db1中,给的都是奇数,db2中,给的都是偶数
            //即db1的pay_order_2中,db2的pay_order_1中
        }
    }
查看结果,发现,在db2的地址数据源里面操作(不看上面注释的话)
即先找数据源地址,然后找表
查询测试:
测试类PayOrderDaoTest的testFindOrderByIds方法:
@Test
    public void testFindOrderByIds() {

        List<Long> ids = new ArrayList<>();
        ids.add(786694216653733889L);
        ids.add(786694217253519360L);
        //各自的数据库中的一个表的order_id数据


        List<Map> mapList = payOrderDao.findOrderByIds(ids);
        System.out.println(mapList);
    }
在之前的说明中,他会先计算出所有的表,然后依次的查询
但是那是因为只有一个数据源(那么默认操作这个数据源,可以不用指定)
如果是多个数据源,那么除了表外的统计,还会操作数据源的统计,当然,这个数据源的统计并不是计算的
而是本来就有的(当然也会操作分库策略,只是这里的字段操作不同,所以自然分库策略是不起作用的,所以就默认是全部了)
所以他add一个值,他也会循环一次,首先选择名称在前的,然后操作完所有的表后,再操作后面的
即由于是db1,db2,那么是先操作db1,然后是db2,如果是db2,db1,那么就是先操作db2,然后是db1
那么如何只操作一个数据源呢,接下来就需要我们之前没有使用的配置了:
#配置数据节点,指定节点的信息,通常只是用来操作查询的
spring.shardingsphere.sharding.tables.pay_order.actual-data-nodes = db1.pay_order_$->{1..2}
#可以这样db$->{1..2}.pay_order_$->{1..2},前面的可以使得操作db1和db2
#后面的因为分片策略的原因,所以基本是不会起作用的,你可以试着将分片策略的操作注释,来操作实际表
#但是因为这里的存在,会发现,他操作了对应的上面指定的表,即上面的可以说是默认的替换表名
#在分片策略,自动生成之前,在分库策略之前(不是分片策略,而是分库策略)
#他基本操作分库策略的替换,分库策略一般覆盖他,所以如果分库策略没有进行操作的话,就使用他的范围进行
#即分库策略为主,如果分库策略没有操作,自然操作的结果是这里的
#前面说过,分片策略的字段不存在,就会操作实际表,而不进行替换,但是如果上面有了,不进行替换,那么就是操作上面的
#这并没有什么问题,即他只是不替换而已,并不是变成逻辑表的表名

#所以根据上面的解释,实际上并不是只会操作查询,其他的,如增加也会操作,只是查询使用的多而已
#但他的前提是,如果{}里面的不存在,即没有根据策略路由到表,则不会操作,因为没有表指定,上面只操作db1的表路由
#所以如果没有指定,那么会报错(没有路由到的错误)

#这里要注意一下:分片策略基本只能指定表,指定数据库一般会报错,而分库策略,可以指定表
#但不会识别,而这里都可以指定且可以识别

#简单来说,分库策略是选取库,而这里他的备用
#如果没有指定的库,即分库策略注释或者不操作了
#那么就直接操作他的范围,否则通常代表操作分库策略
当我们给出这个配置,那么只会查询该指定的数据源的两个表(基本只操作查询),他只能指定到表,单纯的写数据源会报错
但通过测试,只要"."后面有数就可以了,指定的表无论是否正确,他反正会查询出来
如果有其他的作用,你可以百度,如果有能力可以阅读源码的话,那么最好阅读源码
因为阅读源码比百度好多了(因为可能你搜不到具体问题)
那么最后一个问题:查询的字段可以随便写吗,答:基本只能是分片键,否则不会操作分片策略了,默认所有的表
但是如果指定的字段是分库的键,那么可能会报错(除非有逻辑表,没有配置操作表名时,会认为是逻辑表)
因为分库的键,基本不能用来操作条件,否则直接的认定为逻辑表
如果有配置,且出现了与前面的解释的"没有路由到的错误"一样,都是因为没有指定表(可以使用分片策略替换)
至此,我们在不同的数据库里面,操作了水平分库
垂直分库 :
垂直分库是指按照业务将表进行分类,分布到不同的数据库上面,每个库可以放在不同的服务器上, 它的核心理念是专库专用
在使用微服务架构时,业务切割得足够独立,数据也会按照业务切分,保证业务数据隔离,大大提升 了数据库的吞吐能力
创建数据库:
CREATE DATABASE lg_user CHARACTER SET 'utf8';
USE lg_user;
DROP TABLE IF EXISTS users;

CREATE TABLE users (
 id BIGINT(20) PRIMARY KEY,
 username VARCHAR(20),
 phone VARCHAR(11),
 STATUS VARCHAR(11)
);
规则配置:
配置数据源信息:
#其中的db1和db2在前面有配置了,就不加上了
spring.shardingsphere.datasource.names = db1,db2,db3 
spring.shardingsphere.datasource.db3.type = com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.db3.driver-class-name = com.mysql.jdbc.Driver
spring.shardingsphere.datasource.db3.url = jdbc:mysql://localhost:3306/lg_user?characterEncoding=UTF-8&useSSL=false
spring.shardingsphere.datasource.db3.username = root
spring.shardingsphere.datasource.db3.password = 123456
配置数据节点(修改或者添加对应的配置,因为逻辑表不同,所以只要操作的逻辑表名不同,配置之间就不会互相影响):
spring.shardingsphere.sharding.tables.users.actual-data-nodes = db$->{3}.users
spring.shardingsphere.sharding.tables.users.table-strategy.inline.sharding-column = id
spring.shardingsphere.sharding.tables.users.table-strategy.inline.algorithm-expression = users

测试插入与查询:
在dao包下创建UsersDao接口:
package com.lagou.dao;

import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

/**
 *
 */
@Mapper
public interface UsersDao {
    /**
     * 新增用户
     * */
    @Insert("INSERT INTO users(id, username,phone,status) VALUE(#{id},#{username},#{phone},#{status})")
    int insertUser(@Param("id")Long id, @Param("username")String username, @Param("phone")String phone, @Param("status")String status);

}

测试类UserDaoTest:
package com.lagou.dao;

import com.lagou.RunBoot;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = RunBoot.class)
public class UserDaoTest {

    @Autowired
    UsersDao usersDao;

    @Test
    public void testInsert(){

        for (int i = 0; i < 10 ; i++) {
            Long id = i + 100L;
            usersDao.insertUser(id,"giao桑"+i,"13511112222", "1");
        }
    }


}

执行后,看看表数据,若有数据,则操作成功
在接口UsersDao里加上如下:
/**
       * 查询用户
       * */
  @Select({"<script>",
          " select",
          " * ",
          " from users u ",
          " where u.id in",
          "<foreach collection='userIds' item='id' open='(' separator=',' close=')'>",
          "#{id}",
          "</foreach>",
          "</script>"
             })
  List<Map> selectUserbyIds(@Param("userIds") List<Long> userIds);

在测试类UserDaoTest里加上如下:
  @Test
    public void testSelect(){

        List<Long> ids = new ArrayList<>();
        ids.add(101L);
        ids.add(102L);

        List<Map> list = usersDao.selectUserbyIds(ids);
        System.out.println(list);
    }
若执行后,有数据则代表操作成功
至此虽然我们手动的创建数据库,但是看起来业务是不同的,所以垂直分库也算操作完毕
那么修改和删除,为什么没有操作呢,实际上,也差不多的,与查询类似,取得字段来操作
即根据条件来获得字段,这里就不多说了,自己测试就知道了,如果你理解了前面的配置,那么修改和删除,自然游刃有余
我通过测试,的确是根据条件来取字段的,如果你不认同,自己测试即可
我给出的测试方式,在UsersDao接口里加上:
@Delete("DELETE FROM users WHERE id = #{id}")
  void delete(@Param("id") Long id);

  @Update("UPDATE users SET phone = 2 WHERE id =#{id}")
  void update(@Param("id") Long id);
在测试类UserDaoTest里加上:
 @Test
    public void testdelete(){

     usersDao.delete(101L);
    }

    @Test
    public void testupdate(){

        usersDao.update(105L);
    }
修改配置:
#第一种,通过id找表,自然找到了,所以没有报错,而进行了修改或者删除,当然你也可以操作算法来决定去那个表,而不是这里的只有一种,比如users_$->{id % 3}
spring.shardingsphere.sharding.tables.users.table-strategy.inline.sharding-column = id
spring.shardingsphere.sharding.tables.users.table-strategy.inline.algorithm-expression = users

#第二种,通过id找表,可是替换的表不存在,即报错
spring.shardingsphere.sharding.tables.users.table-strategy.inline.sharding-column = id
spring.shardingsphere.sharding.tables.users.table-strategy.inline.algorithm-expression = userss

#第三种,通过phone找表,可是条件里没有该phone字段,即不操作替换,即相当于就是操作逻辑表名(没有其他替换)
#由于逻辑表名是users(如果没有其他的替换,那么就是该逻辑表名users)
#且数据库存在,所以没有报错,而进行了修改或者删除
#修改数据库表名,再次测试,就出现没有users表的错误
spring.shardingsphere.sharding.tables.users.table-strategy.inline.sharding-column = phone
spring.shardingsphere.sharding.tables.users.table-strategy.inline.algorithm-expression = userss

上面就是我测试的部分,你可以试一下
因为垂直分库和垂直分表通常需要我们手动,所以这对代码来说需要改变,你可以自己操作垂直分表
比如创建users1表:
CREATE TABLE users1(
id BIGINT(20),
phone VARCHAR(11)
);

INSERT INTO users1 VALUES(101,1),(103,1);
修改配置:
spring.shardingsphere.sharding.tables.users.table-strategy.inline.sharding-column = id
spring.shardingsphere.sharding.tables.users.table-strategy.inline.algorithm-expression = users1
这样就是一个小的垂直分表的操作了,然后操作修改和删除即可
Sharding-JDBC 操作公共表:
什么是公共表:
公共表属于系统中数据量较小,变动少,而且属于高频联合查询的依赖表,参数表、数据字典表等属 于此类型
可以将这类表在每个数据库都保存一份,所有更新操作都同时发送到所有分库执行
接下来看一下如 何使用Sharding-JDBC实现公共表的数据维护

在这里插入图片描述

公共表配置与测试:
创建数据库:
分别在 lg_order_1,lg_order_2,lg_user都创建 district表
-- 区域表
CREATE TABLE district (
 id BIGINT(20) PRIMARY KEY COMMENT '区域ID',
 district_name VARCHAR(100) COMMENT '区域名称',
 LEVEL INT COMMENT '等级'
);

在Sharding-JDBC的配置文件中 指定公共表:
# 指定district为公共表
spring.shardingsphere.sharding.broadcast-tables=district
#但是我们也知道,如果我们都不操作,那么实际上也是指定所有的,那么公共表的作用是什么能
#答:使得其他关于他的配置不起作用,即他只会操作公共表,而不会操作策略,所以加上这个是为了以防万一
# 主键生成策略
spring.shardingsphere.sharding.tables.district.key-generator.column=id
spring.shardingsphere.sharding.tables.district.key-generator.type=SNOWFLAKE

编写代码,操作公共表:
在dao包下创建DistrictDao接口:
package com.lagou.dao;

import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

/**
 *
 */
@Mapper
public interface DistrictDao {

    /**
     * 插入操作
     */
    @Insert("INSERT INTO district(district_name,level) VALUES(#{district_name},#{level})")
    public void insertDist(@Param("district_name") String district_name,@Param("level") int level);

    /**
     * 删除数据
     */
    @Delete("delete from district where id = #{id}")
    int deleteDict(@Param("id") Long id);

}

创建测试类DistrictDaoTest:
package com.lagou.dao;

import com.lagou.RunBoot;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

/**
 *
 */
@RunWith(SpringRunner.class)
@SpringBootTest(classes = RunBoot.class)
public class DistrictDaoTest {
    @Autowired
    DistrictDao districtDao;

    @Test
    public void testInsert(){
      districtDao.insertDist("昌平区",2);
      districtDao.insertDist("朝阳区",2);
    }

    @Test
    public void testDelete(){
        districtDao.deleteDict(786962229684600833L);
    }

}


执行后会发现,三个这个district都有数据了
表需要存在,若有一个不存在,就会报错
但由于sql语句他们的执行是分开的
这里基本如此,其他操作的策略也基本如此,但是如果是操作程序,那么看分开的报错的,自然就看程序先后了
所以虽然报错,但能执行的自然会有数据
查询虽然类似,但是因为查询需要合并结果,所以只要有一个报错,那么结果就不会返回,直接是错误信息
那么为什么是三个呢:
很明显,当我们设置了spring.shardingsphere.sharding.broadcast-tables=district
那么他就会在所有的数据源里进行执行,也就是说,如果你操作的逻辑表是district,那么就是相当于在所有的数据源里操作
他并不会操作其他策略,基本只有自动生成才会操作,即其他的配置对他无影响(无论是否指定了他这个逻辑表)
Sharding-JDBC读写分离:
Sharding-JDBC读写分离则是根据SQL语义的分析,将读操作和写操作分别路由至主库与从库
它提供透明化读写分离,让使用方尽量像使用一个数据库一样使用主从数据库集群

在这里插入图片描述

MySQL主从同步 :
为了实现Sharding-JDBC的读写分离,首先,要进行mysql的主从同步配置
我们直接使用上一章(这里是95,那么上一章自然是94)的主从复制的数据库
在主服务器中的 test数据库 创建商品表:
CREATE TABLE products (
 pid BIGINT(32) PRIMARY KEY ,
 pname VARCHAR(50) DEFAULT NULL,
 price INT(11) DEFAULT NULL,
 flag VARCHAR(2) DEFAULT NULL
);
现在,我问个问题,操作了同步,那么一定是查询的结果是正确的吗
答:并不是,因为同步也需要时间,如果在这个时间里面查询,那么自然并不是正确的数据
只是我们操作了同步,不需要我们手动的同步了,这是主要的原因
所以整体来说,只要你再次的查询,那么结果就会是正确的(因为是自动的同步),而不是一直不正确(没有手动的同步)
sharding-jdbc实现读写分离 :
配置数据源:
# 定义多个数据源

# db1,db2,db3是之前的,可以不用变
spring.shardingsphere.datasource.names = db1,db2,db3,m1,s1

spring.shardingsphere.datasource.m1.type = com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.m1.driver-class-name = com.mysql.jdbc.Driver
spring.shardingsphere.datasource.m1.url = jdbc:mysql://192.168.164.128:3306/test?characterEncoding=UTF-8&useSSL=false
spring.shardingsphere.datasource.m1.username = root
spring.shardingsphere.datasource.m1.password = QiDian@666

spring.shardingsphere.datasource.s1.type = com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.s1.driver-class-name = com.mysql.jdbc.Driver
spring.shardingsphere.datasource.s1.url = jdbc:mysql://192.168.164.129:3306/test?characterEncoding=UTF-8&useSSL=false
spring.shardingsphere.datasource.s1.username = root
spring.shardingsphere.datasource.s1.password = QiDian@666
配置主库与从库的相关信息:
ms1 包含了 m1 和 s1:
spring.shardingsphere.sharding.master-slave-rules.ms1.master-data-source-name=m1
spring.shardingsphere.sharding.master-slave-rules.ms1.slave-data-source-names=s1
#使用ms1代表存放了主库和从库的消息,必须指定一起,否则报错,其中从库可以指定多个,如s1,s2(逗号两边可以加空格)
#但是主库不能,否则报错

#如果他们两个不是主从库,那么,基本也只会添加主库,那么为什么必须需要加上从库配置呢
#这是因为查询时,只会操作从库,而不会操作主库,简单来说增删改操作m1,查询操作s1,即他们用来给与操作的指向
#相当于ms1自带了一个判断,即只显示谁,虽然他在我们没有配置的地方操作了表替换
#所以Sharding-JDBC也就规定了必须一起

#但也正是因为他们规定了数据源的操作,所以操作公共表的写操作时
#如果主从复制的从库在这里指定,那么基本上主从复制的从库不会操作
#因为这里规定了数据源的分工的原因,所以从库不会操作写操作(写操作:增删改)
#所以对应的数据也不会冲突,即不会变成no

配置数据节点:
#配置数据节点,定义了ms1,那么操作时,相当于操作m1和s1,因为除了这里可以使用{1..2}外
#其他的地方不能使用,因为其他的分库或者分配策略基本只能指定一个,但是使用{1..2}这样类似的
#却只能是一个顺序,而不可以是{m..s},他也只能够操作一个顺序,即1到2之间可以过去(筛选)
#所以想要指定s和m,那么需要前面的配置了(其他的方式基本没有,即基本是用来操作主从复制的)
#所以这里使用ms1,相当于操作了m1和s1
spring.shardingsphere.sharding.tables.products.actual-data-nodes = ms1.products

#这个可以删除,反正只是用来筛选的

#当然,因为实际上可能是操作多个数据源,所以有时候这个ms1也是有作用的,比如
#spring.shardingsphere.sharding.tables.novel.database-strategy.inline.algorithm-expression = ms1
#即该实际表也只能操作上面的两个数据源了,且操作了主从

编写测试代码:
在dao包下创建ProductsDao接口:
package com.lagou.dao;

import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;

import java.util.List;
import java.util.Map;

/**
 *
 */
@Mapper
public interface ProductsDao {

    /**
      * 读写分离 插入
      * */
    @Insert("insert into products(pid,pname,price,flag) values(#{pid},#{pname},#{price},#{flag})")
    int insertProduct(@Param("pid") Long pid, @Param("pname") String pname,@Param("price") int price,@Param("flag") String flag);

   /**
     * 读写分离 查询
     * */
    @Select({"select * from products"})
    List<Map> findAll();


    @Select({"delete from products where pid = #{pid}"})
    void delete(@Param("pid") Long pid);

    @Select({"update products set pname = 1 where pid = #{pid}"})
    void update(@Param("pid") Long pid);

}

创建测试类ProductsDaoTest:
package com.lagou.dao;

import com.lagou.RunBoot;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

/**
 *
 */
@RunWith(SpringRunner.class)
@SpringBootTest(classes = RunBoot.class)
public class ProductsDaoTest {

    @Autowired
    ProductsDao productsDao;

    /**
      * 测试插入
      * */
    @Test
    public void testInsert(){
      for (int i = 0; i < 5; i++) {
        productsDao.insertProduct(100L+i,"小米手机",1888,"1");
      }
    }

      @Test
    public void testdelete(){
      productsDao.delete(102L);

    }

    @Test
    public void testupdate(){
     productsDao.update(102L);
    }


}

执行,若执行后,同步了,则操作成功
最后的删除和修改,他是操作主还是从呢,根据理论来说,他操作主
但实际情况上,会有报错,不需要理会,实际上还是删除或者修改的
至此,我们操作读写分离完毕
  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
分库分表是一种常用的数据库架构设计方法,用于解决数据量过大、读写压力过大等问题。在分库分表的架构中,数据被分散存储在多个数据库或表中,通过某种策略将数据均匀地划分到不同的库和表中,从而提高数据库的读写性能和扩展性。 ShardingJDBC是一个开源的Java分库分表中间件,它提供了对分库分表的支持。使用ShardingJDBC可以轻松地实现数据库的水平拆分,将数据分布到不同的数据库和表中。ShardingJDBC通过在应用层面的透明化处理,将SQL语句中的表名和字段名转换为真正的物理表名和字段名,从而实现对分库分表的透明访问。 在使用ShardingJDBC进行分库分表时,你可以指定分库分表策略,包括数据节点、分片策略和路由策略等。例如,可以通过配置文件指定每个库中的表的数据节点、分片列和分片算法等信息。此外,你还可以通过配置文件设置公共表,使多个库中的多个表都能共享同一个公共表的数据。 总结来说,ShardingJDBC是一个强大的分库分表组件,它能够帮助我们实现数据库的水平拆分,并通过透明化处理,让应用程序能够无感知地访问分库分表的数据。使用ShardingJDBC可以提高数据库的性能和扩展性,是分布式系统中常用的解决方案之一。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [Sharding-JDBC实现分库分表](https://blog.csdn.net/u014494148/article/details/127797808)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [分库分表Sharding-JDBC](https://blog.csdn.net/huxiang19851114/article/details/114880080)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值