简体   繁体   中英

Exposing base CustomRepository method to REST API using Spring Data REST

In a Spring Boot project I am currently working in, we are using a custom Spring Data JPA base repository, to which we have added a find() method which uses our custom querying functionality. Per the Spring Documentation , we have a CustomRepository which extends CrudRepository , we have a CustomRepositoryImpl which extends SimpleJpaRepository , and we have @EnableJpaRepositories(repositoryBaseClass = CustomRepositoryImpl.class) in our @Configuration class.

So far, so good. On the data side everything is working perfectly. However, we are having some problems exposing our custom functionality to our REST api, using Spring Data REST. There are 2 main problems: 1) actually exposing the custom find() method as a REST endpoint, and 2) adding this endpoint to the HAL links on the specific repository level . I will go into more detail regarding these two problems.

1. Exposing the custom method as a REST endpoint

The problem here is that we obviously wish to do this in a generic way. Right now, if we have, for example, a Foo entity, for which we have created a FooRepository extends CustomRepository<Foo> interface, we need to create a 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);
    }
}

This works as expected. However, suppose we now have another entity called Bar , we have to create the exact same method as above, replacing every Foo with Bar and every foo with bar . For the purposes of our project, this is an unacceptable violation of DRY .

To summarize the first problem : all of our repositories will extend a CustomRepository , which has a generic typed Page<T> find() method, with T being the repository's entity type parameter. For every repository that extends our CustomRepository , we want a REST endpoint to /api/<the Entity for that repository>/find . How would we go about doing this, without repeating ourselves for every single repository.

2. Adding the custom method REST endpoint to the HAL links

I have currently not found the proper way to add HAL links on the repository level, using Spring Data REST. Currently, I am aware of three options for ResourceProcessor , them being Resource<Entity> , RepositoryLinksResource and RepositorySearchesResource .

A ResourceProcessor<Resource<Entity>> adds the links returned in the process call to every single serialized entity. Given that we are working with a repository-level find() method, this is obviously not what we want.

A ResourceProcessor<RepositoryLinksResource> seems to add links only to the root level of the api, though I admit I am not entirely certain what RepositoryLinksResource can or cannot do. If I am correct, then that is also not what we want. At the root level, we want to find links to /api/foo and /api/bar , and only at the /api/foo level should we see the /api/foo/find link.

A ResourceProcessor<RepositorySearchesResource> seems to come closest, as it does add links to the repository level, which is what we are looking for. It is, however, tightly coupled with the Spring mechanic which generates implementations for findByProperty methods defined in the repository, meaning that if you have no findByProperty method in any of your repositories, the process method will never be called .

Right now, we have worked around this by hacking into that last option. We have our FooRestController implements ResourceProcessor<RepositorySearchesResource> , with the implemented process(...) method adding our custom /api/foo/find method. We force process to be called by adding a findById(Long id) method to our CustomRepository interface (we can do this because our CustomRepository has a T extends BaseEntity type parameter, and all of our BaseEntity s have a Long id ). This is a really ugly hack, however, as it adds a redundant method and a redundant REST endpoint in /api/foo/search/findById , which gives the exact same result as /api/foo/{id} . It also requires us to do the exact same thing for every entity, again grossly violating DRY.

To summarize the second problem: given that we have a custom find() method in our CustomRepository we would like to add an /api/entity/find link to the HAL links for every repository extending our CustomRepository . This link should be visible on the repository level ( /api/entity ), and we should not need to repeat ourselves.

  1. You don't have to create an implementation of the repository interface. You extend it with your own interface where you can add custom methods.
public interface EntityRepository extends CrudRepository<Entity, Integer>  { 
   @Query("SELECT e FROM Entity e WHERE e.custom = 1 ")      
   List<Entity> find();
}

Using @RepositoryRestController on a repository creates endpoints for the given repository. You could also do this manually by replacing foo in the requestmapping with a variable and use that variable for to determine the type of the repository.

@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. For adding custom links the spring-hateoas project provides somme nice static imports for that, using generics this should help. For example:
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"));

you should remove produce property. and omit @RequestMapping("/api") Anonation

because spring data rest default add prefix to controller when this defined @RepositoryRestController annotation

i edit some code, like this way:

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

i'm apply spring boot 2.0 version.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM