报错背景:在做公司项目的时候,因为数据量庞大,涉及到了分库分表,增删查一般没什么问题,唯独修改的时候出现了错误
## 分库分表配置(举例)
spring:
jdbc:
algorithm-inline-expression: db_resource_${0..1} # 数据库两个
sharding-column: company_id # 分库的分片键
algorithm-expression: db_resource_${company_id % 2} # 分库的方式:company_id取余
inline-database-config:
- dbname: tablename_0
url: jdbc:mysql://ip:port/
username: ****
password: ******
- dbname: tablename_1
url: jdbc:mysql://ip:port/
username: ****
password: ******
inline-table-config:
- logic-table: xxx_table_data
algorithm-inline-expression: xxx_table_data_${0..1}
sharding-column: id # 分表的分片键
algorithm-expression: xxx_table_data_${id % 2}
org.springframework.dao.DataIntegrityViolationException:
### Error updating database. Cause: java.sql.SQLException: Can not update sharding value for table `xxx_table_data`.
### The error may exist in xxx.java(路径) (best guess)
### The error may involve xxxMapper(路径).update-Inline
### The error occurred while setting parameters
### SQL: UPDATE xxx_table_data SET company_id = ?, ...(省略) WHERE (id = ? AND company_id = ?)
### Cause: java.sql.SQLException: Can not update sharding value for table `biz_purchase_data`.
; Can not update sharding value for table `xxx_table_data`.; nested exception is java.sql.SQLException: Can not update sharding value for table `xxx_table_data`.
上面的报错语句中,update语句中的 set company_id = ? 可能是报错的关键(本人猜测),从网上其他相关资料得知,分库分表中进行更新数据的话,不能更新分片键的值,因为这是确保分库分表的关键,所以需要让mybatis-plus生成的SQL语句中不带有set company_id,但是使用了很多方式都没有成功解决。
后来思考了下,因为之前每次都将实体类作为对象传入到更新方法中
/*比如*/
super.update(bizPurchaseData, wrapper);
/*比如*/
sysCompanyService.saveOrUpdateBatchByWrapper(companyList, co -> new LambdaQueryWrapper<SysCompany>().eq(SysCompany::getDb, KFC.mdb(co.getId())));
/*等等*/
而实体类中都是带有company_id字段的,即使设置为null obj.setCompanyId(null);也不能取消掉set company_id的SQL语句。
解决办法:所以对症下药,不使用实体类进行更新,纯粹的使用wrapper对象
LambdaUpdateWrapper<XxxTableData> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(XxxTableData::getId, xxxTableData.getId())
.eq(XxxTableData::getCompanyId, xxxTableData.getCompanyId())
.set(F.isNotEmpty(xxxTableData.getCode()), XxxTableData::getCode, xxxTableData.getCode())
.set()
.set()
.set();
// 可能会修改的值都用set拼接,并判断是否为空
// 最后调用更新时,不传实体类,仅仅使用LambdaUpdateWrapper对象
return super.update(null, wrapper);
// 不要使用update(wrapper); 因为wrapper设置了泛型,mybatis会通过反射获取到实体类,还是会按照实体类的字段进行set,结果还是报错
这样就可以实现更新了,如果是想批量或者想要代码更简洁,也可以再次封装,原理是不使用实体类即可。
因为这个问题困扰了很长时间,加上网上对该报错的直接解决方式很少,所以说的内容有些多,希望能帮助大家。如果有说错或者不足的地方,欢迎大家到评论区补充~