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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/36773171/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-03 01:51:03  来源:igfitidea点击:

Spring Data REST: Override repository method on the controller

javaspringrestspring-data-rest

提问by Nicolas

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

我有以下 REST 存储库,其实现由 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.

这意味着我将有 save()、find()、exists() 和其他可用的方法,并通过 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:

现在,我想覆盖其中一种方法;例如,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);
    }

}

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

问题:如果我启用此控制器,则 Spring 实现的所有其他方法都不再公开。因此,例如,我不能再向 /foo/1 发出 GET 请求

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

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

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

  1. 这个问题似乎非常相似: Spring Data Rest: Override Method in RestController with same request-mapping-path...但我不想将路径更改为 /foo/1/save

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

  3. Spring Data 文档的这部分内容如下:

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

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

所以看起来它应该是开箱即用的,但不幸的是不是。

回答by Marc Tarin

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

有没有办法在保留其他自动生成的 Spring 方法的同时覆盖 REST 方法?

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

回答by Daniel Cerecedo

Let's imagine we have an Accountentity:

假设我们有一个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;
    }
}

With an AccountRepositoryexposing its CRUD endpoints on /accounts:

随着AccountRepository揭露它的CRUD端点/accounts

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

And an AccountControllerthat overrides the default GETendpoint form AccountRepository.:

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

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

如果您调用 ,GET /accounts?size=5&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
  }
}

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

为了完整起见,可以使用以下父级和依赖项配置 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>

回答by azous

Just an update that I found that saved my life. As said brilliantly by @mathias-dpunkt in this answer https://stackoverflow.com/a/34518166/2836627

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

Most importantly the RepositoryRestController is aware of the spring data rest base path and will be served under this base path.

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

So if your base path is "/api" and you are using @RepositoryRestController

因此,如果您的基本路径是“/api”并且您正在使用 @RepositoryRestController

you have to ommit "/api" from @RequestMapping

你必须从@RequestMapping 中省略“/api”

回答by jsannn

I found a neat solution if you are using Java 8 - just use default methods in interface

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

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