【问题标题】:How to create a new parent entity that references an already existing child entity in spring data rest / HATEOAS如何在spring data rest / HATEOAS中创建一个引用已经存在的子实体的新父实体
【发布时间】:2018-05-01 14:57:03
【问题描述】:

在我的项目中,我有两个域模型。一个父实体和一个子实体。父级引用子实体的列表。 (例如帖子和评论)两个实体都有它们的弹簧数据 JPA CrudRepository<Long, ModelClass> 接口,它们公开为 @RepositoryRestResource

HTTP GET 和 PUT 操作工作正常,并返回这些模型的漂亮 HATEOS 表示。

现在我需要一个特殊的 REST 端点“创建一个引用一个或多个已经存在子实体的新父级”。我想将对孩子的引用发布为我在请求正文中传递的文本/uri-list,如下所示:

POST http://localhost:8080/api/v1/createNewParent
HEADER
  Content-Type: text/uri-list
HTTP REQUEST BODY:
   http://localhost:8080/api/v1/Child/4711
   http://localhost:8080/api/v1/Child/4712
   http://localhost:8080/api/v1/Child/4713

如何实现这个休息端点?这是我迄今为止尝试过的:

 @Autowired
 ParentRepo parentRepo  // Spring Data JPA repository for "parent" entity


 @RequestMapping(value = "/createNewParent", method = RequestMethod.POST)
 public @ResponseBody String createNewParentWithChildren(
    @RequestBody Resources<ChildModel> childList,                         
    PersistentEntityResourceAssembler resourceAssembler
) 
{
   Collection<ChildModel> childrenObjects = childList.getContent()

   // Ok, this gives me the URIs I've posted
   List<Link> links = proposalResource.getLinks();

   // But now how to convert these URIs to domain objects???
   List<ChildModel> listOfChildren = ... ???? ...

   ParentModel newParnet = new ParentModel(listOfChildren)
   parentRepo.save(newParent)

}

参考/相关 https://github.com/spring-projects/spring-hateoas/issues/292

【问题讨论】:

  • 备注:我知道如何通过 RepositoryRestResource 公开的 spring-hateoas 休息端点将元素添加到子列表中。在那里,我可以通过 POSTing text/uri-list 创建parnet 子关系,如下所述:stackoverflow.com/questions/26259474/… 但我想知道我是如何在我自己的自定义休息端点中做到这一点的。
  • 有很多类似的问题。但我的特殊情况是:我想创建一个 NEW 父实体,它将链接到已经 EXISTING 子实体。
  • 我有点困惑,但是孩子怎么能在父母之前存在呢?就像您对尚不存在的帖子创建评论一样。通常你也会尽量避免在资源端点中使用动词,因为它会给端点带来某种 RPC 气味,但对于 REST,它是否存在并不重要。
  • @RomanVottner 有两种 OneToMany 关系:组合和聚合。 (参见stackoverflow.com/questions/885937/…)在我的领域模型中,两个实体都可以独立存在并拥有自己的生命周期。它们可以链接。
  • 也许“论坛帖子”和“评论”是一个不好的例子。一个更好的例子是“学校”和“学生”。一所学校有几个学生。 (学校 -> OneToMany ---> 学生)但是一个学生可能会搬到另一所学校,例如当学校关闭或他搬到他住的地方时。学生独自存在。而我的问题是:我想建立一所新学校。我希望它与一些已经存在的学生有关。

标签: java spring spring-data-rest spring-hateoas


【解决方案1】:

我遇到了同样的问题。这个问题有点老了,但我找到了另一个解决方案:

实际上,您必须在父级上强制合并,但在创建时会调用 persis。您可以通过使用空子列表保存父级来绕过,将子级添加到列表并再次保存:

List<ChildModel> listOfChildren = ... ???? ...

ParentModel newParnet = new ParentModel()
parent = parentRepo.save(newParent)
parent.getChilds().addAll(listOfChildren)
parentRepo.save(parent)

要获得合并权限,您必须编写自定义 repo 代码:

public interface PollModelRepositoryCustom {
    public PollModel merge(PollModel poll);
}

及其实现

@Repository
public class PollModelRepositoryCustomImpl implements PollModelRepositoryCustom {
    @PersistenceContext
    private EntityManager entityManager;

    public PollModel merge(PollModel poll) {
        return entityManager.merge(poll);
    }
}

然后你可以打电话:parentRepo.(newParent) 而不是parentRepo.save(newParent)

【讨论】:

  • 保存两次可以解决这个问题,是的。但这也是对数据库的两次调用。在某些极端情况下可能与性能相关。
【解决方案2】:

附注中有一个相关问题,也需要考虑:当我想保存父实体时,我不想以任何方式触摸、保存或更改已经存在的子实体.这在 JPA 中并不容易。因为 JPA 还将(尝试)持久化依赖的子实体。这失败了,但有例外:

javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist:

为了避免这种情况,您必须将子实体合并到 JPA save() 调用的事务中。我发现在一个事务中拥有两个实体的唯一方法是创建一个单独的@Services,它被标记为@Transactional。似乎完全是过度杀伤和过度工程。

这是我的代码:

PollController.java // 父实体的自定义 REST 端点

@BasePathAwareController
public class PollController {

@RequestMapping(value = "/createNewPoll", method = RequestMethod.POST)
public @ResponseBody Resource createNewPoll(
    @RequestBody Resource<PollModel> pollResource, 
    PersistentEntityResourceAssembler resourceAssembler
) throws LiquidoRestException
{
  PollModel pollFromRequest = pollResource.getContent();
  LawModel proposalFromRequest = pollFromRequest.getProposals().iterator().next();             // This propsal is a "detached entity". Cannot simply be saved.
  //jpaContext.getEntityManagerByManagedType(PollModel.class).merge(proposal);      // DOES NOT WORK IN SPRING.  Must handle transaction via a seperate PollService class and @Transactional annotation there.

  PollModel createdPoll;
  try {
    createdPoll = pollService.createPoll(proposalFromRequest, resourceAssembler);
  } catch (LiquidoException e) {
    log.warn("Cannot /createNewPoll: "+e.getMessage());
    throw new LiquidoRestException(e.getMessage(), e);
  }

  PersistentEntityResource persistentEntityResource = resourceAssembler.toFullResource(createdPoll);

  log.trace("<= POST /createNewPoll: created Poll "+persistentEntityResource.getLink("self").getHref());

  return persistentEntityResource;   // This nicely returns the HAL representation of the created poll
}

PollService.java // 用于事务处理

@Service
public class PollService {

    @Transactional    // This should run inside a transaction (all or nothing)
    public PollModel createPoll(@NotNull LawModel proposal, PersistentEntityResourceAssembler resourceAssembler) throws LiquidoException {
    //===== some functional/business checks on the passed enties (must not be null etc)
    //[...]

    //===== create new Poll with one initial proposal
    log.debug("Will create new poll. InitialProposal (id={}): {}", proposal.getId(), proposal.getTitle());
    PollModel poll = new PollModel();
    LawModel proposalInDB = lawRepo.findByTitle(proposal.getTitle());  // I have to lookup the proposal that I already have

    Set<LawModel> linkedProposals = new HashSet<>();
    linkedProposals.add(proposalInDB);
    poll.setProposals(linkedProposals);

    PollModel savedPoll = pollRepo.save(poll);

    return savedPoll;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-05-25
    • 2017-01-06
    • 2017-05-10
    • 1970-01-01
    • 2015-05-15
    • 1970-01-01
    • 2019-09-30
    相关资源
    最近更新 更多