Java 实现 Spring Data 存储库的自定义方法并通过 REST 公开它们

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/25201306/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-10 23:51:37  来源:igfitidea点击:

Implementing custom methods of Spring Data repository and exposing them through REST

javaspringrestjpaspring-data

提问by bachr

I'm trying to add custom methods to my Spring Data repository PersonRepositoryas described in 1.3 Custom implementations for Spring Data repositoriesand exposing these method through REST. The initial code is from Accessing JPA Data with RESTsample, here is the code for added/modified classes:

我正在尝试将自定义方法添加到我的 Spring Data 存储库中PersonRepository,如1.3 Spring Data 存储库的自定义实现中所述,并通过 REST 公开这些方法。初始代码来自使用 REST示例访问 JPA 数据,这是添加/修改类的代码:

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

When I run the application and visit http://localhost:8080/portfolio/search/, I get the following response body:

当我运行应用程序并访问时http://localhost:8080/portfolio/search/,我得到以下响应正文:

{
  "_links" : {
    "findByLastName" : {
      "href" : "http://localhost:8080/people/search/findByLastName{?name}",
      "templated" : true
     }
  }
}

Why findByFirstNameis not exposed even if it is available in the PersonRepositoryinterface?

为什么findByFirstName即使在PersonRepository接口中可用也不暴露?

Also, is there a way to dynamically/programmatically add respositories to be exposed via REST?

另外,有没有办法动态/以编程方式添加要通过 REST 公开的存储库?

采纳答案by Oliver Drotbohm

The reason these methods are not exposed is that you're basically free to implement whatever you want in custom repository methods and thus it's impossible to reason about the correct HTTP method to support for that particular resource.

这些方法没有公开的原因是您基本上可以自由地在自定义存储库方法中实现您想要的任何内容,因此不可能推断正确的 HTTP 方法来支持该特定资源。

In your case it might be fine to use a plain GET, in other cases it might have to be a POSTas the execution of the method has side effects.

在您的情况下,使用 plainGET可能没问题,在其他情况下,它可能必须是 aPOST因为方法的执行有副作用。

The current solution for this is to craft a custom controller to invoke the repository method.

当前的解决方案是制作一个自定义控制器来调用存储库方法。

回答by aux

Another option we used as well is to implement a custom repository factory for your specific storage type.

我们还使用的另一个选项是为您的特定存储类型实现自定义存储库工厂。

You can extend from RepositoryFactoryBeanSupport, build your own PersistentEntityInformationand take care of CRUD ops in a default repo impl for your custom data storage type. See JpaRepositoryFactoryBeanfor example. You maybe need to implement about 10 classes in total but then it gets reusable.

您可以在自定义数据存储类型的默认 repo impl 中扩展RepositoryFactoryBeanSupport、构建自己的PersistentEntityInformation并处理 CRUD 操作。参见JpaRepositoryFactoryBean示例。您可能总共需要实现大约 10 个类,然后它就可以重用了。

回答by Tommy B

The answer is that you haven't followed instructions. Your PersonRepositoryhas to extend both PagingAndSortingRepository<Person, Long>AND PersonRepositoryCustomin order to achieve what you're after. See https://docs.spring.io/spring-data/data-jpa/docs/current/reference/html/#repositories.custom-implementations

答案是您没有按照说明进行操作。您PersonRepository必须同时扩展PagingAndSortingRepository<Person, Long>ANDPersonRepositoryCustom才能实现您所追求的目标。请参阅https://docs.spring.io/spring-data/data-jpa/docs/current/reference/html/#repositories.custom-implementations

回答by leo

After two days, I have solved in this way.

两天后,我就这样解决了。

Custom Repository Interface:

自定义存储库接口:

public interface PersonRepositoryCustom {
    Page<Person> customFind(String param1, String param2, Pageable pageable);
}

Custom Repository Implementation

自定义存储库实现

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 representation

Bean 资源表示

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

自定义 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;
    }

}

回答by Erik Melleg?rd

For GETmethods I have used the following approach:

对于GET方法,我使用了以下方法:

  • create a dummy @Querymethod in the Repository (LogRepository.java)
  • create a custom interface with the same method declared (LogRepositoryCustom.java)
  • create an implementation of the custom interface (LogRepositoryImpl.java)
  • @Query在 Repository (LogRepository.java) 中创建一个虚拟方法
  • 使用声明的相同方法创建自定义接口 (LogRepositoryCustom.java)
  • 创建自定义接口的实现 (LogRepositoryImpl.java)

Using this approach I don't have to manage projections and resource assembling.

使用这种方法,我不必管理预测和资源组装。

@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 the implementation you are free to use the repository methods or going directly to the persistence layer:

在实现中,您可以自由使用存储库方法或直接进入持久层:

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

回答by Binulal Narayanan

Try using

尝试使用

class PersonRepositoryCustomImpl implements PersonRepositoryCustom, InitializingBean {
    ...
}

回答by Binulal Narayanan

The implementing class name should be PersonRepositoryCustomImplinstead of PersonRepositoryImpl.

实现类名应该是PersonRepositoryCustomImpl而不是PersonRepositoryImpl.