備忘録として、いくつかの実装例を記載しておく。
Hibernate ORMのソート(Spring Data JPAを使用する)
Spring Bootを使用していなくても、下記のように依存関係を読み込むことでSpring Data JPAを使用できる。
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>${spring-data.version}</version>
</dependency>
クエリメソッドを使用する
ルールに沿ったメソッド名を使用することにより、自動的にHibernateへSQLが発行される。
public interface CrudRepository<T, ID> extends Repository<T, ID> {
// 登録、または更新
<S extends T> S save(S entity);
// プライマリーキーで検索
Optional<T> findById(ID primaryKey);
// 全検索
Iterable<T> findAll();
// 全件数
long count();
// 削除
void delete(T entity);
// 存在チェック
boolean existsById(ID primaryKey);
例えば、小説データを全検索して、小説のタイトルでソートをかけたい場合、下記のように書く。複数のソート条件を指定する場合は続けて記載する。
@Repository
public interface NovelRepository extends JpaRepository<Novel, Long>, JpaSpecificationExecutor<Novel> {
List<Novel> findAllByOrderByTitle();
}
※「findAll」の後に余分な「By」があるのは、ルール的にそこに検索条件が入ることになるため、今回は全検索なので「By」だけが残っている。
条件 APIを使用する
criteria を記述することにより、クエリの where 句を定義している場合、ソートは以下のように定義する。複数のソート条件を指定する場合は引数を追加する。
novelRepository.findAll(spec, JpaSort.by("title"))
@Repository
public interface NovelRepository extends JpaSpecificationExecutor<Novel> {
}
Hibernate Searchのソート
ソートの処理実装前に、エンティティに@KeywordFieldを追記する必要がある。
/** タイトル */
@FullTextField(analyzer = "japanese")
@KeywordField(name = "titleSort", sortable = Sortable.YES)
private String title;
検索時に上記で定義した名称をソート条件に指定する。
import org.hibernate.search.mapper.orm.Search;
@Service
public class NovelService {
private final EntityManager entityManager;
@Transactional
public List<Novel> findAllIndex() {
return Search.session(entityManager)
.search(Novel.class)
.where(f -> f.matchAll())
.sort(f -> f.field("titleSort"))
.fetchAllHits();
}
ソートの条件を指定していないときは、スコアの高い順にソートされている。大雑把に言えば、スコアが高いほど、より多くの述語にマッチしているか、より良くマッチしていることを意味する。そのため、Hibernate Searchに関しては明示的にソートの条件を指定しない方が、良いのかもしれない。