Spring Data REST: переопределить метод репозитория на контроллере
У меня есть следующий репозиторий REST, реализация которого создается во время выполнения Spring.
@RepositoryRestResource
public interface FooRepository extends CrudRepository<Foo, Long> {
}
Это означает, что у меня будут функции save(), find(), exists() и другие доступные методы и доступные через REST.
Теперь я хотел бы переопределить один из методов; например, save(). Для этого я бы создал контроллер, демонстрирующий этот метод, например:
@RepositoryRestController
@RequestMapping("/foo")
public class FooController {
@Autowired
FooService fooService;
@RequestMapping(value = "/{fooId}", method = RequestMethod.PUT)
public void updateFoo(@PathVariable Long fooId) {
fooService.updateProperly(fooId);
}
}
Проблема:
Если я включу этот контроллер, то все другие методы, реализованные с помощью Spring, больше не отображаются. Так, например, я больше не могу делать запрос GET в /foo/ 1
Вопрос:
Есть ли способ переопределить методы REST, сохраняя при этом другие автоматически сгенерированные методы Spring?
Дополнительная информация:
-
Этот вопрос кажется очень похожим:
Spring Data Rest: переопределить метод в RestController с таким же способом-сопоставлением маршрутов... но я не хочу менять путь к чему-то вроде foo/1/сохранить
-
Я думал об использовании @RepositoryEventHandler, но я не очень люблю эту идею, потому что я хотел бы инкапсулировать ее под службу. Кроме того, вы, похоже, теряете контроль над контекстом транзакции.
-
В этой части документа Spring Документация говорится следующее:
Иногда вам может понадобиться написать специальный обработчик для определенного ресурс. Чтобы воспользоваться параметрами Spring Data RESTs, сообщение преобразователей, обработки исключений и т.д., используйте @RepositoryRestController аннотации вместо стандартного Spring MVC @Controller или @RestController
поэтому кажется, что он должен работать из коробки, но, к сожалению, нет.
Ответы
Ответ 1
Есть ли способ переопределить методы REST, сохраняя при этом другие автогенерированные методы Spring?
Внимательно посмотрите на пример в документации: хотя явным образом запрещаю сопоставление запросов на уровне класса, он использует метод request-mapping на уровне метода.
Я не уверен, что это желаемое поведение или ошибка, но насколько я знаю, это единственный способ заставить его работать, как указано здесь.
Просто измените ваш контроллер на:
@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 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
}
}
Ответ 2
Предположим, что у нас есть объект 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;
}
}
С AccountRepository
, отображающим его конечные точки CRUD на /accounts
:
@RepositoryRestResource(collectionResourceRel = "accounts", path = "accounts")
public interface AccountRepository extends CrudRepository<Account, Integer> {
}
И AccountController
, который переопределяет форму конечной точки GET
по умолчанию 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);
}
Если вы вызываете GET /accounts?size=5&page=0
, вы получите следующий вывод, который использует реализацию 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
}
}
Только для полноты, POM может быть настроен со следующими родителями и зависимостями:
<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>
Ответ 3
Я нашел аккуратное решение, если вы используете Java 8 - просто используйте методы по умолчанию в интерфейсе
@RepositoryRestResource
public interface FooRepository extends CrudRepository<Foo, Long> {
default <S extends T> S save(S var1) {
//do some work here
}
}