2

I am trying to apply a projection on an entity class called Institute.

I have the following projection class defined.

@Projection(name = "instituteProjection", types = { Institute.class })
public interface InstituteProjection {

    String getOrganizationName();

    Contact getContact();

    Address getRegisteredAddress();

    Address getMailingAddress();

}

I followed an answer by Oliver Gierke link and was able to return a collection resource with the projections when http://localhost:8080/institutes is called. I did this by implementing the following method in the service layer and then calling it using a REST controller.

@Autowired
private ProjectionFactory projectionFactory;

@Autowired
InstituteTypeRepository instituteTypeRepo;

@Override
public PagedResources<Institute> getAllInstitutes(Pageable page) {
    Page<?> instituteList = instituteRepo.findAll(page).
            map(institute -> projectionFactory.createProjection(InstituteListProjection.class, institute));
    PagedResources<Institute> instituteListPaged = pagedResourcesAssembler.toResource(instituteList);
    return instituteListPaged;

}

Now how do I apply the same projection to an item resource when http://localhost:8080/institutes/1 is called?

UPDATE 1:

Controller method to get a single resource

@RequestMapping(value = "institutes/{instituteId}", method = RequestMethod.GET,
        produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> getInstitute(@PathVariable Long instituteId) {
    Institute institute = service.getInstitute(instituteId);
    return new ResponseEntity<>(institute, HttpStatus.OK);
}

UPDATE 2:

Service layer method to get the single resource from the repository

@Override
public Institute getInstitute(Long instituteId) {
    Institute institute = instituteRepo.findOne(instituteId);
    return institute;
}
Community
  • 1
  • 1
Charlie
  • 3,113
  • 3
  • 38
  • 60

3 Answers3

1

Your question is somewhat unclear. You can expose Spring Data repositories as rest resources automatically i.e. you do not need to define your own Spring MVC controller.

http://docs.spring.io/spring-data/rest/docs/current/reference/html/#install-chapter

With SDR repositories, projections defined as 'excerpt projections' can be applied automatically to collection resources:

http://docs.spring.io/spring-data/rest/docs/current/reference/html/#projections-excerpts.excerpts

but not to single resources. For single resources clients have to specify the required projection by means of a request parameter.

If you wanted to have a projection automatically applied to a single resource then that may be a reason for implementing your own controller (however it is unclear from your question if this is what you are doing).

For alternative means of auto applying a projection to a single resource (i.e. without a custom controller then see here):

Spring Data REST: projection representation of single resource

If you are creating a custom controller in order to auto apply a projection then it may be possible to simple write a custom query method that returns a Projection rather than the full entity and invoke that from your controller/service:

https://spring.io/blog/2016/05/03/what-s-new-in-spring-data-hopper#projections-on-repository-query-methods

Essentially then with a bit of configuration you could have selected Spring Data repositories automatically exposed as REST resources without having to write any controller code.

Community
  • 1
  • 1
Alan Hay
  • 22,665
  • 4
  • 56
  • 110
  • How do I call the service layer without using a custom REST controller? I know having the SDR generated controllers will save me a lot of code. – Charlie Apr 22 '17 at 08:48
  • If you want to do additional processing in response to create/update/delete REST requests then you can use event handlers. http://docs.spring.io/spring-data/rest/docs/current/reference/html/#events – Alan Hay Apr 22 '17 at 08:53
  • I tried doing this using SDR generated controllers earlier but this made constraint validation using annotations on entity classes throw a JPA rollback exception instead of giving a ConstraintViolationException when a constraint is not fulfilled. So I had to write manual validations in RepositoryEventHandler class which is not convenient. – Charlie Apr 22 '17 at 08:58
  • You can validate in SDR just the same as in a Spring MVC Controller either using JSR303 annotations or Spring Validator implementations. I do not have the required config to hand but I'm sure there are plenty of resources around. – Alan Hay Apr 22 '17 at 09:03
  • With a custom controller I can just add the @Valid annotation to the POST method and validate the entity, This will throw a specific ConstraintViolationException. However if I use SDR generated controllers it throws a JPA roll back exception. I wouldnt know the specific constraint which was violated. Is there a way to solve this problem – Charlie Apr 22 '17 at 09:24
  • If it throws a JPA rollback exception then it sounds like the validation is not executing. If it is executig then create a @ControllerAdvice to transform the errors to a JSON response.: http://stackoverflow.com/questions/25220440/can-jsr-303-bean-validation-be-used-with-spring-data-rest. **Ask a new question if you need further help with validation** – Alan Hay Apr 22 '17 at 09:41
  • The validation is executing and I do see the ConstraintViolation exception in NetBeans error log. But when I am getting a RollBack exception when in PostMan – Charlie Apr 22 '17 at 09:56
  • I started using SDR controllers as you suggested. The valdiations for POST is working but it still throw a rollback exception for PUT request. I asked a new question for this problem here http://stackoverflow.com/questions/43917331/spring-data-rest-bean-validation-is-not-applied-to-put-method – Charlie May 15 '17 at 04:36
0

In your controller or @RestController you should declare a @PathVariable. Maybe that thread will be of service for you: Spring mvc @PathVariable So you implement your controller using path variable parsing and then call your service with the PathVariable as arguement

Community
  • 1
  • 1
strash
  • 1,291
  • 2
  • 15
  • 29
  • I already implemented the controller method to get a single resource. I will include that part in the question so you can take look. Now I am calling the service layer to get the resource through the repository. So how do I apply the projection when I get the resource from the repository? – Charlie Apr 22 '17 at 08:05
0

It can be easily done in lates Spring Data Rest releases!

All you need to do is to:

  1. pass projection name as request param

    `/institutes/1?projection=instituteProjection`
    
  2. return PersistentEntityResource instead of Institute from your service method;

Done!

and I assume you want to call this service method from your custom controller, in this case you need to return ResponseEntity<PersistentEntityResource> from your controller method.

Spring Data Rest takes care of applying @Projections to PersistentEntityResources on api requests, it's just like you keep exposing your @RestResource from @RepositoryRestResource; same behaviour for projections, keeping same naming convention, basically same URI (for current example).

Your service method with a bit of bussiness logic might look like:

    @Override
    @Transactional(readOnly = true)
    public PersistentEntityResource getInstitute(Long instituteId, PersistentEntityResourceAssembler resourceAssembler) {
        Institute institute = instituteRepo.findOne(instituteId);
        return resourceAssembler.toModel(institute);
    }

and your controller method might look like this:

    @GetMapping(value = "institutes/{instituteId}")
    ResponseEntity<PersistentEntityResource> getInstitute(@PathVariable Long instituteId,
                                                                    PersistentEntityResourceAssembler resourceAssembler) {
        return ResponseEntity.status(HttpStatus.OK)
                .body(service.getInstitute(instituteId, resourceAssembler));
    }
}

spring-data-rest 3.3.4.RELEASE

also check out easier way to return a collection resource with the projections

catie
  • 41
  • 4