如何在Micronaut控制器中捕获Hibernate异常?
1. 直接在控制器捕获异常?
可以这么做,但仅适合极小项目。你可以在控制器方法里用try-catch包裹Repository的操作,捕获PersistentObjectException和ConstraintViolationException,然后返回自定义HTTP响应(比如400状态码+友好提示)。但这种方式的问题是多个控制器会重复写相同的捕获逻辑,代码冗余,后续维护成本高。
示例代码:
@Post("/links") public HttpResponse<?> createLink(@Body Link link) { try { linkRepository.save(link); return HttpResponse.created(link); } catch (PersistentObjectException e) { return HttpResponse.badRequest("新增请求不能携带ID,请移除ID字段"); } catch (ConstraintViolationException e) { return HttpResponse.badRequest("该链接的唯一字段已存在,请检查后重试"); } }
2. 封装Service层处理异常?
这是更推荐的最佳实践,哪怕当前没有复杂业务逻辑。Service层可以作为数据访问层(Repository)和控制器之间的隔离层,集中处理异常、统一业务逻辑入口,避免控制器代码臃肿。
你无需一开始就强制引入DTO,初期可以直接传递实体类,后续根据业务需求再逐步调整DTO或分包结构,不会破坏现有架构。具体做法:
- 创建
LinkService类,注入LinkRepository - 在Service方法中调用Repository,捕获Hibernate异常并转换为自定义业务异常(比如
DuplicateLinkException、InvalidIdException) - 控制器调用Service方法,捕获自定义业务异常后返回友好响应
示例代码:
// LinkService @Singleton public class LinkService { private final LinkRepository linkRepository; public LinkService(LinkRepository linkRepository) { this.linkRepository = linkRepository; } public Link createLink(Link link) { if (link.getId() != null) { throw new InvalidIdException("新增链接不能指定ID"); } try { return linkRepository.save(link); } catch (ConstraintViolationException e) { throw new DuplicateLinkException("链接唯一字段已存在"); } } } // 控制器 @Post("/links") public HttpResponse<?> createLink(@Body Link link) { try { Link savedLink = linkService.createLink(link); return HttpResponse.created(savedLink); } catch (InvalidIdException | DuplicateLinkException e) { return HttpResponse.badRequest(e.getMessage()); } }
这种方式职责分离清晰,控制器只处理请求响应,Service处理数据操作和异常,后续扩展业务逻辑(比如权限校验、参数预处理)时,直接在Service层添加即可,无需改动控制器。
3. 手动数据库查询验证?
不推荐这种方式。额外的查询会增加数据库压力,而且存在竞态条件:比如你查询时发现没有重复值,但在执行插入操作前,另一个请求已经插入了相同的唯一字段,最终还是会触发ConstraintViolationException。数据库的约束本身就是最后一道可靠的防线,应该依赖它来检测重复,而非提前查询。
额外推荐:全局异常处理器
这是最优雅的方案,利用Micronaut的全局异常处理机制,统一处理所有Hibernate异常,无需在控制器或Service中逐个捕获。
示例代码:
@Singleton @Produces @Error(exception = ConstraintViolationException.class) public class ConstraintViolationHandler implements ExceptionHandler<ConstraintViolationException, HttpResponse<?>> { @Override public HttpResponse<?> handle(HttpRequest request, ConstraintViolationException exception) { return HttpResponse.badRequest("请求违反数据唯一性约束,请检查参数后重试"); } } @Singleton @Produces @Error(exception = PersistentObjectException.class) public class PersistentObjectHandler implements ExceptionHandler<PersistentObjectException, HttpResponse<?>> { @Override public HttpResponse<?> handle(HttpRequest request, PersistentObjectException exception) { return HttpResponse.badRequest("新增数据不能指定ID,请移除ID字段"); } }
这种方式下,无论你是控制器直接调用Repository,还是通过Service调用,只要触发对应的异常,都会自动返回友好的响应,代码最简洁,维护成本最低。
内容的提问来源于stack exchange,提问作者Severus0191

