零、前言
为了不便形容,咱们将我的项目进行一下形象和简化。
这是一个前端用Angular、后端用Spring的我的项目,我的项目E-R图的其中一段如下:
不难看出,乡镇和社区是1对多的关系。
在治理较低级的区域时,须要关联到较高级区域的外键(例如:社区必须有一个所属的乡镇)
因为这几种区域的查问都很频繁,为缩小SQL频率,在后盾设置了缓存。
为了防止删除数据导致整个零碎的谬误,全局启用了软删除。
一、问题复现
在任何一个实体的治理页面(如乡镇治理)中删除一个对象(乡镇),列表中不再显示删除的数据,数据库中也能看到,已删除对象的deleted=1:
然而在其余实体关联查问时(社区中设置乡镇时),确能够查到曾经删除的对象,而且竟然还能保留...
(如果保留已删除的数据,会导致系统报错)
简略总结一下就是:
因为我的项目代码的某些问题,软删除在列表分页查问时失常,但到了须要外键关联时,软删除却生效了。
二、排查问题
排除缓存起因
首先从issue上看,可能是后端的缓存导致的(之前呈现过相似的问题)。后端设置了从新登录时革除缓存,因而测试很简略。
尝试了浏览器刷新、退出从新登录、换浏览器等操作,并没有解决问题,当初基本上排除缓存起因了。
进一步排查,Spring中应用debug模式步进查问性能的外部代码,发现返回值中呈现了被删除的信息
至此能够判定不是缓存问题而是查询方法的问题。
查看调用关系
既然是查问出了问题,为什么在乡镇列表却能够失常辨别已删除的数据呢?
带着疑难,我找到了前后端的调用关系:
乡镇列表发动的分页查问,最终调用到findAll办法
而在社区->乡镇选择器中查问乡镇,最终也会调用到findAll办法,但参数不同
// findAll没有参数@Overridepublic List<Town> findAll() { return (List<Town>) this.townRepository.findAll();}// page有参数@Overridepublic Page<Town> page(String name, Pageable pageable) { return this.townRepository.findAll(TownSpecs.containingName(name)), pageable);}
于是初步判断,是仓库层TownRepository的getAll()办法漏写了软删除相干的性能导致的,但目前咱们看到的代码中,并没有对于软删除是如何实现的,所以持续找。
摸索软删除的实现
既然曾经晓得问题出在哪,接下来就去找,在这个我的项目中,软删除是怎么实现的,以及影响仓库层查问的要害的代码在哪里,我从历史的Pull Request中找到软删除的PR。
发现本我的项目中,所有的实体都继承了根底实体,启用软删除须要在根底实体中设置deleted和deleteAt字段,以及相干的Setter、Getter办法,用来示意已删除和删除工夫:
而后在所有的继承类上增加@SQLDelete注解,把删除性能替换成”设置deleted=1“
第二行@where(clause = "deleted = false") 作用是在查问时只查问没有被软删除的数据。
此时我想到了一个笨办法:在仓库层所有的findAll上减少deleted = 0 的条件,但问题是,这么多的实体,会产生大量反复代码,而且也没有从根本上解决问题,因而放弃。
至此,找了一圈还是没找到答案:按理说这样曾经能够失效了,但为什么findAll()会不失常呢?
又比对了一下本地最新版本的代码,发现继承实体中曾经删去了@where(clause = "deleted = false")
正当我纳闷的时候,发现代码正文里有一个思否链接,关上一看正是潘老师之前写的软删除的博客,于是我又读了一遍。
spring boot实现软删除
这才理解到:
@Where(clause = "deleted = false")会导致咱们在进行all或page查问时,失去一个500 EntiyNotFound谬误。
博客中也给出了解决办法:创立一个SoftDeleteCrudRepository接口,继承并笼罩JPA外部的CrudRepository的办法,手动的为查询方法增加deleted = false条件(具体代码见博客),这样既能实现软删除又防止了500谬误。
三、解开BUG的神秘面纱
所以能够猜测到,问题肯定是呈现在咱们本人写的SoftDeleteCrudRepository上,大略是因为某些办法没有override。
来到此我的项目的软删除仓库中,对于findAll的重载办法有这些:
@Override public Page<T> findAll(Pageable pageable) { return this.findAll(this.andDeleteFalseSpecification(null), pageable); } @Override public Page<T> findAll(@Nullable Specification<T> specification, Pageable pageable) { return super.findAll(this.andDeleteFalseSpecification(specification), pageable); } @Override public List<T> findAll(@Nullable Specification<T> specification, Sort sort) { return super.findAll(this.andDeleteFalseSpecification(specification), sort); }
咱们再来回顾一下,方才的两种状况是怎么调用的:
// findAll没有参数@Overridepublic List<Town> findAll() { return (List<Town>) this.townRepository.findAll();}// page有参数@Overridepublic Page<Town> page(String name, Pageable pageable) { return this.townRepository.findAll(TownSpecs.containingName(name)), pageable);}
好,破案了,咱们的软删除类中并没有override空参数状况的findAll办法,因而对于空参数,并没有主动加上deleted=1 的查问条件。
所以只须要在这里加上:
@Override public List<T> findAll(@Nullable Specification<T> specification) { return super.findAll(this.andDeleteFalseSpecification(specification)); }
就能够让本文的bug在所有仓库的findAll()办法中隐没,日后再呈现新的调用形式,也只需批改软删除类即可。
至此问题终于解决。
参考资料
- spring boot实现软删除:https://segmentfault.com/a/11...
后记
其实这个我的项目真正的 写法比参考资料中更简单:
- SoftDeleteCrudRepository不再是接口,而是实现类
- 其余仓库并不是间接继承SoftDeleteCrudRepository,而是应用工厂模式注入
- 我的项目中工厂模式的代码我没看懂,其实最初也没搞明确,其余仓库在没有extends也没有implements的状况下,是怎么调用SoftDeleteCrudRepository的。
版权申明
本文作者:河北工业大学梦云智开发团队 - 刘宇轩
新人经验不足,有倡议欢送交换,有谬误欢送轻喷