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