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
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
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);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With