使用Spring Data Rest返回自定义JpaRepository方法的投影列表

时间:2018-04-26 20:46:11

标签: java spring spring-data-jpa spring-data-rest

我有一个简单的Spring Boot应用程序,它有Spring Data JPA和Spring Data Rest模块。

Spring Data Rest会自动公开JPA存储库,但是当我向存储库中的自定义搜索方法发送HTTP GET请求时,会返回投影列表,我得到Couldn't find PersistentEntity for type class com.sun.proxy.$Proxy117!。当我将方法的返回类型从投影列表更改为仅投影时,它就可以正常工作。

人物实体类

@Entity
public class Person {
    @Id
    @GeneratedValue
    private Long id;

    @Column
    private String name;

    @Column
    private String surname;
}

人员存储库

public interface PersonRepository extends JpaRepository<Person, Long> {
    List<NameProjection> findByNameContains(@Param(value = "name") String name);
}

投影

@Projection(name = "nameProjection", types = {Person.class})
public interface NameProjection {
    String getName();
}

网址:http://localhost:8080/persons/search/findByNameContains?name=a

堆栈追踪:

java.lang.IllegalArgumentException: Couldn't find PersistentEntity for type class com.sun.proxy.$Proxy117!
    at org.springframework.data.mapping.context.PersistentEntities.lambda$getRequiredPersistentEntity$2(PersistentEntities.java:78) ~[spring-data-commons-2.0.6.RELEASE.jar:2.0.6.RELEASE]
    at java.util.Optional.orElseThrow(Optional.java:290) ~[na:1.8.0_161]
    at org.springframework.data.mapping.context.PersistentEntities.getRequiredPersistentEntity(PersistentEntities.java:77) ~[spring-data-commons-2.0.6.RELEASE.jar:2.0.6.RELEASE]
    at org.springframework.data.rest.webmvc.PersistentEntityResourceAssembler.wrap(PersistentEntityResourceAssembler.java:72) ~[spring-data-rest-webmvc-3.0.6.RELEASE.jar:3.0.6.RELEASE]
    at org.springframework.data.rest.webmvc.PersistentEntityResourceAssembler.toResource(PersistentEntityResourceAssembler.java:55) ~[spring-data-rest-webmvc-3.0.6.RELEASE.jar:3.0.6.RELEASE]
    at org.springframework.data.rest.webmvc.AbstractRepositoryRestController.entitiesToResources(AbstractRepositoryRestController.java:110) ~[spring-data-rest-webmvc-3.0.6.RELEASE.jar:3.0.6.RELEASE]
    at org.springframework.data.rest.webmvc.AbstractRepositoryRestController.toResources(AbstractRepositoryRestController.java:80) ~[spring-data-rest-webmvc-3.0.6.RELEASE.jar:3.0.6.RELEASE]
    at org.springframework.data.rest.webmvc.RepositorySearchController.lambda$toResource$1(RepositorySearchController.java:209) ~[spring-data-rest-webmvc-3.0.6.RELEASE.jar:3.0.6.RELEASE]
    at java.util.Optional.map(Optional.java:215) ~[na:1.8.0_161]
    at org.springframework.data.rest.webmvc.RepositorySearchController.toResource(RepositorySearchController.java:206) ~[spring-data-rest-webmvc-3.0.6.RELEASE.jar:3.0.6.RELEASE]
    at org.springframework.data.rest.webmvc.RepositorySearchController.executeSearch(RepositorySearchController.java:190) ~[spring-data-rest-webmvc-3.0.6.RELEASE.jar:3.0.6.RELEASE]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_161]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_161]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_161]
    at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_161]
    at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:209) ~[spring-web-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:136) ~[spring-web-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:102) ~[spring-webmvc-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:877) ~[spring-webmvc-5.0.5.RELEASE.jar:5.0.5.RELEASE]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:783) ~[spring-webmvc-5.0.5.RELEASE.jar:5.0.5.RELEASE]

https://github.com/mahsumdemir/projection-demo处有一个示例项目来显示错误。启动项目后,转到http://localhost:8080/persons/search/findByNameContains?name=i网址,您将看到错误。

更新

我想返回List<NameProjection>,因为Spring Data JPA使用存储库方法的返回类型来确定将在数据库上执行的SQL。调用返回类型为List<Person>的方法会在数据库上执行select * from person;。但是调用返回类型为List<NameProjection>的方法会在数据库上执行select name from person;。在Person实体的情况下,从数据库收集的额外数据不是问题。但是,在具有多媒体列的实体上运行时,这是一个大问题。

更新2

相关的jira问题:https://jira.spring.io/browse/DATAREST-1237

2 个答案:

答案 0 :(得分:1)

Spring Data REST 允许多个投影。您可以通过url-parameter

传递所需的投影

示例: / persons / search / nickname?name = r&amp; projection = surname

所以在您的知识库中,您不需要重新 NameProjection ,只需返回

 public interface PersonRepository extends JpaRepository<Person, Long> {
  List<Person> findByNameContains(@Param(value = "name") String name);
 }

并致电 / persons / search / findByNameContains?name = a&amp; projection = nameProjection ,你应该没问题。

查看此demo

答案 1 :(得分:0)

作为解决方法,您可以通过如下所示的辅助控制器来调用repo方法。

@RestController
@RequestMapping("/api")
public class PersonsController {

    @Autowired
    private PersonRepository repo;

    @GetMapping("/persons")
    public List<NameProjection> findByNameContains(@RequestParam(name="name") String name) {
        return repo.findByNameContains(name);
    }

}

呼叫http://localhost:8080/api/persons?name=a会得到没有HATEOAS东西的响应(有时可能有用)

[
  {
    "name": "Adam"
  },
  {
    "name": "Anna"
  }
]

您可以使用@RestResource(exported = false)标记存储库方法,以免通过Spring Data Repository接口直接(调用/persons/search/...导出该方法)。