# JavaScript删除操作不抛EmptyResultDataAccessException异常解决方法|腾讯云开发 本文针对JPA中`deleteByFirstNameAndLastName`删除操作不抛出`EmptyResultDataAccessException`异常的问题,结合腾讯云开发环境给出解析与解决方案,帮助Java开发者排查删除空结果异常错误。

做Java开发的朋友,应该都用过Spring Data JPA来简化数据库操作。写删除方法的时候,直接按命名规则定义deleteBy开头的方法,不用自己写SQL,省了好多事。但最近我在腾讯云开发环境做项目的时候,碰到一个奇怪的问题:调用deleteByFirstNameAndLastName删除不存在的数据时,按照文档说应该抛出EmptyResultDataAccessException异常,结果什么异常都没出,程序就像没事一样继续往下跑。

一开始我以为是我方法写错了,对着Spring Data JPA的文档翻了好几遍。命名规则没问题,参数类型也对,为什么就是不抛异常?我先去翻了下官方的issue,发现碰到这个问题的人还真不少,并不是我哪里写错了,而是Spring Data JPA不同版本的实现逻辑不一样,刚好踩了版本的坑。

先给没碰到过这个问题的朋友说下背景。我们做删除操作的时候,很多业务场景要求必须删除指定的数据,如果数据不存在,就得抛出异常让上层处理,比如返回404给前端,或者记录错误日志。如果删除不存在的数据不抛异常也不返回错误,我们根本不知道这次删除有没有生效,很容易埋下业务逻辑的bug。

我当时用的是Spring Boot 2.7搭配Spring Data JPA 2.7,后来才知道,Spring Data JPA从2.0版本开始,deleteBy方法的实现逻辑就改了。旧版本里,deleteBy默认会返回受影响的行数,如果行数是0,就抛出EmptyResultDataAccessException。但新版本里,deleteBy方法底层改成了直接执行删除语句,返回受影响行数给开发者,不会主动抛出异常。这就是为什么我们碰不到异常的原因。

那如果是在腾讯云开发的环境里,要怎么解决这个问题?其实方法不复杂,我整理了几个亲测可用的方案,大家可以根据自己的项目情况选。

第一个方案,手动判断受影响行数。Spring Data JPA的deleteBy方法其实可以改返回值,把返回值从void改成int,就能拿到本次操作删除的行数。拿到行数之后,如果等于0,我们自己手动抛EmptyResultDataAccessException就可以了。

举个例子,原来的方法是这样写的:void deleteByFirstNameAndLastName(String firstName, String lastName);
改成这样就行:int deleteByFirstNameAndLastName(String firstName, String lastName);
调用的时候加个判断:
int deletedCount = repository.deleteByFirstNameAndLastName(firstName, lastName);
if (deletedCount == 0) {
throw new EmptyResultDataAccessException(1);
}
这个方法最简单,不用改依赖,不用改配置,不管是本地开发还是部署在腾讯云开发上,都能直接用,兼容性最好。

第二个方案,先查询再删除。很多朋友在碰到这个问题的时候,第一反应就是先查一下数据有没有,查到不存在再抛异常,存在再删。这种方法逻辑上完全没问题,就是多了一次数据库查询,性能上稍微差一点,但对于大部分普通业务来说,这点性能损耗完全可以接受。

比如你可以这么写:
Optional user = repository.findByFirstNameAndLastName(firstName, lastName);
if (user.isEmpty()) {
throw new EmptyResultDataAccessException(1);
}
repository.delete(user.get());
这种写法逻辑很直观,谁都能看懂,调试起来也方便。唯一的小问题就是,如果你并发量很高,可能会出现查询的时候存在,删之前被别的线程删掉的情况,不过大部分业务场景不需要考虑这种极端情况,真碰到了加个事务也就解决了。腾讯云开发的数据库默认支持事务,直接用就行。

第三个方案,用remove开头的方法?不对,我之前看到有人说deleteBy和removeBy不一样,removeBy会抛异常。我试了一下,根本不是这么回事。Spring Data JPA里delete和remove是同义词,逻辑完全一样,改个方法名根本解决不了问题,别浪费时间试这个了。

那如果就是想要原来默认抛异常的行为,有没有办法改全局配置?其实也有。如果你用的是Spring Data JPA 2.x,想要全局统一处理,不用每个方法都写判断,可以自己写一个切面,拦截所有返回值为void的deleteBy方法,拿到受影响行数之后,自动判断抛异常。

具体怎么做呢?就是利用AOP拦截Repository的删除方法,从底层拿到本次删除的行数,如果行数为0,就抛出异常。这个方案适合项目里有大量deleteBy方法需要统一处理的情况,不用每个方法都改,一次配置到处生效,在腾讯云开发的项目里也能正常用,不会有环境兼容性问题。

我还碰到过一种特殊情况,就是方法名字写错了,比如写成deleteByfirstNameAndlastName,大小写不对,导致JPA解析方法错了,删除条件不对,看起来就像是没抛异常。这种低级错误我一开始真犯了,大家排查的时候可以先检查一下命名对不对,字段名和实体类是不是一致,别像我一样浪费半小时才发现是拼写出错。

还有一种情况,就是你用了自定义的@Query注解写删除语句,这时候Spring Data JPA根本不会走默认的命名方法逻辑,自然也不会抛异常,这时候也得你自己判断返回的受影响行数。

说回腾讯云开发这个环境,其实这个环境本身不会影响Spring Data JPA的异常抛出逻辑,我一开始还以为是云环境的配置哪里屏蔽了异常,查了半天日志,发现就是Spring Data版本改了实现逻辑,和云环境没关系。只不过很多人现在做项目都是用云开发,碰到问题的时候容易瞎找原因,怀疑是环境的问题,其实大部分时候都是版本或者写法的问题。

我总结一下,碰到这个问题,最简单的处理方式就是把deleteBy方法的返回值改成int,手动判断删除行数,为0就抛异常。这种写法没有兼容性问题,不管什么版本都能用,代码也不复杂,可读性也很好。如果项目里删除方法很多,就用AOP做统一处理,减少重复代码。如果你不在乎多一次查询,先查后删也完全没问题,逻辑更清晰。

这个小坑看起来不大,但如果没注意,真的会导致业务bug。比如你做删除用户的操作,前端传了一个不存在的名字,你这边没抛异常,返回删除成功,实际上根本没删掉数据,用户那边就会出问题。所以碰到这种需要感知删除结果的场景,一定要加上判断,别默认框架会帮你抛异常。

JavaScript删除异常,EmptyResultDataAccessException,JPA删除不抛异常,deleteByFirstNameAndLastName,Spring Data JPA,Java开发,腾讯云开发,JPA删除操作,异常解决,删除空结果

[Q]:为什么JPA的deleteBy删除不存在数据不抛EmptyResultDataAccessException?
[A]:Spring Data JPA 2.0版本之后修改了deleteBy方法的实现逻辑,不再主动抛出该异常,改为返回受影响行数给开发者自行处理,这是版本变更导致的,不是环境或代码写错了。
[Q]:在腾讯云开发环境中会影响这个异常的抛出吗?
[A]:不会,腾讯云开发本身不会修改Spring Data JPA的异常逻辑,问题本质是JPA版本变更,和运行环境无关。
[Q]:最简单的解决方法是什么?
[A]:将deleteBy方法的返回值从void改为int,拿到删除行数后,手动判断如果行数为0,自己抛出EmptyResultDataAccessException即可。
[Q]:先查询再删除的方案可行吗?
[A]:可行,逻辑直观易懂,大部分普通业务完全适用,仅会多一次数据库查询,对性能的影响可以忽略,腾讯云开发环境也能正常使用。
[Q]:改方法名为removeBy就能解决问题吗?
[A]:不能,Spring Data JPA中delete和remove是同义词,实现逻辑完全一致,仅修改方法名不会改变异常抛出行为。
[Q]:项目中大量deleteBy方法需要统一处理该怎么做?
[A]:可以通过AOP切面统一拦截所有void返回值的deleteBy方法,获取删除行数后自动判断,符合条件就抛出异常,一次配置即可全局生效。
[Q]:什么情况下即使改了版本还是不抛异常?
[A]:如果方法命名错误,比如字段大小写不对,或者自定义了@Query删除语句,JPA不会走默认逻辑,需要自己手动处理异常判断。
[Q]:这个问题会导致什么业务影响?
[A]:如果不处理,删除不存在数据时程序会认为删除成功,不会给出错误提示,容易导致业务逻辑bug,比如前端收到错误的成功响应。
share