Categories
JPA Spring-Data

Sorting by a Non-Entity Field

In my work for SceneSat I recently came across the need to sort entries of a table by a field that does not exist in said table but is built on the fly from other fields. Specifically, depending on the status of a show I want to use either the scheduled start time or the actual start time to sort it by.

Turns out, Spring-Data makes this incredibly easy. I already had a JpaRepository for accessing the table (using a Pageable to allow easy paging and sorting from a REST interface) so first I had to add a @Query:

@Query(value =
	"SELECT
		s AS show,
		CASE
			WHEN (s.status = 'scheduled')
       			THEN s.scheduledStart
       		ELSE
       			s.actualStart
       	END AS time
	FROM Show s")
fun findShows(pageable: Pageable): Page<Show>

You can even add additional WHERE or other clauses to the JPQL query, and Spring-Data will happily merge it with whatever you have in the Pageable.

However, now the return value is not really correct anymore. Spring-Data will return a tuple here and you will only realize that once you want to use your Show objects but are suddenly facing a ClassCastException. Also, even if the result was a show you would have now lost the time field (which may or may not be a problem).

JPA allow you to return non-entity data from queries such as these (called “projections” in section 4.8.2 in JSR 338 aka JPA 2.1), and Spring-Data extends this concept with its usual “just give me an interface and I’ll do the rest” magic (which I’m pretty much in awe of after finding out all of this).

interface ShowWithTime {
  val show: Show,
  val time: Date?
}

Now this type can be used as return type for the findShows method:

fun findShows(pageable: Pageable): Page<ShowWithTime>

And, even more miraculously, you can even use the time in your Pageable:

val shows = findShows(PageRequest.of(0, 10, Sort.by(desc("time"))))

I can’t really believe that this feature has managed to evade me for all this time but I’m really glad I stumbled upon it by blind luck!

2 replies on “Sorting by a Non-Entity Field”

With plain Java interface it is important to define projection interface with “get” methods:

interface ShowWithTime {
Show getShow();
Date getTime();
}

And in SELECT use javabeans name “show” like this: “SELECT s AS show, ..”

Wihtout “get” I got “is no access method” errors (trace includes TupleBackedMap).

So DO NOT USE like this in plain java interface:

interface ShowWithTime {
Show show();
Date rime();
}

Yes, absolutely. Luckily when you define a field “show” in Kotlin it turns it into a backing field and an accessor named “getShow” so that JPA is happy. 😄

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.