Ich versuche, meinem Spring Data-Repository PersonRepository
benutzerdefinierte Methoden hinzuzufügen, wie in 1.3 Benutzerdefinierte Implementierungen für Spring Data-Repositorys beschrieben und diese Methode durch REST verfügbar machen. Der ursprüngliche Code stammt von Zugriff auf JPA-Daten mit REST sample, hier ist der Code für hinzugefügte/modifizierte Klassen:
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);
}
Wenn ich die Anwendung starte und http://localhost:8080/portfolio/search/
besuche, erhalte ich den folgenden Antworttext:
{
"_links" : {
"findByLastName" : {
"href" : "http://localhost:8080/people/search/findByLastName{?name}",
"templated" : true
}
}
}
Warum ist findByFirstName
nicht verfügbar, auch wenn sie in der PersonRepository
-Schnittstelle verfügbar ist?
Gibt es auch eine Möglichkeit, dynamisch/programmgesteuert über REST freigelegte Ressorts hinzuzufügen?
Der Grund dafür, dass diese Methoden nicht verfügbar gemacht werden, besteht darin, dass Sie grundsätzlich beliebige Elemente in benutzerdefinierten Repository-Methoden implementieren können. Daher ist es unmöglich, über die richtige HTTP-Methode zu entscheiden, die für diese bestimmte Ressource unterstützt wird.
In Ihrem Fall ist es in Ordnung, ein einfaches GET
zu verwenden. In anderen Fällen muss es sich um ein POST
handeln, da die Ausführung der Methode Nebenwirkungen hat.
Die aktuelle Lösung hierfür besteht darin, einen benutzerdefinierten Controller zu erstellen, um die Repository-Methode aufzurufen.
Nach zwei Tagen habe ich auf diese Weise gelöst.
Benutzerdefinierte Repository-Schnittstelle:
public interface PersonRepositoryCustom {
Page<Person> customFind(String param1, String param2, Pageable pageable);
}
Benutzerdefinierte Repository-Implementierung
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);
}
Bean Resource Darstellung
public class PersonResource extends org.springframework.hateoas.Resource<Person>{
public PersonResource(Person content, Iterable<Link> links) {
super(content, links);
}
}
Resource Assembler
@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));
}
}
Custom Spring MVC Controller
@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;
}
}
Für GET
-Methoden habe ich den folgenden Ansatz verwendet:
@Query
-Methode im Repository (LogRepository.Java).Bei diesem Ansatz muss ich keine Projektionen verwalten und Ressourcen zusammenstellen.
@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);
}
In der Implementierung können Sie die Repository-Methoden verwenden oder direkt zur Persistenzschicht wechseln:
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);
}
Die Antwort ist, dass Sie die Anweisungen nicht befolgt haben. Ihre PersonRepository
muss sowohl PagingAndSortingRepository<Person, Long>
als auch PersonRepositoryCustom
erweitern, um das zu erreichen, wonach Sie suchen. Siehe https://docs.spring.io/spring-data/data-jpa/docs/current/reference/html/#repositories.custom-implementations
Eine weitere Option, die wir verwendet haben, ist die Implementierung einer benutzerdefinierten Repository-Factory für Ihren spezifischen Speichertyp.
Sie können eine Erweiterung von RepositoryFactoryBeanSupport
durchführen, Ihre eigene PersistentEntityInformation
erstellen und CRUD-Operationen in einem Standard-Repo-Impl für Ihren benutzerdefinierten Datenspeichertyp erledigen. Siehe beispielsweise JpaRepositoryFactoryBean
. Möglicherweise müssen Sie insgesamt etwa 10 Klassen implementieren, die dann wiederverwendbar werden.
Versuchen Sie es mit
class PersonRepositoryCustomImpl implements PersonRepositoryCustom, InitializingBean {
...
}
Der implementierende Klassenname sollte PersonRepositoryCustomImpl
anstelle von PersonRepositoryImpl
sein.