Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Data REST: Override repository method on the controller

Tags:

I have the following REST repository, whose implementation is generated at runtime by Spring.

@RepositoryRestResource public interface FooRepository extends CrudRepository<Foo, Long> {  } 

This means that I will have save(), find(), exists() and other methods available and exposed via REST.

Now, I would like to override one of the methods; for example, save(). For that, I would create a controller exposing that method, like so:

@RepositoryRestController @RequestMapping("/foo") public class FooController {      @Autowired     FooService fooService;       @RequestMapping(value = "/{fooId}", method = RequestMethod.PUT)     public void updateFoo(@PathVariable Long fooId) {         fooService.updateProperly(fooId);     }  } 

The problem: If I enable this controller, then all of the other methods implemented by Spring are not exposed anymore. So, for example, I can no longer do a GET request to /foo/1

Question: Is there a way of overriding REST methods while still keeping the other auto-generated Spring methods?

Extra info:

  1. This question seems very similar: Spring Data Rest: Override Method in RestController with same request-mapping-path ... but I don't want to change the path to something like /foo/1/save

  2. I thought of using a @RepositoryEventHandler but I'm not very fond of that idea because I would like to encapsulate it under a service. Also, you seem to lose control of the transaction context.

  3. This part of the Spring Data documentation says the following:

    Sometimes you may want to write a custom handler for a specific resource. To take advantage of Spring Data REST’s settings, message converters, exception handling, and more, use the @RepositoryRestController annotation instead of a standard Spring MVC @Controller or @RestController

so it seems that it should work out of the box, but unfortunately not.

like image 658
Nicolas Avatar asked Apr 21 '16 14:04

Nicolas


People also ask

Is used for exposing spring data repositories over rest using spring data rest?

Spring Data REST can be used to expose HATEOAS RESTful resources around Spring Data repositories. Without writing a lot of code, we can expose RESTful API around Spring Data Repositories.

What does the @RepositoryRestResource annotation do?

@RepositoryRestResource is used to set options on the public Repository interface - it will automatically create endpoints as appropriate based on the type of Repository that is being extended (i.e. CrudRepository/PagingAndSortingRepository/etc).

What is @repositoryrestcontroller?

@RepositoryRestResource:This annotation gives RestController functionality to the repository. This means you can access your Repository directly.

What is spring data rest Webmvc?

The spring-data-rest-webmvc is the project describing the main concepts of spring-data-rest which is the one of the main spring modules. In most cases one will use spring-data-rest dependency for his/her project.


2 Answers

Is there a way of overriding REST methods while still keeping the other auto-generated Spring methods?

Look at the example in the documentation carefully: while not explicitly forbidding class-level requestmapping, it uses method-level requestmapping. I'm not sure if this is the wanted behavior or a bug, but as far as I know this is the only way to make it work, as stated here.

Just change your controller to:

@RepositoryRestController public class FooController {      @Autowired     FooService fooService;      @RequestMapping(value = "/foo/{fooId}", method = RequestMethod.PUT)     public void updateFoo(@PathVariable Long fooId) {         fooService.updateProperly(fooId);     }      // edited after Sergey's comment     @RequestMapping(value = "/foo/{fooId}", method = RequestMethod.PUT)     public RequestEntity<Void> updateFoo(@PathVariable Long fooId) {         fooService.updateProperly(fooId);          return ResponseEntity.ok().build(); // simplest use of a ResponseEntity     } } 
like image 64
Marc Tarin Avatar answered Dec 07 '22 01:12

Marc Tarin


Let's imagine we have an Account entity:

@Entity public class Account implements Identifiable<Integer>, Serializable {      private static final long serialVersionUID = -3187480027431265380L;      @Id     private Integer id;     private String name;      public Account(Integer id, String name) {         this.id = id;         this.name = name;     }      public void setId(Integer id) {         this.id = id;     }      @Override     public Integer getId() {         return id;     }      public String getName() {         return name;     }      public void setName(String name) {         this.name = name;     } } 

With an AccountRepository exposing its CRUD endpoints on /accounts:

@RepositoryRestResource(collectionResourceRel = "accounts", path = "accounts") public interface AccountRepository extends CrudRepository<Account, Integer> { }  

And an AccountController that overrides the default GET endpoint form AccountRepository.:

@RepositoryRestController public class AccountController {     private PagedResourcesAssembler<Account> pagedAssembler;      @Autowired     public AccountController(PagedResourcesAssembler<Account> pagedAssembler) {         this.pagedAssembler = pagedAssembler;     }      private Page<Account> getAccounts(Pageable pageRequest){         int totalAccounts= 50;         List<Account> accountList = IntStream.rangeClosed(1, totalAccounts)                                              .boxed()                                              .map( value -> new Account(value, value.toString()))                                              .skip(pageRequest.getOffset())                                              .limit(pageRequest.getPageSize())                                              .collect(Collectors.toList());         return new PageImpl(accountList, pageRequest, totalAccounts);     }      @RequestMapping(method= RequestMethod.GET, path="/accounts", produces = "application/hal+json")     public ResponseEntity<Page<Account>> getAccountsHal(Pageable pageRequest, PersistentEntityResourceAssembler assembler){         return new ResponseEntity(pagedAssembler.toResource(getAccounts(pageRequest), (ResourceAssembler) assembler), HttpStatus.OK);     } 

If you invoke the GET /accounts?size=5&page=0 you will get the following output which is using the mock implementation:

{   "_embedded": {     "accounts": [       {         "name": "1",         "_links": {           "self": {             "href": "http://localhost:8080/accounts/1"           },           "account": {             "href": "http://localhost:8080/accounts/1"           }         }       },       {         "name": "2",         "_links": {           "self": {             "href": "http://localhost:8080/accounts/2"           },           "account": {             "href": "http://localhost:8080/accounts/2"           }         }       },       {         "name": "3",         "_links": {           "self": {             "href": "http://localhost:8080/accounts/3"           },           "account": {             "href": "http://localhost:8080/accounts/3"           }         }       },       {         "name": "4",         "_links": {           "self": {             "href": "http://localhost:8080/accounts/4"           },           "account": {             "href": "http://localhost:8080/accounts/4"           }         }       },       {         "name": "5",         "_links": {           "self": {             "href": "http://localhost:8080/accounts/5"           },           "account": {             "href": "http://localhost:8080/accounts/5"           }         }       }     ]   },   "_links": {     "first": {       "href": "http://localhost:8080/accounts?page=0&size=5"     },     "self": {       "href": "http://localhost:8080/accounts?page=0&size=5"     },     "next": {       "href": "http://localhost:8080/accounts?page=1&size=5"     },     "last": {       "href": "http://localhost:8080/accounts?page=9&size=5"     }   },   "page": {     "size": 5,     "totalElements": 50,     "totalPages": 10,     "number": 0   } } 

Just for the sake of completeness, the POM could be configured with the following parent and dependencies:

<parent>         <groupId>org.springframework.boot</groupId>         <artifactId>spring-boot-starter-parent</artifactId>         <version>1.5.2.RELEASE</version>     </parent>     <dependencies>         <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter-web</artifactId>         </dependency>         <dependency>             <groupId>org.springframework.data</groupId>             <artifactId>spring-data-rest-webmvc</artifactId>             <version>2.6.1.RELEASE</version>         </dependency>         <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter-data-jpa</artifactId>         </dependency>         <dependency>             <groupId>com.h2database</groupId>             <artifactId>h2</artifactId>         </dependency>     </dependencies> 
like image 23
Daniel Cerecedo Avatar answered Dec 06 '22 23:12

Daniel Cerecedo