You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Envers修订记录适配HATEOAS时遇PersistentEntity为空异常求助

问题:Spring Data Envers修订记录转HATEOAS响应时抛出PersistentEntity must not be null异常

场景复现

我正在使用Spring Boot、Spring Data REST、Spring Data Envers和Hibernate,想要通过Spring Data Envers加载实体的所有修订记录,并返回符合HATEOAS格式的响应。我的控制器代码如下:

@GetMapping(path = "/fines/{id}/revisions") 
public ResponseEntity<?> findRevisions(@PathVariable(value = "id") Long id, Pageable pageable, PersistentEntityResourceAssembler resourceAssembler) { 
    if (id != null) { 
        // 查询当前对象的所有修订记录 
        Page<Revision<Integer, Fine>> revisions = fineRepository.findRevisions(id, pageable); 
        return new ResponseEntity<>(pagedResourcesAssembler.toResource(revisions, resourceAssembler), HttpStatus.OK); 
    } else { 
        throw new ResourceNotFoundException(); 
    } 
}

遇到的问题

当我尝试用pagedResourcesAssembler.toResource(revisions, resourceAssembler)将修订记录转换为HATEOAS格式响应时,触发了以下异常:

19/01/2018 12:20:40,658 ERROR http-nio-8080-exec-4 [dispatcherServlet]:181 - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.IllegalArgumentException: PersistentEntity must not be null!]

如果直接返回revisions的话可以正常运行,但响应不符合HATEOAS规范,想知道哪里操作错了?


解决方案

这个问题的核心原因很明确:Spring Data REST的PersistentEntityResourceAssembler无法识别Revision类型。Revision是Spring Data Envers提供的包装类,不是你自己定义的JPA实体,Spring Data REST没有为它注册对应的PersistentEntity元数据,所以在尝试转换时会抛出"PersistentEntity must not be null"的异常。

下面是两种可行的解决方式,推荐第一种,更直观易维护:

方式一:自定义HATEOAS资源包装类

我们可以手动把Revision对象包装成Spring HATEOAS能识别的ResourceSupport子类,再进行转换:

1. 创建自定义修订资源类

import org.springframework.hateoas.ResourceSupport;
import org.springframework.hateoas.mvc.ControllerLinkBuilder;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;

public class RevisionResource extends ResourceSupport {
    private Integer revisionNumber;
    private LocalDateTime revisionDate;
    private Fine fine;

    // 构造器,直接从Revision对象提取数据
    public RevisionResource(Revision<Integer, Fine> revision) {
        this.revisionNumber = revision.getRevisionNumber();
        this.revisionDate = revision.getRevisionDate();
        this.fine = revision.getEntity();

        // 添加指向对应Fine实体的链接
        add(linkTo(methodOn(FineController.class).getFine(fine.getId())).withRel("fine"));
        // 添加指向当前修订列表的链接
        add(linkTo(methodOn(FineController.class).findRevisions(fine.getId(), null)).withRel("revisions"));
    }

    // 省略getter方法
    public Integer getRevisionNumber() { return revisionNumber; }
    public LocalDateTime getRevisionDate() { return revisionDate; }
    public Fine getFine() { return fine; }
}

2. 修改控制器方法转换资源

@GetMapping(path = "/fines/{id}/revisions") 
public ResponseEntity<?> findRevisions(@PathVariable(value = "id") Long id, 
                                       Pageable pageable, 
                                       PagedResourcesAssembler<RevisionResource> pagedResourcesAssembler) { 
    if (id == null) { 
        throw new ResourceNotFoundException(); 
    }

    Page<Revision<Integer, Fine>> revisions = fineRepository.findRevisions(id, pageable);
    // 将Revision列表转换为自定义的RevisionResource列表
    Page<RevisionResource> revisionResources = revisions.map(RevisionResource::new);
    
    // 现在可以正常转换为HATEOAS分页资源
    return ResponseEntity.ok(pagedResourcesAssembler.toResource(revisionResources)); 
}

这样处理后,返回的响应会包含完整的分页信息和HATEOAS链接,同时避免了PersistentEntity为空的异常。

方式二:扩展Spring Data REST的实体元数据(进阶)

如果你希望Spring Data REST自动处理Revision类型,可以注册一个自定义的PersistentEntity实现,或者使用RepresentationModelProcessor来扩展序列化逻辑,但这种方式相对复杂,适合需要全局支持Revision序列化的场景。


内容的提问来源于stack exchange,提问作者drenda

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 03:51:01