スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

Spring MVC 検索にソート条件を追加する(DBアクセス編)

今回は、検索にソート条件を追加して検索結果の並び替えができるようにしてみたいと思う。

なお、ソート条件追加にあたり実現したいことをまとめておく。
①ソートする項目はリストボックスから選択する(デフォルトはID)
②昇順、降順はラジオボタンで選択する(デフォルトは昇順)
③国際化対応によりリストボックスの内容も日本語・英語で切り替わるようにする
④ページング機能は変わらず使えるようにする

今回、ボリュームが多いため、記事を3回に分けて掲載する。
 ①DBアクセス編
  ⇒本記事
 ②コントローラ編
  ⇒次回掲載予定
 ③JSP編
  ⇒次々回掲載予定

◎動作検証にあたっての各バージョンは以下の通り
  • SpringFramework 3.2.8.RELEASE
  • Java 1.7
  • Tomcat 7.0

イメージを掴むため、ソート条件追加後の画面を掲載しておく。

検索一覧(完成イメージ)
Sort_1.png

1.BookDao.java(関連箇所のみ掲載)

package jp.co.sample.book.dao;

import java.util.List;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

public interface BookDao<T> {

Page<T> findPageByNameLike(Pageable pageable,String name,String sortId,String direction);
//他のメソッド定義は省略
}

実現方法はいろいろあると思うが、今回は「ソート項目」と「昇順or降順」を渡して実装してみたい。

2.BookDaoImpl.java(関連箇所のみ掲載)

package jp.co.sample.book.dao;

import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

import jp.co.sample.book.entity.BookEntity;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

@Repository
@Transactional
public class BookDaoImpl implements BookDao<BookEntity> {

private static Logger logger = LoggerFactory.getLogger(BookDaoImpl.class);

@PersistenceContext
private EntityManager manager;

//方法1
@Override
@Transactional(readOnly=true)
public Page<BookEntity> findPageByNameLike(Pageable pageable, String name,String sortId, String direction) {
logger.info("findPageByNameLike start");
Query query = manager.createQuery("from BookEntity where bookName like ?1 order by " + sortId + " " + direction);
//下記は実行時例外となる
//Query query = manager.createQuery("from BookEntity where bookName like ?1 order by ?2 ?3");
query.setParameter(1, "%" + name + "%");
int total = query.getResultList().size();
List<BookEntity> list = query.setFirstResult(pageable.getOffset()).setMaxResults(pageable.getPageSize()).getResultList();
Page<BookEntity> pageList = new PageImpl(list,pageable,total);
return pageList;
}

//方法2
@Override
@Transactional(readOnly=true)
public Page<BookEntity> findPageByNameLike(Pageable pageable, String name,String sortId, String direction) {
logger.info("findPageByNameLike start");
CriteriaBuilder cb = manager.getCriteriaBuilder();
CriteriaQuery<BookEntity> query = cb.createQuery(BookEntity.class);
Root<BookEntity> c = query.from(BookEntity.class);
query.select(c).where(cb.like(c.get("bookName").as(String.class),"%" + name + "%"));
if("desc".equals(direction)){
query.orderBy(cb.desc(c.get(sortId)));
} else {
query.orderBy(cb.asc(c.get(sortId)));
}
int total = manager.createQuery(query).getResultList().size();
List<BookEntity> list = manager.createQuery(query).setFirstResult(pageable.getOffset()).setMaxResults(pageable.getPageSize()).getResultList();
Page<BookEntity> pageList = new PageImpl(list,pageable,total);
return pageList;
}
//他のメソッドは省略
}

findPageByNameLikeメソッドの実装方法を2通り掲載している。

方法1は、JPQLにorder by句を追加して、「ソート項目」と「昇順or降順」を文字列結合している。34行目はコメント化しているが、order by句には「Ordinal Parameters」「Named Parameters」ともに使用できないようだ。実際に「order by ?2 ?3」に対して、「query.setParameter(2, sortId); query.setParameter(3, direction);」として実行した結果、「org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.IllegalArgumentException: org.hibernate.hql.internal.ast.QuerySyntaxException: unexpected token: ? near line 1, column 78 [from jp.co.sample.book.entity.BookEntity where bookName like ?1 order by ?2 ?3]」と実行時例外になった。そのため、文字列結合させている。ソート条件に対するJPQLインジェクションの可能性は、画面上選択項目のため、入力項目であるタイトルのwhere句よりリスクは低いと思われるが、他に方法はないのだろうか。

そう思って掲載したのが、Criteria APIを使った方法2となる。

Criteria APIは見てもらえれば分かると思うが、JPQLのクエリーをメソッドを使って組み立てている。
JPQLに比べて行数は多くなっているが、何をしているかは、だいたい見当がつくと思う。51~55行目がソート条件を構築している箇所となる。56行目のcreateQueryメソッドにはCriteriaQueryを渡している点も補足しておく。

■JPQLインジェクションに関する過去の記事
Spring Data JPA JPQLインジェクション対策


3.BookService.java(関連箇所のみ掲載)

package jp.co.sample.book.service;

import java.util.List;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

public interface BookService<T> {

Page<T> findPageByNameLike(Pageable pageable,String name,String sortId,String direction);
//他のメソッドは省略
}


4.BookServiceImpl.java(関連箇所のみ掲載)

package jp.co.sample.book.service;

import java.util.List;
import jp.co.sample.book.dao.BookDao;
import jp.co.sample.book.entity.BookEntity;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;

@Service
public class BookServiceImpl implements BookService<BookEntity> {

@Autowired
BookDao<BookEntity> dao;

@Override
public Page<BookEntity> findPageByNameLike(Pageable pageable, String name,
                      String sortId, String direction) {
return dao.findPageByNameLike(pageable,name,sortId,direction);
}
//他のメソッドは省略
}

Serviceについては、DAOに処理を委譲しているだけとなっている。


JPQLのorder by句で対応できると想定していたが、文字列結合しかできず、結果的にCriteria APIを使う良い機会となった。
次回に続く。

■過去のSpring関連記事
Spring関連記事 Index

スポンサーサイト

コメントの投稿

非公開コメント

プロフィール

bookmount8

Author:bookmount8
システムエンジニア。サーバーサイドでjavaを扱うことが多い。最近は、ミドルやフロント周りも関心あり。

最新記事
カテゴリ
検索フォーム
最新コメント
月別アーカイブ
これまでの訪問者数
ブロとも申請フォーム

この人とブロともになる

RSSリンクの表示
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。