JPQL Query Sort by average rating then box office value for movie

Viewed 172

Ok so I have two JPA Entities a Movie and a MovieRating. Similar to the below

@Entity
@Table(name = Movie.TABLE_NAME)
public class Movie {

    static final String TABLE_NAME = "Movies";

    @Id
    @Column(name = "IMDB_ID")
    private String imdbID;

    @Column(name = "BOX_OFFICE_TAKINGS")
    private int boxOfficeTakings;

    @OneToMany(mappedBy = "movie", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<MovieRating> ratings = new ArrayList<>();

    // Getters and setters

}

And

@Entity
@Table(name = MovieRating.TABLE_NAME)
public class MovieRating {

    static final String TABLE_NAME = "MovieRatings";

    @Id
    @GeneratedValue(generator = "UUID")
    @GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
    private UUID id;

    private int rating;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "IMDB_ID")
    private Movie movie;

    //Getters and setters
}

I've written a @Query in the JPA Repository that should return results ordered by average rating descending and then box office value descending eg.

@Query("SELECT m FROM Movie m LEFT JOIN m.ratings r GROUP BY m ORDER BY AVG(r.rating) desc, m.boxOfficeTakings desc")
List<Movie> findTop10OrderedByBoxOfficeTakings(Pageable pageable);

So I'd expect for example a rated movie with a lower box office value to appear above an unrated movie with a higher box office value.

So I've written a test around this and it seems to work like I'd expect

@Test
public void testGetTop10ReturnsRatedMovieAboveUnratedMovieWithHigherValue() {
    Movie movie1 = createMovieWithRatings(new Movie("tt000001", "The Godfather part 1", 2010, 268500000), null);
    Movie movie2 = createMovieWithRatings(new Movie("tt000002", "The Godfather part 2", 2010, 93000000),
            Arrays.asList(new MovieRating(10)));
    
    movieRepository.saveAll(Arrays.asList(movie1, movie2));
    
    List<MovieResponse> top10 = movieService.getTop10OrderedByBoxOfficeTakings();
    
    List<String> expectedMovieIds = Arrays.asList(movie2.getImdbID(), movie1.getImdbID());

    List<String> actualMovieIds = top10.stream().map(movie -> movie.getImdbID()).collect(Collectors.toList());

    Assertions.assertEquals(expectedMovieIds, actualMovieIds);
}

private Movie createMovieWithRatings(Movie movie, List<MovieRating> ratings) {
    if (ratings != null) {
        ratings.forEach(rating -> movie.addRating(rating));
    }
    return movie;
}

However in practice when the code is running the order is movie1, and then movie2. Movie2 only moves above movie1 when it receives more than 1 rating. Why?

1 Answers

Maybe because you are doing a LEFT JOIN and with LEFT JOIN anything you try to join on if doesn't have a value, in your case "The Godfather part 1" doesn't have any ratings so comes as null. try putting a null check in the query itself and if it is null replace with 0, so now it can calculate the AVG for each movie. That might help.

Related