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
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.
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.
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.
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.
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;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With