2016-04-21 32 views
11

Ho il seguente repository REST, la cui implementazione viene generata in fase di esecuzione da Spring.Spring Data REST: sovrascrive il metodo di repository sul controller

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

} 

Questo significa che avrò save(), find(), esiste() e altri metodi disponibili ed esposti tramite REST.

Ora, desidero sovrascrivere uno dei metodi; ad esempio, save(). Per questo, vorrei creare un controller di esporre tale metodo, in questo modo:

@RepositoryRestController 
@RequestMapping("/foo") 
public class FooController { 

    @Autowired 
    FooService fooService; 


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

} 

Il problema: Se abilito questo controller, poi tutti gli altri metodi implementati entro la primavera non sono esposti più. Così, per esempio, non posso più fare una richiesta GET a/foo/1

Domanda: C'è un modo di rilevante metodi REST pur mantenendo gli altri metodi di primavera generate automaticamente?

Info extra:

  1. Questa domanda sembra molto simile: Spring Data Rest: Override Method in RestController with same request-mapping-path ... ma io non voglio cambiare il percorso a qualcosa come/foo/1/salvare

  2. Ho pensato di usare @RepositoryEventHandler ma non mi piace molto quell'idea perché vorrei incapsularla sotto un servizio. Inoltre, sembri perdere il controllo del contesto della transazione.

  3. This part of the Spring Data documentation dice il seguente:

    A volte si può decidere di scrivere un gestore personalizzato per una specifica risorsa . Per usufruire di impostazioni di primavera dati REST, messaggio convertitori, gestione delle eccezioni, e di più, usare l'annotazione @RepositoryRestController al posto di uno standard di Spring MVC @Controller o @RestController

così sembra che dovrebbe funzionare fuori dagli schemi, ma sfortunatamente no.

+1

http://docs.spring.io/spring-data/data-jpa/docs/current/reference/html/#repositories.single-repository-behaviour Questo forse ti aiuto? – Tarmo

+0

Mi rendo conto che questa domanda non è una domanda di Grails, ma il concetto è simile alla domanda/risposta qui descritta: http://stackoverflow.com/questions/19360559/adding-functionality-to-grails-restfulcontroller – rmlan

+0

@Tarmo : Anche se penso che possa funzionare, mi costringerebbe a continuare ad aggiungere logica in un repository, e preferisco tenerlo in un servizio. – Nicolas

risposta

8

Esiste un modo per sovrascrivere i metodi REST mantenendo gli altri metodi Spring generati automaticamente?

Esaminare attentamente l'esempio nella documentazione: mentre non vieta esplicitamente la creazione di richieste a livello di classe, utilizza la procedura di richiesta del metodo. Non sono sicuro se questo è il comportamento voluto o un bug, ma per quanto ne so questo è l'unico modo per farlo funzionare, come indicato here.

basta cambiare il controller a:

@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 
    } 
} 
+1

Purtroppo, questo non funziona neanche. Se lo faccio, allora il metodo GET implementato da Spring non funziona. – Nicolas

+1

Sembra funzionare da me (spring-boot-starter-data-rest 1.4.1.RELEASE) Anche il '@ RepositoryRestController' vs' @ RestController' ha fatto il trucco. –

+1

Doveva inoltre aggiungere '@ ResponseBody 'ai metodi del controller sovrascritto. –

3

ho trovato una soluzione ordinata se si utilizza Java 8 - basta usare metodi predefiniti nell'interfaccia

@RepositoryRestResource 
public interface FooRepository extends CrudRepository<Foo, Long> { 
    default <S extends T> S save(S var1) { 
     //do some work here 
    } 
} 
+0

Questo sovrascriverà il metodo 'save' per l'intera applicazione per questo repository. Se questo non è il comportamento desiderato, non dovrebbe essere usato, altrimenti è un'opzione valida. – lealceldeiro

5

Immaginiamo che abbiamo un'entità Account:

@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; 
    } 
} 

Con un AccountRepository esponendo la sua C endpoint RUD su /accounts:

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

E un AccountController che sovrascrive quella di default GET modulo endpoint 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); 
    } 

Se si richiama il GET /accounts?size=5&page=0 otterrete l'output seguente che sta usando l'implementazione mock:

{ 
    "_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 
    } 
} 

Solo per ragioni di completezza, il POM potrebbe essere con figurato con le seguenti dipendenze padre e:

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