Sharding-JDBC整合Mybatisplus分片键生成策略冲突问题及分析解决

Sharding-JDBC整合Mybatisplus分片键生成策略冲突问题及分析解决

  • 问题描述
  • 问题复现
  • 问题定位
    • 为什么InsertStatement会持有分片键字段
    • 从MP对实体类信息的封装处入手
  • 解决方案

问题描述

博主最近在学习Sharding-Sphere的组件,在使用Sharding-JDBC时,遇到问题:如果数据表主键名称为id且又是分片键,则MP(Mybatisplus)会屏蔽掉Sharding-JDBC的分片键生成策略,而使用MP默认的雪花算法生成主键。

问题复现

这里我们以Sharding-JDBC的公共表做测试:
公共表配置公共表配置
Sharding-JDBC整合Mybatisplus分片键生成策略冲突问题及分析解决_第1张图片实体类

SysDict实体对象里面没有指定id是哪个字段,如果一个实体类存在名为id的字段,MP则会把该字段当做主键(后面源码会讲到),因此这里不指定主键也没问题。

Sharding-JDBC整合Mybatisplus分片键生成策略冲突问题及分析解决_第2张图片ShardingJDBC表分片键生成策略配置
这里我们指定了主键生成策略为UUID,一堆字符串,而我们的主键是Long类型,按说生效肯定会报错,下面开始测试。

Sharding-JDBC整合Mybatisplus分片键生成策略冲突问题及分析解决_第3张图片
测试结果:
Sharding-JDBC整合Mybatisplus分片键生成策略冲突问题及分析解决_第4张图片
代码并没有报错,可以看到id字段的值根本不是UUID,而是雪花算法生成的一串数字。因此上面配置的ShardingJDBC表主键生成策略并没有生效,下面我们来定位到底是ShardingJDBC还是MP的问题。
注:我这里配置了四个数据源:三个分片库外加一个默认库,公共表操作会去所有的库执行一遍操作。

问题定位

排查Sharding-JDBC

  1. 打开源码的ShardingRule这个类,这个类里面保存了很多分片规则信息,断点到下图所示位置:

Sharding-JDBC整合Mybatisplus分片键生成策略冲突问题及分析解决_第5张图片
可以看到,分片键的生成策略没有问题,确实是UUID的实现UUIDShardingKeyGenerator,但是为什么没有生效呢???

  1. 配置没问题,为什么没生效呢?
    直接看源码GeneratedKeyContextEngine,这个类是分片键的上下文引擎,断点到createGenerateKeyContext这个创建分片键上下文对象的方法,见下图:
    Sharding-JDBC整合Mybatisplus分片键生成策略冲突问题及分析解决_第6张图片
    这个方法内部有个方法containsGenerateKey,用来判断这个insertStatement对象里面是否存在着分片键字段,打开看确实存在,因此走到findGeneratedKey这个方法:
    Sharding-JDBC整合Mybatisplus分片键生成策略冲突问题及分析解决_第7张图片
    执行到最后一行,可以看到,id这个字段居然有值,而且是一串雪花算法生成的数字。。。
    这里先放着,我们继续往下,看看为什么ShardingJDBC配置的策略会失效。
    一路debug。。。直到这个类InsertClauseShardingConditionEngine,看字面意思,这是个处理插入分片条件引擎,方法为createShardingConditions,字面意思:创建分片条件,见下图:
    Sharding-JDBC整合Mybatisplus分片键生成策略冲突问题及分析解决_第8张图片
    以上可以看出,if条件对generatedKey这个变量做了判断,第二个条件不满足,所以这里就不会执行Sharding-JDBC的分片键生成策略逻辑了,而这个generatedKey.generated属性又是由上文的GeneratedKeyContextEngine.findGeneratedKey赋值的,而进入findGeneratedKey方法的条件是insertStatement这玩意里面包含分片键字段(忘记的去看上面的图),正常来讲,这个里面是不应该包含分片键字段的,那么为什么这玩意里面会有分片键字段呢?

为什么InsertStatement会持有分片键字段

从上节的GeneratedKeyContextEngine.createGenerateKeyContext方法一路反找,最终在DataNodeRouter这个类的createRouteContext方法找到了:
Sharding-JDBC整合Mybatisplus分片键生成策略冲突问题及分析解决_第9张图片
打个断点,重新debug,最终在MySQLDMLVisitor.visitInsertValuesClause这个类的方法逮到了,这里对sql插入的列做了解析。
可以看到里面确实有个分片键id字段,这些值被设置到了InsertStatement的insertColumns属性里面,而前面也是判断这个属性是否包含分片键字段的。
Sharding-JDBC整合Mybatisplus分片键生成策略冲突问题及分析解决_第10张图片
列名称来自入参ctx,那么问题来了,这个InsertValuesClauseContext又是个啥呢?它是MySQLStatementParser的一个内部类,到这里基本上线索就很难找了。但是我们可以从MP的角度再去找 ,到底为什么会有这个分片键字段。

从MP对实体类信息的封装处入手

关于实体类信息封装的,要找的源码类肯定是TableInfoHelper“实体类反射表辅助类”,找到initTableInfo这个方法,
Sharding-JDBC整合Mybatisplus分片键生成策略冲突问题及分析解决_第11张图片
直接到复写的initTableInfo方法,可以看到封装字段信息的方法入口,debug进去。
Sharding-JDBC整合Mybatisplus分片键生成策略冲突问题及分析解决_第12张图片
跑到这里我们基本可以看到一些端倪了,MP对实体类进行反射,拿到实体类字段上的注解,这里也对主键做了判断,继续往下走。
Sharding-JDBC整合Mybatisplus分片键生成策略冲突问题及分析解决_第13张图片
没有找到主键字段,会走到initTableIdWithoutAnnotation这个方法里面,
Sharding-JDBC整合Mybatisplus分片键生成策略冲突问题及分析解决_第14张图片
方法注释写的很清楚,主键属性初始化,这里就可以看到,MP的主键判断策略:
如果是没有显示指定哪个字段是主键,这个方法里面就会判断字段是否是“id”,是就把该字段默认当做表主键。
Sharding-JDBC整合Mybatisplus分片键生成策略冲突问题及分析解决_第15张图片
方法的最终结果可以看到,表信息内存入了一个“id”字段,被标记为了主键,而该字段恰巧就是我们的分片键字段。。。
而主键类型idType因为没有指定,MP默认的就是ASSIGN_ID,即雪花算法。可想而知,后面MP就会根据主键生成策略的类型去自动生成主键。
Sharding-JDBC整合Mybatisplus分片键生成策略冲突问题及分析解决_第16张图片
继续debug可以看到,走到了MP的默认主键生成器里面,就是雪花算法的实现。
Sharding-JDBC整合Mybatisplus分片键生成策略冲突问题及分析解决_第17张图片
而这里封装的TableInfo对象肯定是跟MySQLStatementParser的InsertValuesClauseContext内存储的表字段信息是一致的,所以才会导致Sharding-JDBC的分片键策略失效。

虽然有些深入的代码没搞明白,比如MySQLStatementParser的内部类InsertValuesClauseContext,但是并不影响我们找到问题所在。

解决方案

现在问题找到了:MP在不显式指定主键且有名为“id”的字段,会将id视为主键,当分片键也是该字段时,会与MP冲突,导致Sharding-JDBC的分片键生成策略失效。
那么解决方案就很简单了:
第一种:分片键不叫“id”就行了
第二种:如果分片键是主键,又想叫id,可在分片键上标记主键@TableId,并指定主键类型type = IdType.AUTO即可
Sharding-JDBC整合Mybatisplus分片键生成策略冲突问题及分析解决_第18张图片
添加@TableId(type = IdType.AUTO)后,自定义一个分片键生成策略CUSTOMKEY,并指定该策略:
Sharding-JDBC整合Mybatisplus分片键生成策略冲突问题及分析解决_第19张图片
执行结果:
Sharding-JDBC整合Mybatisplus分片键生成策略冲突问题及分析解决_第20张图片
可以看到分片键id插入的值是自定义策略的一串数字

这里是以Sharding-JDBC公共表为案例的,对于这样的主键id字段,使用Sharding-JDBC的分片键策略的话,分表分库均会出现这样的问题,大家可以自行验证。

初学Sharding-Sphere,欢迎大佬前来毒打
Alt

你可能感兴趣的:(Sharding-Sphere,Java,Mybatisplus,java,sharding,jdbc,mybatis,mysql优化)