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

6

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

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

Person实体类

@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
3个回答

5

Spring Data REST允许使用多个投影。您可以通过url参数传递所需的投影。

例如:/persons/search/nickname?name=r&projection=surname

因此,在您的Repository中,您不需要返回NameProjection,只需返回Person即可。

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

调用 /persons/search/findByNameContains?name=a&projection=nameProjection 就可以了。

查看 演示


没有Param注释,我会得到“无法检测参数名称。在JDK 8上使用Param或编译时加上-parameters”。我删除了Projection注释并再次尝试,但仍然没有改变。 - mahsum
您提供的项目未使用spring-boot-starter-data-rest项目。使用此项目,存储库方法默认公开,因此我们不需要单独的RestController类。我在https://github.com/mahsumdemir/projection-demo上提供了一个项目。启动项目后,转到http://localhost:8080/persons/search/findByNameContains?name=i网址,您将看到我所说的错误。 - mahsum
我的错,我以为你是指通过rest-controller访问数据,我更正了我的答案,希望能有所帮助。 - Dirk Deyne
我想返回 List<NameProjection>,因为Spring Data JPA使用存储库方法的返回类型来确定将在数据库上执行的SQL。调用返回类型为 List<Person> 的方法会在数据库上执行 select * from person;。但是调用返回类型为 List<NameProjection> 的方法会在数据库上执行 select name from person;。对于Person实体,从数据库中获取的额外数据不是问题。但是,在操作具有多媒体列的实体时,这是一个大问题。 - mahsum
List<NameProjection> executes select name from person; I don't think so, have a try with a @Restcontroler and set property spring.jpa.show-sql=true - Dirk Deyne
是的,就像你说的那样,Spring在我提供的项目中执行的查询没有进行优化。我查看了Spring文档,它说:“如果使用了封闭投影,则Spring Data模块甚至可以优化查询执行,因为我们确切地知道支持投影代理所需的所有属性。” 我更新了项目。现在,它显示了我提到的情况。文档可以在https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projections.interfaces.closed找到。 - mahsum

1
我找到了一个解决方法,非常丑陋但有效。我将投影作为实体而不是接口创建(我的事件统计)。
然后我为这个新类创建了一个合法的仓库,但从未将其用作仓库(只自动生成表格但其中没有任何内容)。
我正在执行分组以获取一个项目的统计信息,这样我就可以得到我想要的结果。
@RepositoryRestResource
public interface IStatisticRepository extends CrudRepository<Statistic, Long> {

    @Query(nativeQuery = true, value = "select rownum() as id, category as category, categorySum as category_sum " +
            "from (select i.category as category, sum(pi.qty) as categorySum " +
            "from item i " +
            "left join participation_item pi on i.id = pi.item_id " +
            "left join participation p on p.id = pi.participation_id " +
            "left join event e on p.event_id = e.id " +
            "where e.id=:eventId " +
            "group by i.category )")
    List<Statistic> getByIdToStatistic(Long eventId);

}

0

作为解决方法,您可以通过类似以下的辅助控制器调用仓库方法。

@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/...调用)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接