web-dev-qa-db-fra.com

Mettre en œuvre des méthodes personnalisées du référentiel Spring Data et les exposer via REST

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?

42
bachr

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.

21
Oliver Drotbohm

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

}
22
leo

Pour les méthodes GET, j'ai utilisé l'approche suivante:

  • créer une méthode factice @Query dans le référentiel (LogRepository.Java)
  • créer une interface personnalisée avec la même méthode déclarée (LogRepositoryCustom.Java)
  • créer une implémentation de l'interface personnalisée (LogRepositoryImpl.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);
}
8
Erik Mellegård

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

2
Tommy B

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.

1
aux

Essayez d'utiliser 

class PersonRepositoryCustomImpl implements PersonRepositoryCustom, InitializingBean {
    ...
}
0
Binulal Narayanan

Le nom de la classe d'implémentation doit être PersonRepositoryCustomImpl au lieu de PersonRepositoryImpl.

0
Binulal Narayanan