java - example - spring data rest custom controller
Implementando métodos personalizados del repositorio de Spring Data y exponiéndolos a través de REST (5)
Después de dos días, lo he resuelto de esta manera.
Interfaz de repositorio personalizado:
public interface PersonRepositoryCustom {
Page<Person> customFind(String param1, String param2, Pageable pageable);
}
Implementación de repositorio personalizado
public class PersonRepositoryImpl implements PersonRepositoryCustom{
@Override
public Page<Person> customFind(String param1, String param2, Pageable pageable) {
// custom query by mongo template, entity manager...
}
}
Repositorio de datos de primavera:
@RepositoryRestResource(collectionResourceRel = "person", path = "person")
public interface PersonRepository extends MongoRepository<Person, String>, PersonRepositoryCustom {
Page<Person> findByName(@Param("name") String name, Pageable pageable);
}
Representación de recursos de frijoles
public class PersonResource extends org.springframework.hateoas.Resource<Person>{
public PersonResource(Person content, Iterable<Link> links) {
super(content, links);
}
}
Ensamblador de recursos
@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));
}
}
Controlador Custom Spring MVC
@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;
}
}
Estoy intentando agregar métodos personalizados a mi repositorio de datos de Spring PersonRepository
como se describe en 1.3 Implementaciones personalizadas para repositorios de Spring Data y exponer estos métodos a través de REST. El código inicial es de Acceso a datos JPA con muestra REST , aquí está el código para clases añadidas / modificadas:
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);
}
Cuando ejecuto la aplicación y visito http://localhost:8080/portfolio/search/
, obtengo el siguiente cuerpo de respuesta:
{
"_links" : {
"findByLastName" : {
"href" : "http://localhost:8080/people/search/findByLastName{?name}",
"templated" : true
}
}
}
¿Por qué findByFirstName
no está expuesto incluso si está disponible en la interfaz de PersonRepository
?
Además, ¿hay alguna forma de agregar repositorios dinámicamente / programáticamente a través de REST?
La razón por la que estos métodos no están expuestos es que básicamente eres libre de implementar lo que quieras en los métodos de repositorio personalizados y, por lo tanto, es imposible razonar sobre el método HTTP correcto para admitir ese recurso en particular.
En su caso, podría estar bien usar un GET
simple, en otros casos podría tener que ser un POST
ya que la ejecución del método tiene efectos secundarios.
La solución actual para esto es crear un controlador personalizado para invocar el método de repositorio.
La respuesta es que no has seguido las instrucciones. Su PersonRepository
debe ampliar tanto PagingAndSortingRepository<Person, Long>
AND PersonRepositoryCustom
para lograr lo que PersonRepositoryCustom
. Ver https://docs.spring.io/spring-data/data-jpa/docs/current/reference/html/#repositories.custom-implementations
Otra opción que utilizamos también es implementar una fábrica de repositorio personalizada para su tipo de almacenamiento específico.
Puede extender desde RepositoryFactoryBeanSupport
, crear su propia PersistentEntityInformation
y ocuparse de las operaciones de CRUD en una impl de repo predeterminada para su tipo de almacenamiento de datos personalizado. Ver JpaRepositoryFactoryBean
por ejemplo. Quizás necesites implementar unas 10 clases en total, pero luego se vuelve reutilizable.
Para los métodos GET
, he usado el siguiente enfoque:
- crear un método
@Query
ficticio en el Repositorio (LogRepository.java) - crear una interfaz personalizada con el mismo método declarado (LogRepositoryCustom.java)
- crear una implementación de la interfaz personalizada (LogRepositoryImpl.java)
Al usar este enfoque, no tengo que administrar las proyecciones y el ensamblaje de recursos.
@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);
}
En la implementación, puede usar los métodos de repositorio o ir directamente a la capa de persistencia:
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);
}