Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create a new parent entity that references an already existing child entity in spring data rest / HATEOAS

In my project I have two domain models. A parent and a child entity. The parent references a list of child entitires. (e.g. Post and Comments) Both entities have their spring data JPA CrudRepository<Long, ModelClass> interfaces which are exposed as @RepositoryRestResource

HTTP GET and PUT operations work fine and return nice HATEOS representation of these models.

Now I need a special REST endpoint "create a new Parent that references one ore more already existing child entities". I'd like to POST the references to the children as a text/uri-list that I pass in the body of the request like this:

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

How do I implement this rest endpoint? This is what I tried so far:

 @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)

}

Reference / Related https://github.com/spring-projects/spring-hateoas/issues/292

like image 694
Robert Avatar asked Nov 17 '17 18:11

Robert


People also ask

What does the @RepositoryRestResource annotation do?

The @RepositoryRestResource annotation is optional and is used to customize the REST endpoint. If we decided to omit it, Spring would automatically create an endpoint at “/websiteUsers” instead of “/users“. That's it! We now have a fully-functional REST API.

What is @entity annotation in spring?

The @Entity annotation specifies that the class is an entity and is mapped to a database table. The @Table annotation specifies the name of the database table to be used for mapping.

What is difference between JpaRepository and CrudRepository?

CrudRepository provides CRUD functions. PagingAndSortingRepository provides methods to do pagination and sort records. JpaRepository provides JPA related methods such as flushing the persistence context and delete records in a batch.

What does @entity do in spring boot?

Entity: The entities are the persistence objects stores as a record in the database. Persistence Unit: It defines a set of all entity classes. In an application, EntityManager instances manage it. The set of entity classes represents the data contained within a single data store.


1 Answers

There is one related problem on a side note, that one also needs to take into account: When I want to save the parent entity, then I do not want to touch, save or alter the already existing child entity in any way. This is not that easy in JPA. Because JPA will also (try to) persist the dependant child entity. This fails with the exception:

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

To circumvent that, you have to merge the child entity into the transactin of the JPA save() call. The only way I found to have both entities in one transaction was to create a seperate @Services which is marked as @Transactional. Seems like complete overkill and overengeneering.

Here is my code:

PollController.java // the custom REST endpoint for the PARENT entiy

@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 // for transaction handling

@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;
}
like image 56
Robert Avatar answered Sep 18 '22 20:09

Robert