Search code examples
springhibernatejpaspring-dataspring-data-rest

How do I dynamically sort nested entities using Spring Data's Sort?


I have the following JPQL query in a Spring Data Repository:

public interface CarRepository extends Repository<Car, Integer> {
    
    @Query("select distinct c.model from Car c where c.id in :ids")
    Set<Model> findDistinctModelByIdIn(@Param("ids") Set<Integer> ids, Sort sort);
}

A client calls the query as follows (which is exposed via Spring Data REST):

http://localhost:8080/api/cars/search/findDistinctModelByIdIn?ids=1,33,55,43&sort=model.name,desc

However, the results are returned unsorted. How can I sort based on the client sort request parameter?

Does Spring only sort on the domain type the repository manages (e.g., only Car not Model)?

Update

Here is my domain model:

@Entity
@Data
public class Car {
    @Id
    private Long id;
    
    @ManyToOne
    private Model model;
}

@Entity
@Data
public class Model {
    @Id
    private Long id;

    private String name;
}
 

Update

After turning on trace for org.springframework.web, I found the following:

2023-02-09T12:20:16.315-06:00 TRACE 21812 --- [io-9006-exec-10] o.s.web.method.HandlerMethod : Arguments: [org.springframework.data.rest.webmvc.RootResourceInformation@6e3e0c99, {ids=[33283,37901], sort=[model.name,desc]}, findDistinctModelByIdIn, DefaultedPageable(pageable=Page request [number: 0, size 20, sort: UNSORTED], isDefault=true), UNSORTED, org.springframework.data.rest.webmvc.PersistentEntityResourceAssembler...

However, when using @Yuriy-Tsarkov project, the following is logged:

2023-02-09T12:16:17.818-06:00 TRACE 22460 --- [nio-8097-exec-1] o.s.web.method.HandlerMethod : Arguments: [org.springframework.data.rest.webmvc.RootResourceInformation@3e78567e, {ids=[33283,37901], sort=[model.name,desc]}, findDistinctModelByIdIn, DefaultedPageable(pageable=Page request [number: 0, size 20, sort: model.name: DESC], isDefault=false), model.name: DESC, org.springframework.data.rest.webmvc.PersistentEntityResourceAssembler...

So, Spring is perceiving some difference even though I'm using the exact same version of dependencies and my code & config from what I can tell is the same.


Solution

  • Summary

    Spring Data will not sort on a nested entity that has its own repository and is exported (which is the default).

    Details

    After further research, I found the answer as to why @yuriy-tsarkov application is able to sort on nested entity but my application is not able.

    Cause of the Problem

    I have a repository for the nested entity, Model:

    public interface ModelRepository extends Repository<Model, Integer> {
        //various method defs    
    }
    

    If you add a ToyRepository to @yuriy-tsarkov application, it will fail to sort Toys returned by his PetRepository just like my application fails to sort Models returned by the CarRepository.

    Why?

    Because once Model (or Toy in the case of yuriy-tsarkov app) has its own repository, it is considered a linkable association. That is, org.springframework.data.rest.webmvc.mapping.Associations.isLinkableAssociation(PersistentProperty<?>) returns true for the model (toy) association. It ultimately returns the boolean from isMapped which is also true :

    @Override
    public boolean isMapped(PersistentProperty<?> property) {
        return repositories.hasRepositoryFor(property.getActualType()) && super.isMapped(property);
    }
    

    This is because repositories.hasRepositoryFor returns true b/c as noted above I have a repository for Model and super.isMapped also returns true. super.isMapped return false only if @RestResource(exported=false) is defined. Since I didn't define that, it return true.

    (I believe this may make sense because my Model might be only represented with links. That said, it does seem like I could still may want those links sorted by the model name.)

    Solution

    In my case I need a repository that manages Model entities. So, I cannot simply remove the ModelRepository.

    I can, however, allow the nested entity prop, model, to not be exported:

    @Entity
    @Data
    public class Car {
        @Id
        private Long id;
        
        @ManyToOne
        @RestResource(exported=false)
        private Model model;
    } 
    

    That allows super.isMapped to return false (because it is not an exported resource). Then sorting does work b/c it's not simply links potentially being returned.

    I feel this solution is a workaround though and that Spring Data Rest should still be able to sort on a attribute even if only links are provided. In my scenario, the repo wasn't providing links anyway (even without the @RestResource).