J'essaie d'ajouter des méthodes personnalisées à mon référentiel Spring Data PersonRepository
comme décrit dans 1.3 Implémentations personnalisées pour les référentiels Spring Data et d'exposer ces méthodes via REST. Le code initial provient de Accès aux données JPA avec REST sample, voici le code des classes ajoutées/modifiées:
interface PersonRepositoryCustom {
List<Person> findByFistName(String name);
}
class PersonRepositoryImpl implements PersonRepositoryCustom, InitializingBean {
@Override
public void afterPropertiesSet() throws Exception {
// initialization here
}
@Override
public List<Person> findByFistName(String name) {
// find the list of persons with the given firstname
}
}
@RepositoryRestResource(collectionResourceRel = "people", path = "people")
public interface PersonRepository extends PagingAndSortingRepository<Person, Long> {
List<Person> findByLastName(@Param("name") String name);
}
Lorsque j'exécute l'application et que je visite http://localhost:8080/portfolio/search/
, le corps de la réponse suivant s'affiche:
{
"_links" : {
"findByLastName" : {
"href" : "http://localhost:8080/people/search/findByLastName{?name}",
"templated" : true
}
}
}
Pourquoi findByFirstName
n'est pas exposé même s'il est disponible dans l'interface PersonRepository
?
De même, existe-t-il un moyen d'ajouter de manière dynamique/par programme des répertoires à exposer via REST?
La raison pour laquelle ces méthodes ne sont pas exposées est que vous êtes fondamentalement libre d'implémenter ce que vous voulez dans les méthodes de référentiel personnalisé. Il est donc impossible de déterminer la méthode HTTP appropriée pour prendre en charge cette ressource particulière.
Dans votre cas, il pourrait être judicieux d’utiliser une GET
plaine, dans d’autres cas, il pourrait s’agir d’une POST
car l’exécution de la méthode a des effets secondaires.
La solution actuelle consiste à créer un contrôleur personnalisé pour appeler la méthode de référentiel.
Après deux jours, j'ai résolu de cette façon.
Interface de référentiel personnalisé:
public interface PersonRepositoryCustom {
Page<Person> customFind(String param1, String param2, Pageable pageable);
}
Implémentation d'un référentiel personnalisé
public class PersonRepositoryImpl implements PersonRepositoryCustom{
@Override
public Page<Person> customFind(String param1, String param2, Pageable pageable) {
// custom query by mongo template, entity manager...
}
}
Spring Data Repository:
@RepositoryRestResource(collectionResourceRel = "person", path = "person")
public interface PersonRepository extends MongoRepository<Person, String>, PersonRepositoryCustom {
Page<Person> findByName(@Param("name") String name, Pageable pageable);
}
Représentation de la ressource de haricot
public class PersonResource extends org.springframework.hateoas.Resource<Person>{
public PersonResource(Person content, Iterable<Link> links) {
super(content, links);
}
}
Assembleur de ressources
@Component
public class PersonResourceAssembler extends ResourceAssemblerSupport<Person, PersonResource> {
@Autowired
RepositoryEntityLinks repositoryEntityLinks;
public PersonResourceAssembler() {
super(PersonCustomSearchController.class, PersonResource.class);
}
@Override
public PersonResource toResource(Person person) {
Link personLink = repositoryEntityLinks.linkToSingleResource(Person.class, person.getId());
Link selfLink = new Link(personLink.getHref(), Link.REL_SELF);
return new PersonResource(person, Arrays.asList(selfLink, personLink));
}
}
Contrôleur MVC à ressort personnalisé
@BasePathAwareController
@RequestMapping("person/search")
public class PersonCustomSearchController implements ResourceProcessor<RepositorySearchesResource> {
@Autowired
PersonRepository personRepository;
@Autowired
PersonResourceAssembler personResourceAssembler;
@Autowired
private PagedResourcesAssembler<Person> pagedResourcesAssembler;
@RequestMapping(value="customFind", method=RequestMethod.GET)
public ResponseEntity<PagedResources> customFind(@RequestParam String param1, @RequestParam String param2, @PageableDefault Pageable pageable) {
Page personPage = personRepository.customFind(param1, param2, pageable);
PagedResources adminPagedResources = pagedResourcesAssembler.toResource(personPage, personResourceAssembler);
if (personPage.getContent()==null || personPage.getContent().isEmpty()){
EmbeddedWrappers wrappers = new EmbeddedWrappers(false);
EmbeddedWrapper wrapper = wrappers.emptyCollectionOf(Person.class);
List<EmbeddedWrapper> embedded = Collections.singletonList(wrapper);
adminPagedResources = new PagedResources(embedded, adminPagedResources.getMetadata(), adminPagedResources.getLinks());
}
return new ResponseEntity<PagedResources>(adminPagedResources, HttpStatus.OK);
}
@Override
public RepositorySearchesResource process(RepositorySearchesResource repositorySearchesResource) {
final String search = repositorySearchesResource.getId().getHref();
final Link customLink = new Link(search + "/customFind{?param1,param2,page,size,sort}").withRel("customFind");
repositorySearchesResource.add(customLink);
return repositorySearchesResource;
}
}
Pour les méthodes GET
, j'ai utilisé l'approche suivante:
@Query
dans le référentiel (LogRepository.Java)En utilisant cette approche, je n'ai pas à gérer les projections et l'assemblage des ressources.
@RepositoryRestResource(collectionResourceRel = "log", path = "log")
public interface LogRepository extends PagingAndSortingRepository<Log, Long>,
LogRepositoryCustom {
//NOTE: This query is just a dummy query
@Query("select l from Log l where l.id=-1")
Page<Log> findAllFilter(@Param("options") String options,
@Param("eid") Long[] entityIds,
@Param("class") String cls,
Pageable pageable);
}
public interface LogRepositoryCustom {
Page<Log> findAllFilter(@Param("options") String options,
@Param("eid") Long[] entityIds,
@Param("class") String cls,
Pageable pageable);
}
Dans l'implémentation, vous êtes libre d'utiliser les méthodes du référentiel ou d'accéder directement à la couche de persistance:
public class LogRepositoryImpl implements LogRepositoryCustom{
@Autowired
EntityManager entityManager;
@Autowired
LogRepository logRepository;
@Override
public Page<Log> findAllFilter(
@Param("options") String options,
@Param( "eid") Long[] entityIds,
@Param( "class" ) String cls,
Pageable pageable) {
//Transform kendoui json options to Java object
DataSourceRequest dataSourceRequest=null;
try {
dataSourceRequest = new ObjectMapper().readValue(options, DataSourceRequest.class);
} catch (IOException ex) {
throw new RuntimeException(ex);
}
Session s = entityManager.unwrap(Session.class);
Junction junction = null;
if (entityIds != null || cls != null) {
junction = Restrictions.conjunction();
if (entityIds != null && entityIds.length > 0) {
junction.add(Restrictions.in("entityId", entityIds));
}
if (cls != null) {
junction.add(Restrictions.eq("cls", cls));
}
}
return dataSourceRequest.toDataSourceResult(s, Log.class, junction);
}
La réponse est que vous n'avez pas suivi les instructions. Votre PersonRepository
doit étendre à la fois PagingAndSortingRepository<Person, Long>
ET PersonRepositoryCustom
afin d’atteindre ce que vous cherchez. Voir https://docs.spring.io/spring-data/data-jpa/docs/current/reference/html/#repositories.custom-implementations
Une autre option que nous avons également utilisée consiste à implémenter une fabrique de référentiels personnalisée pour votre type de stockage spécifique.
Vous pouvez étendre à partir de RepositoryFactoryBeanSupport
, créer votre propre PersistentEntityInformation
et gérer les opérations CRUD dans une implication de référentiel par défaut pour votre type de stockage de données personnalisé. Voir JpaRepositoryFactoryBean
par exemple. Vous devrez peut-être implémenter environ 10 classes au total, mais cela deviendra réutilisable.
Essayez d'utiliser
class PersonRepositoryCustomImpl implements PersonRepositoryCustom, InitializingBean {
...
}
Le nom de la classe d'implémentation doit être PersonRepositoryCustomImpl
au lieu de PersonRepositoryImpl
.