javaer你还在手写分表分库?来看看这个框架怎么做的 干货满满

java orm框架easy-query分库分表之分表

高并发三驾马车:分库分表、MQ、缓存。今天给大家带来的就是分库分表的干货解决方案,哪怕你不用我的框架也可以从中听到不一样的结局方案和实现。

一款支持自动分表分库的orm框架easy-query 帮助您解脱跨库带来的复杂业务代码,并且提供多种结局方案和自定义路由来实现比中间件更高性能的数据库访问。

  • GITHUB github地址

  • GITEE gitee地址

目前市面上有的分库分表JAVA组件有很多:中间件代理有:sharding-sphere(proxy),mycat 客户端JDBC:sharding-sphere(jdbc)等等,中间件因为代理了一层会导致所有的sql执行都要经过中间件,性能会大大折扣,但是因为中间部署可以提供更加省的连接池,客户端无需代理,仅需对sql进行分析即可实现,但是越靠近客户的模式可以优化的性能越高,所以本次带来的框架可以提供前所未有的分片规则自由和前所未有的便捷高性能。

本文 demo地址 http://github.com/xuejmnet/easy-sharding-test

怎么样的orm算是支持分表分库

首先orm是否支持分表分库不仅仅是看框架是否支持动态修改表名,让数据正确存入对应的表或者修改对应的数据,这些说实话都是最最简单的实现,真正需要支持分库分表那么需要orm实现复杂的跨表聚合查询,这才是分表分库的精髓,很显然目前的orm很少有支持的。接下来我将给大家演示基于springboot3.x的分表分库演示,取模分片和时间分片。本章我们主要以使用为主后面下一章我们来讲解优化方案,包括原理解析,后续有更多的关于分表分库的经验是博主多年下来的实战经验分享给大家保证大家的happy coding。

初始化项目

进入 http://start.spring.io/ 官网直接下载

安装依赖


		<!-- http://mvnrepository.com/artifact/com.alibaba/druid -->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid</artifactId>
			<version>1.2.15</version>
		</dependency>
		<!-- mysql驱动 -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>8.0.17</version>
		</dependency>
		<dependency>
			<groupId>com.easy-query</groupId>
			<artifactId>sql-springboot-starter</artifactId>
			<version>0.9.7</version>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<version>1.18.18</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

application.yml配置

server:
  port: 8080

spring:

  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/easy-sharding-test?serverTimezone=GMT%2B8&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true&rewriteBatchedStatements=true
    username: root
    password: root

logging:
  level:
    com.easy.query.core: debug

easy-query:
  enable: true
  name-conversion: underlined
  database: mysql

取模

常见的分片方式之一就是取模分片,取模分片可以让以分片键为条件的处理完美路由到对应的表,性能上来说非常非常高,但是局限性也是很大的因为无意义的id路由会导致仅支持这一个id条件而不支持其他条件的路由,只能全分片表扫描来获取对应的数据,但是他的实现和理解也是最容易的,当然后续还有基因分片一种可以部分解决仅支持id带来的问题不过也并不是非常的完美。

简单的取模分片

我们本次测试案例采用order表对其进行5表拆分:order_00,order_01,order_02,order_03,order_04,采用订单id取模进行分表
数据库脚本

CREATE DATABASE IF NOT EXISTS `easy-sharding-test` CHARACTER SET 'utf8mb4';
USE `easy-sharding-test`;
create table order_00
(
    id varchar(32) not null comment '主键ID'primary key,
    uid varchar(50) not null comment '用户id',
    order_no int null comment '订单号'
)comment '订单表';
create table order_01
(
    id varchar(32) not null comment '主键ID'primary key,
    uid varchar(50) not null comment '用户id',
    order_no int null comment '订单号'
)comment '订单表';
create table order_02
(
    id varchar(32) not null comment '主键ID'primary key,
    uid varchar(50) not null comment '用户id',
    order_no int null comment '订单号'
)comment '订单表';
create table order_03
(
    id varchar(32) not null comment '主键ID'primary key,
    uid varchar(50) not null comment '用户id',
    order_no int null comment '订单号'
)comment '订单表';
create table order_04
(
    id varchar(32) not null comment '主键ID'primary key,
    uid varchar(50) not null comment '用户id',
    order_no int null comment '订单号'
)comment '订单表';
//定义了一个对象并且设置表名和分片初始化器`shardingInitializer`,设置id为主键,并且设置id为分表建
@Data
@Table(value = "order",shardingInitializer = OrderShardingInitializer.class)
public class OrderEntity {
    @Column(primaryKey = true)
    @ShardingTableKey
    private String id;
    private String uid;
    private Integer orderNo;
}
//编写订单取模初始化器,只需要实现两个方法,当然你也可以自己实现对应的`EntityShardingInitializer`这边是继承`easy-query`框架提供的分片取模初始化器
@Component
public class OrderShardingInitializer extends AbstractShardingModInitializer<OrderEntity> {
     /**
     * 设置模几我们模5就设置5
     * @return
     */
    @Override
    protected int mod() {
        return 5;
    }

    /**
     * 编写模5后的尾巴长度默认我们设置2就是左补0
     * @return
     */
    @Override
    protected int tailLength() {
        return 2;
    }
}
//编写分片规则`AbstractModTableRule`由框架提供取模分片路由规则,如果需要自己实现可以继承`AbstractTableRouteRule`这个抽象类
@Component
public class OrderTableRouteRule extends AbstractModTableRule<OrderEntity> {
    @Override
    protected int mod() {
        return 5;
    }

    @Override
    protected int tailLength() {
        return 2;
    }
}

初始化工作做好了开始编写代码

新增初始化


@RestController
@RequestMapping("/order")
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class OrderController {

    private final EasyQuery easyQuery;

    @GetMapping("/init")
    public Object init() {
        ArrayList<OrderEntity> orderEntities = new ArrayList<>(100);
        List<String> users = Arrays.asList("xiaoming", "xiaohong", "xiaolan");

        for (int i = 0; i < 100; i++) {
            OrderEntity orderEntity = new OrderEntity();
            orderEntity.setId(String.valueOf(i));
            int i1 = i % 3;
            String uid = users.get(i1);
            orderEntity.setUid(uid);
            orderEntity.setOrderNo(i);
            orderEntities.add(orderEntity);
        }
        long l = easyQuery.insertable(orderEntities).executeRows();
        return "成功插入:"+l;
    }
}

查询单条

按分片键查询

可以完美的路由到对应的数据库表和操作单表拥有一样的性能

    @GetMapping("/first")
    public Object first(@RequestParam("id") String id) {
        OrderEntity orderEntity = easyQuery.queryable(OrderEntity.class)
                .whereById(id).firstOrNull();
        return orderEntity;
    }
http://localhost:8080/order/first?id=20
{"id":"20","uid":"xiaolan","orderNo":20}


http-nio-8080-exec-1, name:ds0, Preparing: SELECT t.`id`,t.`uid`,t.`order_no` FROM `order_03` t WHERE t.`id` = ? LIMIT 1
==> http-nio-8080-exec-1, name:ds0, Parameters: 20(String)
<== Total: 1

日志稍微解释一下

  • http-nio-8080-exec-1表示当前语句执行的线程,默认多个分片聚合后需要再线程池中查询数据后聚合返回。
  • name:ds0 表示数据源叫做ds0,如果不分库那么这个数据源可以忽略,也可以自己指定配置文件中或者设置defaultDataSourceName

全程无需您去计算路由到哪里,并且规则和业务代码已经脱离解耦

不按分片键查询

当我们的查询为非分片键查询那么会导致路由需要进行全分片扫描然后来获取对应的数据进行判断哪个时我们要的


    @GetMapping("/firstByUid")
    public Object firstByUid(@RequestParam("uid") String uid) {
        OrderEntity orderEntity = easyQuery.queryable(OrderEntity.class)
                .where(o->o.eq(OrderEntity::getUid,uid)).firstOrNull();
        return orderEntity;
    }

http://localhost:8080/order/firstByUid?uid=xiaoming
{"id":"18","uid":"xiaoming","orderNo":18}

//这边把日志精简了一下可以看到他是开启了5个线程进行分片查询
==> SHARDING_EXECUTOR_1, name:ds0, Preparing: SELECT t.`id`,t.`uid`,t.`order_no` FROM `order_00` t WHERE t.`uid` = ? LIMIT 1
==> SHARDING_EXECUTOR_5, name:ds0, Preparing: SELECT t.`id`,t.`uid`,t.`order_no` FROM `order_03` t WHERE t.`uid` = ? LIMIT 1
==> SHARDING_EXECUTOR_4, name:ds0, Preparing: SELECT t.`id`,t.`uid`,t.`order_no` FROM `order_04` t WHERE t.`uid` = ? LIMIT 1
==> SHARDING_EXECUTOR_3, name:ds0, Preparing: SELECT t.`id`,t.`uid`,t.`order_no` FROM `order_02` t WHERE t.`uid` = ? LIMIT 1
==> SHARDING_EXECUTOR_2, name:ds0, Preparing: SELECT t.`id`,t.`uid`,t.`order_no` FROM `order_01` t WHERE t.`uid` = ? LIMIT 1
==> SHARDING_EXECUTOR_3, name:ds0, Parameters: xiaoming(String)
==> SHARDING_EXECUTOR_4, name:ds0, Parameters: xiaoming(String)
==> SHARDING_EXECUTOR_5, name:ds0, Parameters: xiaoming(String)
==> SHARDING_EXECUTOR_1, name:ds0, Parameters: xiaoming(String)
==> SHARDING_EXECUTOR_2, name:ds0, Parameters: xiaoming(String)
<== Total: 1

因为uid不是分片键所以在分片查询的时候需要遍历所有的表然后返回对应的数据,可能有同学会问就这?当然这只是简单演示后续下一篇我会给出具体的优化方案来进行处理。

分页查询

分片后的分页查询是分片下的一个难点,这边框架自带功能,分片后分页之所以难是因为如果是自行实现业务代码会变得非常复杂,有一种非常简易的方式就是把分页重写pageIndex永远为1,然后全部取到内存后在进行stream过滤,但是带来的另一个问题就是pageIndex不能便宜过大不然内存会完全存不下导致内存爆炸,并且如果翻页到最后几页那将是灾难性的,给程序带来极其不稳定,但是easy-query提供了和sharding-sphere一样的分片聚合方式并且因为靠近业务的关系所以可以有效的优化深度分页pageIndex过大


    @GetMapping("/page")
    public Object page(@RequestParam("pageIndex") Integer pageIndex,@RequestParam("pageSize") Integer pageSize) {
        EasyPageResult<OrderEntity> pageResult = easyQuery.queryable(OrderEntity.class)
                .orderByAsc(o -> o.column(OrderEntity::getOrderNo))
                .toPageResult(pageIndex, pageSize);
        return pageResult;
    }


http://localhost:8080/order/page?pageIndex=1&pageSize=10

{"total":100,"data":[{"id":"0","uid":"xiaoming","orderNo":0},{"id":"1","uid":"xiaohong","orderNo":1},{"id":"2","uid":"xiaolan","orderNo":2},{"id":"3","uid":"xiaoming","orderNo":3},{"id":"4","uid":"xiaohong","orderNo":4},{"id":"5","uid":"xiaolan","orderNo":5},{"id":"6","uid":"xiaoming","orderNo":6},{"id":"7","uid":"xiaohong","orderNo":7},{"id":"8","uid":"xiaolan","orderNo":8},{"id":"9","uid":"xiaoming","orderNo":9}]}
==> SHARDING_EXECUTOR_3, name:ds0, Preparing: SELECT COUNT(1) FROM `order_02` t
==> SHARDING_EXECUTOR_2, name:ds0, Preparing: SELECT COUNT(1) FROM `order_03` t
==> SHARDING_EXECUTOR_5, name:ds0, Preparing: SELECT COUNT(1) FROM `order_04` t
==> SHARDING_EXECUTOR_1, name:ds0, Preparing: SELECT COUNT(1) FROM `order_01` t
==> SHARDING_EXECUTOR_4, name:ds0, Preparing: SELECT COUNT(1) FROM `order_00` t
<== Total: 1
==> SHARDING_EXECUTOR_5, name:ds0, Preparing: SELECT t.`id`,t.`uid`,t.`order_no` FROM `order_04` t ORDER BY t.`order_no` ASC LIMIT 10
==> SHARDING_EXECUTOR_2, name:ds0, Preparing: SELECT t.`id`,t.`uid`,t.`order_no` FROM `order_03` t ORDER BY t.`order_no` ASC LIMIT 10
==> SHARDING_EXECUTOR_4, name:ds0, Preparing: SELECT t.`id`,t.`uid`,t.`order_no` FROM `order_00` t ORDER BY t.`order_no` ASC LIMIT 10
==> SHARDING_EXECUTOR_1, name:ds0, Preparing: SELECT t.`id`,t.`uid`,t.`order_no` FROM `order_01` t ORDER BY t.`order_no` ASC LIMIT 10
==> SHARDING_EXECUTOR_3, name:ds0, Preparing: SELECT t.`id`,t.`uid`,t.`order_no` FROM `order_02` t ORDER BY t.`order_no` ASC LIMIT 10
<== Total: 10

这边可以看到一行代码实现分页,下面是第二页

http://localhost:8080/order/page?pageIndex=2&pageSize=10
{"total":100,"data":[{"id":"10","uid":"xiaohong","orderNo":10},{"id":"11","uid":"xiaolan","orderNo":11},{"id":"12","uid":"xiaoming","orderNo":12},{"id":"13","uid":"xiaohong","orderNo":13},{"id":"14","uid":"xiaolan","orderNo":14},{"id":"15","uid":"xiaoming","orderNo":15},{"id":"16","uid":"xiaohong","orderNo":16},{"id":"17","uid":"xiaolan","orderNo":17},{"id":"18","uid":"xiaoming","orderNo":18},{"id":"19","uid":"xiaohong","orderNo":19}]}

==> SHARDING_EXECUTOR_9, name:ds0, Preparing: SELECT COUNT(1) FROM `order_02` t
==> SHARDING_EXECUTOR_8, name:ds0, Preparing: SELECT COUNT(1) FROM `order_01` t
==> SHARDING_EXECUTOR_10, name:ds0, Preparing: SELECT COUNT(1) FROM `order_04` t
==> SHARDING_EXECUTOR_7, name:ds0, Preparing: SELECT COUNT(1) FROM `order_03` t
==> SHARDING_EXECUTOR_6, name:ds0, Preparing: SELECT COUNT(1) FROM `order_00` t
<== Total: 1
==> SHARDING_EXECUTOR_9, name:ds0, Preparing: SELECT t.`id`,t.`uid`,t.`order_no` FROM `order_01` t ORDER BY t.`order_no` ASC LIMIT 20
==> SHARDING_EXECUTOR_8, name:ds0, Preparing: SELECT t.`id`,t.`uid`,t.`order_no` FROM `order_03` t ORDER BY t.`order_no` ASC LIMIT 20
==> SHARDING_EXECUTOR_10, name:ds0, Preparing: SELECT t.`id`,t.`uid`,t.`order_no` FROM `order_04` t ORDER BY t.`order_no` ASC LIMIT 20
==> SHARDING_EXECUTOR_6, name:ds0, Preparing: SELECT t.`id`,t.`uid`,t.`order_no` FROM `order_02` t ORDER BY t.`order_no` ASC LIMIT 20
==> SHARDING_EXECUTOR_7, name:ds0, Preparing: SELECT t.`id`,t.`uid`,t.`order_no` FROM `order_00` t ORDER BY t.`order_no` ASC LIMIT 20
<== Total: 10

按时间分表

这边我们简单还是以order订单为例,按月进行分片假设我们从2022年1月到2023年5月一共17个月表名为t_order_202201t_order_202202t_order_202203...t_order_202304t_order_202305

数据库脚本

create table t_order_202201
(
    id varchar(32) not null comment '主键ID'primary key,
    uid varchar(50) not null comment '用户id',
    order_no int not null comment '订单号',
    create_time datetime not null comment '创建时间'
)comment '订单表';
create table t_order_202202
(
    id varchar(32) not null comment '主键ID'primary key,
    uid varchar(50) not null comment '用户id',
    order_no int not null comment '订单号',
    create_time datetime not null comment '创建时间'
)comment '订单表';
....
create table t_order_202304
(
    id varchar(32) not null comment '主键ID'primary key,
    uid varchar(50) not null comment '用户id',
    order_no int not null comment '订单号',
    create_time datetime not null comment '创建时间'
)comment '订单表';
create table t_order_202305
(
    id varchar(32) not null comment '主键ID'primary key,
    uid varchar(50) not null comment '用户id',
    order_no int not null comment '订单号',
    create_time datetime not null comment '创建时间'
)comment '订单表';

@Data
@Table(value = "t_order",shardingInitializer = OrderByMonthShardingInitializer.class)
public class OrderByMonthEntity {

    @Column(primaryKey = true)
    private String id;
    private String uid;
    private Integer orderNo;
    /**
     * 分片键改为时间
     */
    @ShardingTableKey
    private LocalDateTime createTime;
}

//路由规则可以直接继承AbstractShardingMonthInitializer也可以自己实现
@Component
public class OrderByMonthShardingInitializer extends AbstractShardingMonthInitializer<OrderByMonthEntity> {
   /**
     * 开始时间不可以使用LocalDateTime.now()因为会导致每次启动开始时间都不一样
     * @return
     */
    @Override
    protected LocalDateTime getBeginTime() {
        return LocalDateTime.of(2022,1,1,0,0);
    }

    /**
     * 如果不设置那么就是当前时间,用于程序启动后自动计算应该有的表包括最后时间
     * @return
     */
    @Override
    protected LocalDateTime getEndTime() {
        return LocalDateTime.of(2023,5,31,0,0);
    }

    @Override
    public void configure0(ShardingEntityBuilder<OrderByMonthEntity> builder) {
        //后续用来实现优化分表
    }
}
//按月分片路由规则也可以自己实现因为框架已经封装好了所以可以用框架自带的
@Component
public class OrderByMonthTableRouteRule extends AbstractMonthTableRule<OrderByMonthEntity> {
    @Override
    protected LocalDateTime convertLocalDateTime(Object shardingValue) {
        return (LocalDateTime)shardingValue;
    }
}

初始化


@RestController
@RequestMapping("/orderMonth")
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class OrderMonthController {

    private final EasyQuery easyQuery;

    @GetMapping("/init")
    public Object init() {
        ArrayList<OrderByMonthEntity> orderEntities = new ArrayList<>(100);
        List<String> users = Arrays.asList("xiaoming", "xiaohong", "xiaolan");
        LocalDateTime beginTime=LocalDateTime.of(2022,1,1,0,0);
        LocalDateTime endTime=LocalDateTime.of(2023,5,31,0,0);
        int i=0;
        while(!beginTime.isAfter(endTime)){

            OrderByMonthEntity orderEntity = new OrderByMonthEntity();
            orderEntity.setId(String.valueOf(i));
            int i1 = i % 3;
            String uid = users.get(i1);
            orderEntity.setUid(uid);
            orderEntity.setOrderNo(i);
            orderEntity.setCreateTime(beginTime);
            orderEntities.add(orderEntity);
            beginTime=beginTime.plusDays(1);
            i++;
        }
        long l = easyQuery.insertable(orderEntities).executeRows();
        return "成功插入:"+l;
    }
}

http://localhost:8080/orderMonth/init
成功插入:516

获取第一条数据

    @GetMapping("/first")
    public Object first(@RequestParam("id") String id) {
        OrderEntity orderEntity = easyQuery.queryable(OrderEntity.class)
                .whereById(id).firstOrNull();
        return orderEntity;
    }

http://localhost:8080/orderMonth/first?id=11
{"id":"11","uid":"xiaolan","orderNo":11,"createTime":"2022-01-12T00:00:00"}
//以每5组一个次并发执行聚合

==> SHARDING_EXECUTOR_1, name:ds0, Preparing: SELECT t.`id`,t.`uid`,t.`order_no`,t.`create_time` FROM `t_order_202205` t WHERE t.`id` = ? LIMIT 1
==> SHARDING_EXECUTOR_1, name:ds0, Parameters: 11(String)
==> SHARDING_EXECUTOR_2, name:ds0, Preparing: SELECT t.`id`,t.`uid`,t.`order_no`,t.`create_time` FROM `t_order_202207` t WHERE t.`id` = ? LIMIT 1
==> SHARDING_EXECUTOR_2, name:ds0, Parameters: 11(String)
==> SHARDING_EXECUTOR_3, name:ds0, Preparing: SELECT t.`id`,t.`uid`,t.`order_no`,t.`create_time` FROM `t_order_202303` t WHERE t.`id` = ? LIMIT 1
==> SHARDING_EXECUTOR_3, name:ds0, Parameters: 11(String)
==> SHARDING_EXECUTOR_4, name:ds0, Preparing: SELECT t.`id`,t.`uid`,t.`order_no`,t.`create_time` FROM `t_order_202212` t WHERE t.`id` = ? LIMIT 1
==> SHARDING_EXECUTOR_4, name:ds0, Parameters: 11(String)
==> SHARDING_EXECUTOR_5, name:ds0, Preparing: SELECT t.`id`,t.`uid`,t.`order_no`,t.`create_time` FROM `t_order_202302` t WHERE t.`id` = ? LIMIT 1
==> SHARDING_EXECUTOR_5, name:ds0, Parameters: 11(String)
==> SHARDING_EXECUTOR_1, name:ds0, Preparing: SELECT t.`id`,t.`uid`,t.`order_no`,t.`create_time` FROM `t_order_202304` t WHERE t.`id` = ? LIMIT 1
==> SHARDING_EXECUTOR_5, name:ds0, Preparing: SELECT t.`id`,t.`uid`,t.`order_no`,t.`create_time` FROM `t_order_202206` t WHERE t.`id` = ? LIMIT 1
==> SHARDING_EXECUTOR_2, name:ds0, Preparing: SELECT t.`id`,t.`uid`,t.`order_no`,t.`create_time` FROM `t_order_202305` t WHERE t.`id` = ? LIMIT 1
==> SHARDING_EXECUTOR_1, name:ds0, Parameters: 11(String)
==> SHARDING_EXECUTOR_2, name:ds0, Parameters: 11(String)
==> SHARDING_EXECUTOR_4, name:ds0, Preparing: SELECT t.`id`,t.`uid`,t.`order_no`,t.`create_time` FROM `t_order_202209` t WHERE t.`id` = ? LIMIT 1
==> SHARDING_EXECUTOR_3, name:ds0, Preparing: SELECT t.`id`,t.`uid`,t.`order_no`,t.`create_time` FROM `t_order_202204` t WHERE t.`id` = ? LIMIT 1
==> SHARDING_EXECUTOR_5, name:ds0, Parameters: 11(String)
==> SHARDING_EXECUTOR_3, name:ds0, Parameters: 11(String)
==> SHARDING_EXECUTOR_4, name:ds0, Parameters: 11(String)
==> SHARDING_EXECUTOR_2, name:ds0, Preparing: SELECT t.`id`,t.`uid`,t.`order_no`,t.`create_time` FROM `t_order_202208` t WHERE t.`id` = ? LIMIT 1
==> SHARDING_EXECUTOR_5, name:ds0, Preparing: SELECT t.`id`,t.`uid`,t.`order_no`,t.`create_time` FROM `t_order_202201` t WHERE t.`id` = ? LIMIT 1
==> SHARDING_EXECUTOR_3, name:ds0, Preparing: SELECT t.`id`,t.`uid`,t.`order_no`,t.`create_time` FROM `t_order_202210` t WHERE t.`id` = ? LIMIT 1
==> SHARDING_EXECUTOR_5, name:ds0, Parameters: 11(String)
==> SHARDING_EXECUTOR_4, name:ds0, Preparing: SELECT t.`id`,t.`uid`,t.`order_no`,t.`create_time` FROM `t_order_202202` t WHERE t.`id` = ? LIMIT 1
==> SHARDING_EXECUTOR_3, name:ds0, Parameters: 11(String)
==> SHARDING_EXECUTOR_2, name:ds0, Parameters: 11(String)
==> SHARDING_EXECUTOR_4, name:ds0, Parameters: 11(String)
==> SHARDING_EXECUTOR_1, name:ds0, Preparing: SELECT t.`id`,t.`uid`,t.`order_no`,t.`create_time` FROM `t_order_202211` t WHERE t.`id` = ? LIMIT 1
==> SHARDING_EXECUTOR_1, name:ds0, Parameters: 11(String)
==> SHARDING_EXECUTOR_2, name:ds0, Preparing: SELECT t.`id`,t.`uid`,t.`order_no`,t.`create_time` FROM `t_order_202203` t WHERE t.`id` = ? LIMIT 1
==> SHARDING_EXECUTOR_5, name:ds0, Preparing: SELECT t.`id`,t.`uid`,t.`order_no`,t.`create_time` FROM `t_order_202301` t WHERE t.`id` = ? LIMIT 1
==> SHARDING_EXECUTOR_2, name:ds0, Parameters: 11(String)
==> SHARDING_EXECUTOR_5, name:ds0, Parameters: 11(String)
<== Total: 1

获取范围内的数据

    @GetMapping("/range")
    public Object first() {
        List<OrderByMonthEntity> list = easyQuery.queryable(OrderByMonthEntity.class)
                .where(o -> o.rangeClosed(OrderByMonthEntity::getCreateTime, LocalDateTime.of(2022, 3, 1, 0, 0), LocalDateTime.of(2022, 9, 1, 0, 0)))
                .toList();
        return list;
    }
http://localhost:8080/orderMonth/range
[{"id":"181","uid":"xiaohong","orderNo":181,"createTime":"2022-07-01T00:00:00"},{"id":"182","uid":"xiaolan","orderNo":182,"createTime":"2022-07-02T00:00:00"},{"id":"183","uid":"xiaoming","orderNo":183,"createTime":"2022-07-03T00:00:00"},...........,{"id":"239","uid":"xiaolan","orderNo":239,"createTime":"2022-08-28T00:00:00"},{"id":"240","uid":"xiaoming","orderNo":240,"createTime":"2022-08-29T00:00:00"},{"id":"241","uid":"xiaohong","orderNo":241,"createTime":"2022-08-30T00:00:00"},{"id":"242","uid":"xiaolan","orderNo":242,"createTime":"2022-08-31T00:00:00"}]

//可以精准定位到对应的分片路由上获取数据
==> SHARDING_EXECUTOR_1, name:ds0, Preparing: SELECT t.`id`,t.`uid`,t.`order_no`,t.`create_time` FROM `t_order_202207` t WHERE t.`create_time` >= ? AND t.`create_time` <= ?
==> SHARDING_EXECUTOR_5, name:ds0, Preparing: SELECT t.`id`,t.`uid`,t.`order_no`,t.`create_time` FROM `t_order_202209` t WHERE t.`create_time` >= ? AND t.`create_time` <= ?
==> SHARDING_EXECUTOR_2, name:ds0, Preparing: SELECT t.`id`,t.`uid`,t.`order_no`,t.`create_time` FROM `t_order_202206` t WHERE t.`create_time` >= ? AND t.`create_time` <= ?
==> SHARDING_EXECUTOR_4, name:ds0, Preparing: SELECT t.`id`,t.`uid`,t.`order_no`,t.`create_time` FROM `t_order_202203` t WHERE t.`create_time` >= ? AND t.`create_time` <= ?
==> SHARDING_EXECUTOR_3, name:ds0, Preparing: SELECT t.`id`,t.`uid`,t.`order_no`,t.`create_time` FROM `t_order_202205` t WHERE t.`create_time` >= ? AND t.`create_time` <= ?
==> SHARDING_EXECUTOR_4, name:ds0, Parameters: 2022-03-01T00:00(LocalDateTime),2022-09-01T00:00(LocalDateTime)
==> SHARDING_EXECUTOR_3, name:ds0, Parameters: 2022-03-01T00:00(LocalDateTime),2022-09-01T00:00(LocalDateTime)
==> SHARDING_EXECUTOR_2, name:ds0, Parameters: 2022-03-01T00:00(LocalDateTime),2022-09-01T00:00(LocalDateTime)
==> SHARDING_EXECUTOR_5, name:ds0, Parameters: 2022-03-01T00:00(LocalDateTime),2022-09-01T00:00(LocalDateTime)
==> SHARDING_EXECUTOR_1, name:ds0, Parameters: 2022-03-01T00:00(LocalDateTime),2022-09-01T00:00(LocalDateTime)
==> SHARDING_EXECUTOR_4, name:ds0, Preparing: SELECT t.`id`,t.`uid`,t.`order_no`,t.`create_time` FROM `t_order_202208` t WHERE t.`create_time` >= ? AND t.`create_time` <= ?
==> SHARDING_EXECUTOR_2, name:ds0, Preparing: SELECT t.`id`,t.`uid`,t.`order_no`,t.`create_time` FROM `t_order_202204` t WHERE t.`create_time` >= ? AND t.`create_time` <= ?
==> SHARDING_EXECUTOR_4, name:ds0, Parameters: 2022-03-01T00:00(LocalDateTime),2022-09-01T00:00(LocalDateTime)
==> SHARDING_EXECUTOR_2, name:ds0, Parameters: 2022-03-01T00:00(LocalDateTime),2022-09-01T00:00(LocalDateTime)
<== Total: 185

最后

目前为止你已经看到了easy-query对于分片的便捷性,但是本章只是开胃小菜,相信了解分库分表的小伙伴肯定会说就这?不是和sharding-jdbc一样吗为什么要用你的呢。我想说第一篇只是给大家了解一下如何使用,后续的文章才是分表分库的精髓相信我你一定没看过

demo地址 http://github.com/xuejmnet/easy-sharding-test

  • GITHUB github地址

  • GITEE gitee地址

本文转载于网络 如有侵权请联系删除

相关文章

  • 【青山学css】如何用css实现抖音直播评论区透明渐变效果

    今天在看抖音直播的时候,忽然发现一点好玩的东西,那就是抖音直播评论区,评论从半透明到显示,又从显示到逐渐消失的,是如何实现的这个效果突然就引起了我的注意,毕竟我是个又菜又爱玩的css菜鸟,看到好玩的效果就想去实现。于是乎只能到百度和谷歌上到处搜索,但是就是找不到相关的解决方法,不知道是不是我搜的姿势不对还是没有人写过相关文章,无奈之下只能自己进行摸索。方案一:background-clip首先,你看到这个效果的第一步,想到的是什么,是渐变!对不对?我想的也是,css3支持背景渐变,也支持从颜色到透明的渐变,这个几乎每个人都知道,但是好像并没有什么卵用啊,我们需要让文字跟着背景颜色的显示而显示,根据背景颜色的消失而消失,简而言之就是,有背景颜色的地方才有字!这时候是不是一个css属性在你脑子里跃跃欲试?它就是background-clip:text,background-clip的作用正如他字面意思一样——背景裁切,那我们用文字把背景颜色裁切下来不就好了吗?有颜色的地方进行裁切出来还是字,透明的地方裁切出来还是透明的,这不就好了吗?说干就干怎么样,是不是效果差不多,但你如果也试试或者仔细

  • 旋转方向

    importcv2 importnumpyasnp o=cv2.imread('C:/Users/xpp/Desktop/coins.png')#原始图像 cv2.imshow("original",o) gray=cv2.cvtColor(o,cv2.COLOR_BGR2GRAY)#将彩色图片转换为灰度图片 ret,binary=cv2.threshold(gray,127,255,cv2.THRESH_BINARY)#将灰度图片转换为二值图片 contours,hierarchy=cv2.findContours(binary,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)#计算图像轮廓 n=len(contours) contoursImg=[] foriinrange(n): temp=np.zeros(o.shape,np.uint8) contoursImg.append(temp) ellipse=cv2.fitEllipse(contours[i]) retval=cv2.fitEllipse(con

  • AI 与小学生的做题之战,孰胜孰败?

    编译|禾木木出品|AI科技大本营现在小学生的数学题不能用简单来形容,有的时候家长拿到题也需要思考半天,看看是否有其他隐含的解题方法。市面上更是各种拍题搜答案的软件,也是一样的套路,隐含着各种氪金的信息。就像网络上说的“不写作业母慈子孝,一写作业鸡飞狗跳”。近日,OpenAI训练了一个新系统,可以解决小学数学题,大大提升了GPT-3的逻辑推理问题。新系统可以解决小学数学问题,60亿参数的GPT-3采用“新方法”,准确率直接翻倍!甚至追平了拥有1750亿参数,采用微调方法的GPT-3模型。还有一项重要的结果是,一个9-12岁的小学生在测试中得分为60分,采用新方法的GPT-3在同样的问题上可以拿到55分,已经达到了小学生90%左右的水平!训练验证器GPT-3之前就有说过许多令人印象深刻的技能,像是模仿人的多种写作风格、20分钟内完成论文、在文本生成上与人类写作相媲美等。然而,他们却很难执行准确多步推理的任务,例如小学数学题。尽管类似这样的模型可以推导出正确解决方案大致内容,但也经常会产生严重的逻辑错误。为了在复杂逻辑领域可以达到与人类相媲美的性能,模型必须具有判别自身错误的能力,并谨慎地执

  • TypeScript 的 generic 函数

    官方链接编写一个函数,其中输入的类型与输出的类型相关,或者两个输入的类型以某种方式相关。让我们考虑一个返回数组第一个元素的函数:functionfirstElement(arr:any[]){ returnarr[0]; }复制这个函数完成了它的工作,但不幸的是返回类型为any。如果函数返回数组元素的类型会更好。在TypeScript中,当我们想要描述两个值之间的对应关系时,会使用泛型。我们通过在函数签名中声明一个类型参数来做到这一点:functionfirstElement<T>(arr:T[]):T{ returnarr[0]; } constarr:string[]=['1','2','3']; constresult=firstElement(arr); console.log(result); constresult1:number=firstElement(arr);复制TypeScript的类型推断-Typeinferencefunctionmap<Input,Output>(ar

  • ElasticSearch教程(二)——ElasticSearch基本插件head

    公司一直在使用ES作为分布式的搜索引擎,由于数据量的不断升高,ES出现了性能瓶颈。公司决定进一步的优化ES配置,所以最近几天在研究ES,最近会更新一系列ES的教程,希望大家持续关注。不多说了,Action。前言这篇文章我们介绍一个非常好的工具:elasticsearch-head,从简单介绍,到安装,最后使用。简介elasticsearch-head是一个界面化的集群操作和管理工具,可以对集群进行傻瓜式操作。你可以通过插件把它集成到es(首选方式),也可以安装成一个独立webapp。es-head主要有三个方面的操作:显示集群的拓扑,并且能够执行索引和节点级别操作搜索接口能够查询集群中原始json或表格格式的检索数据能够快速访问并显示集群的状态有一个输入窗口,允许任意调用RESTfulAPI。这个接口包含几个选项,可以组合在一起以产生有趣的结果; 请求方法(get、put、post、delete),查询json数据,节点和路径支持JSON验证器支持重复请求计时器支持使用javascript表达式变换结果收集结果的能力随着时间的推移(使用定时器),或比较的结果能力图表转换后的结果在一个简

  • 超性感的React Hooks(四):useEffect

    1想不想验证一下自己的React底子到底怎么样?或者验证一下自己的学习能力?这里有一段介绍useEffect的文字,如果你能够从中领悟到useEffect的用法,那么恭喜你,你应该大概率是个天赋型选手。那么试试看:在function组件中,每当DOM完成一次渲染,都会有对应的副作用执行,useEffect用于提供自定义的执行内容,它的第一个参数(作为函数传入)就是自定义的执行内容。为了避免反复执行,传入第二个参数(由监听值组成的数组)作为比较(浅比较)变化的依赖,比较之后值都保持不变时,副作用逻辑就不再执行。如果读懂了,顺手给我点个赞,然后那么这篇文章到这里就可以完结了。如果没有读懂,也没有关系,一起来学习一下。首先,我们要抛开生命周期的固有思维。许多朋友试图利用class语法中的生命周期来类比理解useEffect,也许他们认为,hooks只是语法糖而已。那么,即使正在使用hooks,也有可能对我上面这一段话表示不理解,甚至还会问:不类比生命周期,怎么学习hooks?我不得不很明确的告诉大家,生命周期和useEffect是完全不同的。2什么是副作用effect本来吃这个药?,我只是想

  • 2019年中国网络安全产业报告发布

    2019年9月17日,《2019年中国网络安全产业报告》在“国家网络安全宣传周-网络安全标准与产业高峰论坛”上发布。该报告由中国网络安全产业联盟联合网络安全行业研究机构数说安全共同完成,以具备网络安全产品、服务和解决方案的销售收入的厂商为研究对象,筛选和梳理了超过200家企业的数据,最大程度上反映了我国中国网络安全产业的真实情况。《报告》指出2018年我国网络安全市场规模为393.25亿元,同比增长率约为17.8%,预计未来三年产业整体市场依然会保持20%左右的高速增长,到2021年我国网络安全市场规模将达到668.05亿元。受国家网络安全政策、产业内龙头企业并购重组、互联网厂商进入安全市场、信息技术创新带来的新兴安全应用场景、资本注入重组等关键因素的影响,现有网络安全企业的竞争格局将在未来3-5年发生变化。2018年中国网络安全市场CR1为6.41%,CR4为21.71%,CR8为38.75%。根据美国经济学家贝恩对行业集中度的划分标准,中国网络安全行业CR8小于40%,属于竞争型市场。分析显示,中国网络安全市场正在由竞争型市场向低集中寡占型市场转变。《报告》通过数据分析得出,中国网

  • 接口自动化测试实践指导(中):接口测试场景有哪些

    在第一篇文章接口自动化测试实践指导(上):接口自动化需要做哪些准备工作中详细给小伙伴们讲解了一下接口自动化需要做哪些准备工作,准备工作中最后一步接口测试用例设计是非常重要的一个环节,用例设计的好不好,直接关系到我们的测试质量,那如何进行测试用例设计呢,这里呢我结合自身经验,帮助大家梳理一下接口测试用例设计思路,希望对大家后续接口测试工作有所帮助和提升。可以看看这个:https://www.eolink.com/1接口测试场景梳理1.1设计思路在接口测试中,很大程度上,我们的测试质量依赖于接口测试场景的设计,而接口的测试场景和传统的功能测试场景又有所不同,不少测试同学一时无法很好的转换,一上来进行接口测试思路上会比较乱,这里呢给大家梳理一下接口的常用测试场景,并进行了分类,感兴趣的同学建议反复多看几遍,并多思考一下。注:性能角度和安全角度的实际测试要比功能角度测试复杂的多,本篇重点讲解功能角度测试,后续再写专题文章来详细讲解接口的性能和安全测试如何进行。1.2功能角度讲解接口测试的功能角度划分,依据侧重的角度不同,可以有多种划分方法,目前我实际测试工作中主要使用的测试场景可以划分为五类:

  • Linux系统工作面试要掌握的命令

    大家好,这里是不念博客,很高兴再次和大家相见~今天给大家分享工作中要掌握的命令,在日常工作中排除报错、测试程序非常重要!Linux面试题如何查看文件末尾100行tail-n100/var/log/messeges复制如何过滤文件内容中包含error的行greperrro/var/log/access.log复制如何查看端口号netstat-luntp复制ss-lunpt复制如何查看进程号ps-ef|grephttpd复制如何查看IP地址ifconfig复制ipa复制如何找出httpd的进程,并且杀掉这个进程ps-ef|grephttpdgrep-vgrepawk'[print$2}'xargskill复制按预定时间关闭系统shutdown-hhours:minutes复制搜索在10天内被创建或者修改过的文件find/usr/bin-typef-mtime-10复制每隔20秒,一共执行3次,将统计结果导入到test.txt文件中top-d20-n3-b>test.txt复制 ------本页内容已结束,喜欢请分享------

  • C#实现无物理边距 可打印区域的绘图\打印 z

    经常在开发实际的应用程序中,需要用到图形绘制和打印程序。如何实现完整的精确打印和绘图是需要注意许多细节地方的。最近在遇到打印问题的时候,仔细研究一阵,总结这篇博文,写得有点杂乱,看文要还请费点神。 基本功能:窗体绘图与鼠标交互  打印预览与打印输出 开发平台:VisualStudio2010 (C#) 1绘图坐标系统 1.1绘图系统坐标转换(屏幕窗口/打印机)   绘图程序涉及到多种坐标系统,总体上可分为三个坐标系:世界坐标系、页面坐标系以及设备坐标系。想要将图形图像会知道最终的设备上,中间需要做各种坐标转换,下面将详细介绍绘图系统中的坐标转换关系 1、世界坐标 实际的绘图区域,如港口码头的2000米长度的范围、测井3000米的测量深度等。通常的实际应用中如果用到有打印这样的精确绘制功能,则还需要注意由世界坐标映射到逻辑坐标的比例,有两种方式: (1)根据绘图窗口大小动态的计算映射比例,一般绘图都是这样,这种方式可以让用户更加方便的阅览全局绘图; (2)设定映射比例为一个定值,计算出相应的转换坐标,如测井中绘图的MD 1:2

  • innosetup安装之前关闭进程

      InnoSetup覆盖安装的时候可能会因为源程序正在运行而安装失败,以下脚本能够关闭原运行进程。 [code]//安装前检查关闭**进程functionInitializeSetup():Boolean;//进程IDvarappWnd:HWND;begin Result:=true; //Log('CheckingIfRunning...'); //根据窗体名字获取进程ID appWnd:=FindWindowByWindowName('IEScavenger'); if(appWnd<>0)then   //进程存在,关闭   begin    //Log('IsRuning...');    //给进程发送关闭消息    PostMessage(appWnd,18,0,0);   //qui

  • 湾区求职分享:三个月刷题拿到 Google offer,欢迎踊跃提问

    本文仅以个人经历和个人观点作为参考。如能受益,不胜荣幸。 本文会不断的修正,更新。希望通过大家的互动最后能写出一份阅者受益的文章。 本文纯手打,会有错别字,欢迎指出,虚心接受及时更改。 小马过河,大牛觉得轻松,松鼠觉得可怕。 湾区求职经验分享:我是如何通过三个月努力拿到Googleoffer,欢迎踊跃提问! 有朋友指出“三个月”是不是哗众取宠博取眼球。其实我确实是实话实说(详见下文)三个月。我只是想分享如何高效的做题,让大家少走弯路。那些刷五遍十遍的朋友,在我看来是走了弯路的,如果大家都能一两遍做懂,何乐不为? 本人背景介绍: 本科国内某211,EEmajorPower 硕士研究生USCCSGeneral 实习经历 AmazonInc,Seattle,WA.3-monthintern TeslaMotors,Fremont,CA.4-monthintern LinkedIn 简历 简历的意义:简历到底有多重要?是不是要把简历写的很Fancy?写的比较多比较夸张会不会被问到? 对于NewGrad来说,简历只是敲门砖,好的简历的目的是让HR注意到你“哎哟,这个New Grad

  • php压缩文件

    直接代码,简洁明了 $path=__DIR__."/a.txt"; $filename="test.zip"; $zip=newZipArchive(); $zip->open($filename,ZipArchive::CREATE);//打开压缩包 $zip->addFile($path,basename($path));//向压缩包中添加文件 $zip->close();//关闭压缩包复制  

  • 人工智能汇总---政策-应用--技术

      2017.8-2018.6月      那些脑袋迷糊的日子,不知啥是人工智能,接下来一步一步去了解,从大政策,到媒体、企业、学校、自己动手,逐步对人工智能有个初步的了解。     下面对精华网址汇总,供有共同爱好的学习,讨论群(366244662)。 2017.8月新一代人工智能发展规划的通知http://www.gov.cn/zhengce/content/2017-07/20/content_5211996.htm   一、问题定义、调研 (政策文件的分析)2017.8月(分析)新一代人工智能发展规划的通知--精华 https://www.cnblogs.com/2010dream/p/8011154.html   二、(社会)人工智能的应用领域及解决的问题人工智能技术应用---cctv机智过人与中科院合作的http://tv.cctv.com/lm/jzgr/index.shtml   三、(企业--大学)大学里最新的讲座(技术与教育相关的)人工智

  • shell下批量重命名svn文件的方法

    shell下批量重命名svn文件的方法 目标: 将svn目录下所有文件重命名,原文件前缀为ucc_,批量改为xmd_ 用tree看下当前svn目录 ucc_1.c ucc_1.h ucc_2.c ucc_2.h 复制 首先更新svn目录 svnup. 批量变更文件名 方法1. lsucc*.[ch]|awk'{new=$1;gsub("ucc_","xmd_",new);print"svnmv"$1,new}'|sh 复制 方法2 lsucc*.[ch]>/tmp/lst whilereadname do new=$(name/ucc_/xmd} svnmvnamenew done</tmp/lst 复制 提交 svnci-m"renameucc_*toxmd_*" 复制 完成。

  • 【模板】线段树

    【模板】线段树 日期:2020-05-31 目录【模板】线段树一、【模板】问题1.题意分析2.暴力分析二、线段树概念1.概念2.问题实现1).建树2).修改3).查询三、线段树的构建与操作0.定义结点1.建树2.懒标记下放和回溯3.修改4.查询四、完整代码 一、【模板】问题 题目链接(洛谷P3372) 1.题意分析 给定一个数列,现有两种操作: 将区间\([x,y]\)内每个数加上\(k\); 输出区间\([x,y]\)内每个数的和。 2.暴力分析 修改:暴力对区间内每个数加上\(k\),时间复杂度:\(O(n)\); 查询:暴力求区间内每个数的和,时间复杂度:\(O(n)\)。 操作次数为\(m\),则总时间复杂度为\(O(nm)\)。 \(\because1\len,m\le10^5\), \(\thereforeO(nm)=O(10^{10})\),故\(TLE\)。 所以,我们需要一种更好的方法(数据结构)。 二、线段树概念 1.概念 线段树,一种形如二叉树的数据结构,适用于处理区间操作。线段树的每个结点存储一个区间,如下图所示: 2.问题实现 1).建树 输入#1

  • Maven Assembly plugin and Spring Namespace handlers

    By AlexisSeigneurin on September17,20099:40AM | NoComments Yesterday,IcameaccrossaweirderrorwhenworkingwithaSpring-enabledapplication.ItwasworkingfinewithinEclipebutwouldn'trunonthecommandline: Exceptioninthread"main"org.springframework.beans.factory.parsing.BeanDefinitionParsingException:Configurationproblem: UnabletolocateSpringNamespaceHandlerforXMLschemanamespace[http://www.springframework.org/schema/p] IknewthatSpringclasseswereintheclasspath,butIdidn'tknoww

  • MyBatis 【单独连接】

    Configurationconf=newConfiguration(); conf.setEnvironment( newEnvironment.Builder("only") .transactionFactory(newJdbcTransactionFactory()) .dataSource(newPooledDataSource( "com.mysql.jdbc.Driver", "jdbc:mysql://127.0.0.1:3306/xxx_rule_engine", "root", "123456" )).build()); conf.addMapper(PrizeMapper.class); SqlSessionFactoryssf=newSqlSessionFactoryBuilder().build(conf); SqlSessionopenSession=ssf.openSession(); PrizeMappermapper=openSession.getMapper(PrizeMapper.class); List<PrizeModel>

  • Memecached 服务器安装(一)

    Memecached服务器安装(一) 前提:首先您的php环境已经安装完成,如若没有则参考 http://www.cnblogs.com/xulele/p/5264781.html 安装环境链接:http://pan.baidu.com/s/1i4IbJox Memecached服务器安装(一) memcachedphp扩展(二) redis服务器端安装(三) PHP-Redis扩展安装(四)       1、先下载安装所需要的软件 解压后你会发现有四个软件包,这里给大家进行说明: libevent-2.0.22-stable.tar.gz   安装 Memcached 服务器所依赖的软件包 libmemcached-1.0.18.tar.gz  是一个 memcached 的库 memcached-1.4.25.tar.gz Memcached 服务器软件包 memcached-2.2.0.tar.gz PHP开启 Memc

  • LINKB2B啟動配置

       

  • tensorflow运行原理分析(源码)

    tensorflow运行原理分析(源码)   https://pan.baidu.com/s/1GJzQg0QgS93rfsqtIMURSA 当神已无能为力,那便是魔渡众生

相关推荐

推荐阅读