Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Returning a list of projection from a custom JpaRepository method with Spring Data Rest

I have a simple Spring Boot application that has Spring Data JPA and Spring Data Rest modules.

Spring Data Rest automatically exposes JPA Repositories but when I send an HTTP GET request to a custom search method in the repository which returns a List of projection I get Couldn't find PersistentEntity for type class com.sun.proxy.$Proxy117!. When I change the return type of the method from list of projections to just a projection it just works fine.

Person Entity class

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

    @Column
    private String name;

    @Column
    private String surname;
}

Person Repository

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

Projection

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

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

Stack trace:

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]

There is an example project at https://github.com/mahsumdemir/projection-demo to show the error. After starting project go to http://localhost:8080/persons/search/findByNameContains?name=i url and you will see the error.

UPDATE

I want to return List<NameProjection> because Spring Data JPA uses the return type of repository method to determine the SQL that will be executed on the database. Calling a method with return type of List<Person> executes select * from person; on database. But calling a method with return type of List<NameProjection> executes select name from person; on the database. The extra data gathered from the database is not a problem in case of Person entity. But it is a big problem while operating on an entity which has multimedia columns.

UPDATE 2

Related jira issue: https://jira.spring.io/browse/DATAREST-1237

like image 917
mahsum Avatar asked Apr 26 '18 20:04

mahsum


2 Answers

Spring Data REST allows multiple projections. And you can pass the desired projection via an url-parameter

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

so in your Repository you don't need to retrun NameProjection, just return Person

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

and call /persons/search/findByNameContains?name=a&projection=nameProjection and you should be fine.

have a look at this demo

like image 110
Dirk Deyne Avatar answered Oct 16 '22 07:10

Dirk Deyne


I found a a workaround, quite ugly but works. I created the projection as an Entity instead of interface (statistic of my event)

Then I created a legit repo for this new class, but never uses it as a repo (just autogenerates table but nothing in it)

I was doing a group by to get statistics for one item, like this I can get the result as I wanted it

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

}
like image 1
olivier Avatar answered Oct 16 '22 06:10

olivier