vdubus
vdubus

Reputation: 456

How to sort a list of entities on the property from a collection of entities

I have an entity Mission which has a collection of entities Trip, and I would like to be able to sort the mission in function of the content in the collection of Trip.

The Trip entity:

@Entity
public Class Trip {
    @Id private Long id;
    @Column private LocalDateTime time;
}

The Mission entity:

@Entity
public Class Mission {
    @Id private Long id;
    @Column private String city;
    @OneToMany(mappedBy = "mission")
    private Collection<Trip> trips;
}

The Mission Repository:

@Repository
public interface MissionRepository extends PagingAndSortingRepository<Mission, Long>, QuerydslPredicateExecutor<Mission>, QuerydslBinderCustomizer<QMission> {

    @Override
    default void customize(final QuerydslBindings bindings, final QMission mission) {
        bindings.bind(String.class).first((final StringPath path, final String value) -> path.containsIgnoreCase(value));
        bindings.including(mission.city);
        bindings.excludeUnlistedProperties(true);
    }

    Page<T> findAll(Predicate predicate, Pageable pageable);
}

The Mission Service:

@Service
@Transactional
public class MissionService {

    private final MissionRepository missionRepository;

    @Autowired
    public MissionService(final MissionRepository missionRepository) {
        this.missionRepository = missionRepository;
    }

    public Page<Mission> findAll(final Predicate predicate, final Pageable pageable) {
        return this.missionRepository.findAll(predicate, pageable);
    }
}

The Mission Controller:

@RestController
@RequestMapping("/api/missions")
public class MissionController {

    private final MissionService missionService;

    @Autowired
    public MissionController(final MissionService missionService) {
        this.missionService = missionService;
    }

    @GetMapping
    @ResponseBody
    public Page<Mission> getAllMissions(@QuerydslPredicate(root = Mission.class) final Predicate predicate, @PageableDefault final Pageable pageable) {
        return this.missionService.findAll(predicate, pageable);
    }
}

When calling /api/missions?sort=city,DESC, it work perfectly. But when calling /api/missions?sort=trips.time,DESC I am getting duplicate entries which match the number of Trips linked with a Mission. All these mission are correctly sorted, but the duplicate part is just a no go…

The objective is to be able to sort the Mission (ASC or DESC) in function of the departure date from the Trip collection (which would be the first Trip sorted in ASC).

So, how to sort this list of Mission on the content from the collection of Trip without getting the duplication part?

I would also like to avoid changing the REST method signature or the database structure, if possible.

I was thinking of adding a DISTINCT on the list, but It wouldn't work as the sorting need to be on the first Trip's time field. Sorting in ASC mode would work. But in DESC, it would then be sorted in function of the arrival date, and not the departure…

A crazy idea: would it be possible to automatically fill a transient property in the mission with the content of a query, and sort on it using the Pageable class?

Upvotes: 1

Views: 1952

Answers (1)

vdubus
vdubus

Reputation: 456

1 – First option

I was able to fix my problem by using the @Formula annotation from Hibernate.

1.1 – Adding the following property inside the Mission entity

@Formula("(select min(t.time) from Trip t where t.mission_id = id)")
private LocalDateTime startDate;

1.2 – Sorting on the property

We just need to call the property.

/api/missions?sort=startDate,DESC.


2 – Second option

Currently I don't have any performance issue. Yet, just in case, I am also considering to use a view which will then be linked to my entity.

NB: This one wasn't test, but is based on my understanding from the following blog.

2.1 – Create the view based on tables Trip and Mission

CREATE OR REPLACE VIEW mission_derived AS
select 
  m.`id` as id,
  min(t.`time`) as startDate
from `trip` t
inner join `mission` m on t.`mission_id` = m.`id`
group by m.`id`;

2.2 – Create the java entity matching this view

@Entity
@Immutable
public class MissionDerived {

  @Id
  private long id;

  @Column
  private Date startDate;

}

2.3 – Adding the property to my Mission entity

@OneToOne
@PrimaryKeyJoinColumn(name="ID")
private MissionDerived derived;

2.4 – Sorting using this property

We would then need to call the property from the mission, and then the property from the derived entity.

/api/missions?sort=derived.startDate,DESC

Upvotes: 1

Related Questions