spring-data-rest icon indicating copy to clipboard operation
spring-data-rest copied to clipboard

Add support for query methods returning a projection [DATAREST-841]

Open spring-projects-issues opened this issue 9 years ago • 4 comments

Oliver Drotbohm opened DATAREST-841 and commented

Currently Spring Data REST does not support returning projections from query methods directly. The feature has been introduced for Spring Data JPA and MongoDB in the Hopper release train and it would be cool to get this working in Spring Data REST, too.

Currently the problem is that PersistentEntityResourceAssembler and a couple of downstream components try to lookup a PersistentEntity for the returned instance's type which fails due to the proxy type being used and not the underlying target class. Even if that's fixed, the question is how we can make sure to be able to create self links properly as the identifier property is not guaranteed to be exposed by the proxy and contrary to the projection mechanism applied by Spring Data REST the proxy might not be backed by the actual domain object.

For DTOs used, we might even run into problems in the MappingContext as e.g. JPA currently only considers managed types a PersistentEntity


Issue Links:

  • DATAREST-1257 Spring Data Projection doesn't work when using with Page ("is duplicated by")
  • DATAREST-1054 Exposing projections from query methods on the repository level ("is duplicated by")
  • DATAJPA-885 No aliases found in result tuple

8 votes, 10 watchers

spring-projects-issues avatar Jun 10 '16 20:06 spring-projects-issues

Sergey Burmin commented

We would be very much obliged, if you could start working on this task as soon as possible

spring-projects-issues avatar Dec 01 '16 14:12 spring-projects-issues

Sergei Poznanski commented

the question is how we can make sure to be able to create self links properly @Oliver, how about apply for DTOs the similar technics as for Repositories?

Create new interface 'Dto', for example:

public interface Dto<T, ID extends Serializable> extends Identifiable<ID> {
    @JsonIgnore
    Class<T> getBaseEntity();
}

Then if we create custom DTO interface (for example Book with its Author, Publisher and ratings):

public interface BookWithRatings extends Dto<Book, Long> {

    @JsonProperty("title")
    default String getTitle() {
        return getBook().getTitle();
    }
    
    @JsonProperty("author")
    default String getAuthorName() {
        return getAuthor().getName();
    }

    @JsonProperty("publisher")
    default String getPublisherName() {
        return getPublisher().getName();
    }

    @JsonIgnore
    Book getBook();

    @JsonIgnore
    Author getAuthor();

    @JsonIgnore
    Publisher getPublisher();

    Double getRating(); // Book rating

    Long getReadings(); // Book reading count
    
    @Override
    default Class<Book> getBaseEntity() {
        return Book.class;
    }

    @JsonIgnore
    @Override
    default Long getId() {
        return getBook().getId();
    }
}

We can get base entity class for this DTO: getBaseEntity(), and get its 'Id': getId(). That allow us to make self link for DTO:

Link selfLink = entityLinks.linkForSingleResource(dto.getBaseEntity(), dto.getId()).withSelfRel();

BookRepo example:

@RepositoryRestResource
public interface BookRepo extends JpaRepository<Book, Long> {

    @RestResource(path = "topRating", rel = "topRating")
    @Query(value = "select r.book as book, r.book.author as author, r.book.publisher as publisher, avg(r.rating) as rating, count(r) as readings from Reading r group by r.book order by rating desc, r.book.title asc",
            countQuery = "select count(b) from Book b")
    Page<BookWithRatings> topRating(Pageable pageable);
}

spring-projects-issues avatar Apr 18 '17 07:04 spring-projects-issues

Hi there, have the same problem with projections. Just looked through the code and got a maybe stupid question, since I'm not getting the whole picture. Thing is, for a search with a single ressource it works. Since the RepositorySearchController is using not the assambler for that but PersistentEntity, ?> entity = information.getPersistentEntity(); (line 209) Why not pass that information in the Assembler as well? Or at least use the annoation on the projection interface to get the entity type for that object you already hv in hands in the assembler? Seems for now a valid way to reuse the whole follow up implementation that should work by now.

Greetings and enjoy your day.

Juhu182 avatar May 25 '22 07:05 Juhu182

Ok. Did a little drilldown. Found some things might be helpful:

in RepositorySearchController the lines: // Returned value is not of the aggregates type - probably some projection if (!entity.getType().isInstance(it)) { return ResponseEntity.ok(it); } Make it work with a projection. This has the impact of not having the HATEOS information within the output. While in the other projection implementations this is included. Imo an inconsistency.

I also tried something for solving the problem quite quickly, to see if i have some result. Passed another PersistentEntities in the PersistentEntityResourceAssemblerArgumentResolver. On getRequiredPersistentEntity I checked the annotation on the passed class. If its a projection I took 1st class from that array, to return entity from that object. (I know not perfect). Did solve the problems except in EmbeddedResourcesAssembler line 63 where final PersistentPropertyAccessor<?> accessor = entity.getPropertyAccessor(instance); is done. There the 2 objects are compared by type and I stopped investigating. Next step would include to override that class, or change functionality there, but I stopped due to noobidity.

Now going back to mute mode.

Juhu182 avatar May 25 '22 12:05 Juhu182