# 22 — Sorting [← Previous: 21 — Aggregate functions](21-aggregate-functions.md) | [Next: 23 — Pagination →](23-pagination.md) | [Back to README →](../README.md) Backs the rewrite of ankurm.com post 4889 (sorting). ## `@OrderBy` names the property, not the column ```java @Column(name = "song_title") private String title; ``` ```java @OneToMany(mappedBy = "playlist") @OrderBy("title asc") private List songs; ``` `title` is the entity property; the column it's stored under is `song_title`, deliberately different from the property name. Hibernate resolves `@OrderBy`'s value against the entity's metamodel, not the mapped table, so it translates the property to the right column itself — a raw column name here would be a coincidence at best. [`Playlist.java`](../src/main/java/com/ankurm/hibernatedemo/sorting/Playlist.java) — [test](../src/test/java/com/ankurm/hibernatedemo/sorting/SortingTest.java) — [output](output/22-orderby-property-name.txt) ## `@SortNatural` and `@SortComparator` on element collections ```java @SortNatural private SortedSet tags = new TreeSet<>(); @SortComparator(LengthThenAlphaComparator.class) private SortedSet genres = new TreeSet<>(new LengthThenAlphaComparator()); ``` Both rebuild a real `java.util.TreeSet` in memory when the collection is loaded — this is not an `ORDER BY` added to the collection's own SQL fetch. `@SortComparator`'s class needs a no-arg constructor; Hibernate instantiates it by reflection. [`Playlist.java`](../src/main/java/com/ankurm/hibernatedemo/sorting/Playlist.java), [`LengthThenAlphaComparator.java`](../src/main/java/com/ankurm/hibernatedemo/sorting/LengthThenAlphaComparator.java) — [output](output/22-sort-natural-and-comparator.txt) ## Dynamic sorting: the injection risk, and the fix Concatenating a caller-supplied field name directly into an `order by` clause hands that caller a way to inject arbitrary HQL — a path onto an unrelated entity, a nested expression, or simply a string that breaks the query as a denial-of-service. The fix is a whitelist checked *before* the string ever reaches the query, not an attempt to sanitize it: ```java private static final Set ALLOWED = Set.of("title", "artist", "rating"); public static String toHqlPropertyOrThrow(String requested) { if (!ALLOWED.contains(requested)) { throw new IllegalArgumentException(...); } return requested; } ``` [`SongSortField.java`](../src/main/java/com/ankurm/hibernatedemo/sorting/SongSortField.java) — [test](../src/test/java/com/ankurm/hibernatedemo/sorting/SortingTest.java) — [output](output/22-dynamic-injection-guard.txt) ## Criteria `Order` across a join `Order` is not limited to the query root's own columns — a joined entity's property works the same way: ```java Join playlistJoin = root.join("playlist"); cq.select(playlistJoin.get("name")).orderBy(cb.asc(playlistJoin.get("name"))); ``` [Test](../src/test/java/com/ankurm/hibernatedemo/sorting/SortingTest.java) — [output](output/22-criteria-order-join.txt) ## Null precedence via `jakarta.persistence.criteria.Nulls` Jakarta Persistence 3.2 (Hibernate 7's baseline) added `jakarta.persistence.criteria.Nulls` (`FIRST`, `LAST`, `NONE`) and the matching `CriteriaBuilder.asc(Expression, Nulls)` / `.desc(Expression, Nulls)` overloads — confirmed with `javap` against `jakarta.persistence-api-3.2.0.jar`, not assumed from documentation prose: ```java cq.orderBy(cb.asc(root.get("rating"), Nulls.LAST), cb.asc(root.get("title"))); ``` This makes null precedence explicit in the generated SQL's `ORDER BY`, independent of whatever a given dialect's own default null-ordering would otherwise do. [Test](../src/test/java/com/ankurm/hibernatedemo/sorting/SortingTest.java) — [output](output/22-null-precedence.txt) ## Case-insensitive sorting via `cb.lower()` ```java cq.orderBy(cb.asc(cb.lower(root.get("title")))); ``` The comparison happens on the lower-cased *value*, computed by the database, not on the raw column — `"Apple"` sorts before `"banana"` despite the capital letter. [Test](../src/test/java/com/ankurm/hibernatedemo/sorting/SortingTest.java) — [output](output/22-case-insensitive.txt) ## Going deeper - `@OrderBy` with no value defaults to the collection's primary key, ascending — easy to miss when a collection appears correctly ordered by accident and then isn't after a schema change. - A `SortedMap` supports the same `@SortNatural`/`@SortComparator` pair as a `SortedSet`, sorting by key. - [Jakarta Persistence 3.2 specification, §7.2 (`Nulls`, `Order`)](https://jakarta.ee/specifications/persistence/3.2/)