使用Spring Data REST将基础CustomRepository方法公开到REST API

时间:2016-03-09 13:05:37

标签: java spring rest spring-boot spring-data-rest

在我目前正在使用的Spring Boot项目中,我们使用了一个自定义的Spring Data JPA基本存储库,我们在其中添加了一个find()方法,该方法使用了我们的自定义查询功能。根据{{​​3}},我们有CustomRepository扩展CrudRepository,我们有CustomRepositoryImpl扩展SimpleJpaRepository,我们@EnableJpaRepositories(repositoryBaseClass = CustomRepositoryImpl.class) @Configuration {1}}上课。

到目前为止,这么好。在数据方面,一切都很完美。但是,我们在使用Spring Data REST将自定义功能暴露给REST API时遇到了一些问题。有两个主要问题:1)实际上将自定义find()方法公开为REST端点,以及2)将此端点添加到特定存储库级别上的HAL链接。我将详细介绍这两个问题。

1。将自定义方法公开为REST端点

这里的问题是我们显然希望以通用的方式做到这一点。现在,如果我们有一个Foo实体,我们已经创建了一个FooRepository extends CustomRepository<Foo>接口,我们需要创建一个RestController:

@RepositoryRestController
@RequestMapping("/api")
public class FooRestController {

    @Autowired 
    private FooRepository fooRepository;
    @Autowired
    private PagedResourcesAssembler pagedResourcesAssembler;

    @RequestMapping(value = "/foo/find", method = RequestMethod.GET,
            produces = MediaType.APPLICATION_JSON_VALUE)
    @ResponseBody
    public PagedResources<PersistentEntityResource> findEndPoint(
            @RequestParam Map<String, String> params,
            Pageable pageable,
            PersistentEntityResourceAssembler resourceAssembler) throws Exception {

         Page<Foo> page = fooRepository.find(params, pageable);

         return pagedResourcesAssembler.toResource(page, resourcesAssembler);
    }
}

这可以按预期工作。但是,假设我们现在有另一个名为Bar的实体,我们必须创建与上面完全相同的方法,将每个Foo替换为Bar,将每个foo替换为{{1} }}。出于我们项目的目的,这是违反the Spring Documentation的不可接受的行为。

总结第一个问题:我们所有的存储库都会扩展bar,它有一个通用类型CustomRepository方法,其中T是存储库&#39; s实体类型参数。对于扩展我们Page<T> find()的每个存储库,我们需要一个REST端点CustomRepository。如何在不重复每个存储库的情况下实现这一目标。

2。将自定义方法REST端点添加到HAL链接

我目前还没有找到使用Spring Data REST在存储库级别添加HAL链接的正确方法。目前,我了解/api/<the Entity for that repository>/find的三个选项,即ResourceProcessorResource<Entity>RepositoryLinksResource

RepositorySearchesResourceResourceProcessor<Resource<Entity>>调用中返回的链接添加到每个序列化实体。鉴于我们正在使用存储库级process方法,这显然不是我们想要的。

find() 似乎仅将链接添加到api的根级别,但我承认我并不完全确定ResourceProcessor<RepositoryLinksResource>能做什么或不能做什么。如果我是对的,那也不是我们想要的。在根级别,我们希望找到指向RepositoryLinksResource/api/foo的链接,并且只有在/api/bar级别才会看到/api/foo链接。

/api/foo/find似乎最接近,因为它确实添加了到存储库级别的链接,这正是我们正在寻找的。但是,它与Spring机制紧密结合,后者为存储库中定义的ResourceProcessor<RepositorySearchesResource>方法生成实现,这意味着如果您的任何存储库中没有findByProperty方法,{{永远不会调用方法

现在,我们通过黑客攻击最后一个选项来解决这个问题。我们有findByProperty,其中使用已实施的process方法添加了我们的自定义FooRestController implements ResourceProcessor<RepositorySearchesResource>方法。我们通过向process(...)界面添加/api/foo/find方法来强制调用process(我们可以这样做,因为我们的findById(Long id)具有CustomRepository类型参数,并且我们所有的CustomRepository都有T extends BaseEntity)。然而,这是一个非常丑陋的黑客攻击,因为它在BaseEntity中添加了冗余方法和冗余REST端点,它提供了与Long id完全相同的结果。它还要求我们为每个实体做同样的事情,再次严重违反DRY。

总结第二个问题:,鉴于我们在/api/foo/search/findById中有自定义/api/foo/{id}方法,我们希望在HAL中添加find()链接扩展我们CustomRepository的每个存储库的链接。此链接应该在存储库级别(/api/entity/find)上可见,我们不需要重复。

2 个答案:

答案 0 :(得分:0)

  1. 您不必创建存储库接口的实现。您可以使用自己的界面扩展它,您可以在其中添加自定义方法。
  2. public interface EntityRepository extends CrudRepository<Entity, Integer>  { 
       @Query("SELECT e FROM Entity e WHERE e.custom = 1 ")      
       List<Entity> find();
    }
    

    在存储库上使用@RepositoryRestController可为给定的存储库创建端点。 您也可以手动执行此操作,方法是将requestmapping中的foo替换为变量,并使用该变量来确定存储库的类型。

    @RequestMapping(method = RequestMethod.GET, value = "/{repository}/find")
    public Entity find(@PathVariable String repository) {
    
        Entity entity;
    
        switch(repository){
            case "Foo": entity = fooRepository.find();
                break;
            case "Bar": entity = barRepository.find();
        }
    
    1. 为了添加自定义链接,spring-hateoas项目为此提供了很好的静态导入,使用泛型这应该有帮助。例如:
    2. import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
      import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;    
      resource.add(linkTo(methodOn(EntityController.class).getParent(entity.getParentId())).withRel("entityParent"));
      

答案 1 :(得分:0)

你应该删除产品属性。并省略@RequestMapping(“/ api”)Anonation

因为当定义了@RepositoryRestController注释

时,spring数据会默认为控制器添加前缀

我编辑一些代码,就像这样:

@RepositoryRestController
public class FooRestController {

    @Autowired 
    private FooRepository fooRepository;
    @Autowired
    private PagedResourcesAssembler pagedResourcesAssembler;

    @RequestMapping(value = "/foo/find", method = RequestMethod.GET)
    @ResponseBody
    public PagedResources<PersistentEntityResource> findEndPoint(
            @RequestParam Map<String, String> params,
            Pageable pageable,
            PersistentEntityResourceAssembler resourceAssembler) throws Exception {

         Page<Foo> page = fooRepository.find(params, pageable);

         return pagedResourcesAssembler.toResource(page, resourcesAssembler);
    }
}

我使用的是spring boot 2.0版本。