共计 3164 个字符,预计需要花费 8 分钟才能阅读完成。
零、前言
为了不便形容,咱们将我的项目进行一下形象和简化。
这是一个前端用 Angular、后端用 Spring 的我的项目,我的项目 E - R 图的其中一段如下:
不难看出,乡镇和社区是 1 对多的关系。
在治理较低级的区域时,须要关联到较高级区域的外键(例如:社区必须有一个所属的乡镇)
因为这几种区域的查问都很频繁,为缩小 SQL 频率,在后盾设置了缓存。
为了防止删除数据导致整个零碎的谬误,全局启用了软删除。
一、问题复现
在任何一个实体的治理页面(如乡镇治理)中删除一个对象(乡镇),列表中不再显示删除的数据,数据库中也能看到,已删除对象的 deleted=1:
然而在其余实体关联查问时(社区中设置乡镇时),确能够查到曾经删除的对象,而且竟然还能保留 …
(如果保留已删除的数据,会导致系统报错)
简略总结一下就是:
因为我的项目代码的某些问题,软删除在列表分页查问时失常,但到了须要外键关联时,软删除却生效了。
二、排查问题
排除缓存起因
首先从 issue 上看,可能是后端的缓存导致的(之前呈现过相似的问题)。后端设置了从新登录时革除缓存,因而测试很简略。
尝试了浏览器刷新、退出从新登录、换浏览器等操作,并没有解决问题,当初基本上排除缓存起因了。
进一步排查,Spring 中应用 debug 模式步进查问性能的外部代码,发现返回值中呈现了被删除的信息
至此能够判定不是 缓存问题 而是查询方法的问题。
查看调用关系
既然是查问出了问题,为什么在乡镇列表却能够失常辨别已删除的数据呢?
带着疑难,我找到了前后端的调用关系:
乡镇列表发动的分页查问,最终调用到 findAll 办法
而在社区 -> 乡镇选择器中查问乡镇,最终也会调用到 findAll 办法,但参数不同
// findAll 没有参数
@Override
public List<Town> findAll() {return (List<Town>) this.townRepository.findAll();}
// page 有参数
@Override
public 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 没有参数
@Override
public List<Town> findAll() {return (List<Town>) this.townRepository.findAll();}
// page 有参数
@Override
public 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 的。
版权申明
本文作者:河北工业大学梦云智开发团队 – 刘宇轩
新人经验不足,有倡议欢送交换,有谬误欢送轻喷