Spring Data REST:覆盖控制器上的存储库方法

Posted

技术标签:

【中文标题】Spring Data REST:覆盖控制器上的存储库方法【英文标题】:Spring Data REST: Override repository method on the controller 【发布时间】:2016-08-14 20:46:20 【问题描述】:

我有以下 REST 存储库,其实现由 Spring 在运行时生成。

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


这意味着我将拥有 save()、find()、exists() 和其他可用并通过 REST 公开的方法。

现在,我想重写其中一种方法;例如,保存()。为此,我将创建一个公开该方法的控制器,如下所示:

@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 实现的所有其他方法都不再暴露。因此,例如,我不能再向 /foo/1 发出 GET 请求

问题: 有没有一种方法可以覆盖 REST 方法,同时仍保留其他自动生成的 Spring 方法?

额外信息:

    这个问题看起来很相似: Spring Data Rest: Override Method in RestController with same request-mapping-path ...但我不想将路径更改为 /foo/1/save 之类的东西

    我曾想过使用@RepositoryEventHandler,但我不太喜欢这个想法,因为我想将它封装在一个服务下。此外,您似乎失去了对事务上下文的控制。

    This part of the Spring Data documentation 说如下:

    有时您可能想为特定的 资源。要利用 Spring Data REST 的设置,消息 转换器、异常处理等,使用 @RepositoryRestController 注解而不是标准的 Spring MVC @Controller 或 @RestController

所以它似乎应该开箱即用,但不幸的是不是。

【问题讨论】:

docs.spring.io/spring-data/data-jpa/docs/current/reference/html/… 这对你有帮助吗? 我意识到这个问题不是 Grails 问题,但概念类似于此处描述的问题/答案:***.com/questions/19360559/… @Tarmo:虽然我认为这可能可行,但它会迫使我继续将逻辑添加到存储库中,我更愿意将其保留在服务中。 【参考方案1】:

有没有一种方法可以覆盖 REST 方法,同时仍保留其他自动生成的 Spring 方法?

仔细查看文档中的示例:虽然没有明确禁止类级别的请求映射,但它使用了方法级别的请求映射。 我不确定这是想要的行为还是错误,但据我所知,这是使其工作的唯一方法,如 here 所述。

只需将您的控制器更改为:

@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
    

【讨论】:

不幸的是,这也不起作用。如果我这样做了,那么 Spring 实现的 GET 方法就不起作用了。 似乎对我有用 (spring-boot-starter-data-rest 1.4.1.RELEASE) @RepositoryRestController vs @RestController 也成功了。 还必须将@ResponseBody 添加到覆盖的控制器方法中。 这样做会丢失 HATEOAS 格式...是否可以选择保持相同的格式? @Rafael:不是一个选项。您必须使用(扩展)Resource 和 ResourceAssemblerSupport。官方documentation有资料。你也可以阅读this和this【参考方案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/accounts 上公开其 CRUD 端点:

@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&amp;page=0,您将获得以下使用模拟实现的输出:


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

【讨论】:

这就是答案! ResourceAssembler 来自 hatoas 1.0,称为 RepresentationModelAssembler。见github.com/spring-projects/spring-hateoas/blob/master/etc/…【参考方案3】:

只是我发现的一个更新挽救了我的生命。 正如@mathias-dpunkt 在这个答案中所说的那样 https://***.com/a/34518166/2836627

最重要的是 RepositoryRestController 知道 spring 数据休息 基本路径,并将在此基本路径下提供。

所以如果你的基本路径是“/api”并且你正在使用@RepositoryRestController

你必须在 @RequestMapping 中省略 "/api"

【讨论】:

【参考方案4】:

如果您使用的是 Java 8,我找到了一个巧妙的解决方案 - 只需在接口中使用默认方法

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

【讨论】:

这将覆盖此存储库的整个应用程序的 save 方法。如果这不是所需的行为,则不应使用,否则它是一个有效的选项。

以上是关于Spring Data REST:覆盖控制器上的存储库方法的主要内容,如果未能解决你的问题,请参考以下文章

spring-data-rest 和控制器,使用相同的 objectMaper 进行序列化/反序列化

Spring Data JPA - 使用 REST 调用上的查询参数从方法名称生成查询

我可以让自定义控制器镜像 Spring-Data-Rest / Spring-Hateoas 生成的类的格式吗?

通过 REST 控制器使用 Spring Data JPA 和 QueryDsl 的异常

Spring Data REST 基本路径的更改不涉及自定义控制器

Spring Data REST 似乎不适用于 elasticsearch