Java EE 7 検証環境構築(7) JPA 問い合わせ(1) 名前付きクエリを使ってみる。テストでトランザクションも意識する
- Java EE 7 検証環境構築(1) WildFly + JBoss Tools で EARプロジェクトを作成し Arquillian で ユニットテストをグリーンにするところまで
- Java EE 7 検証環境構築(2) WildFly に DataSourceを作成
- Java EE 7 検証環境構築(3) JPAからMySQLに接続するユニットテストをArquillianで実行
- Java EE 7 検証環境構築(4) Java EE での DI(Dependency Injection) および CDI(Contexts and Dependency Injection)をながめる
- Java EE 7 検証環境構築(5) JBoss Toolsが生成したサンプルソースのCDIを確認する
- Java EE 7 検証環境構築(6) JPA エンティティの作成と挿入
- Java EE 7 検証環境構築(7) JPA 問い合わせ(1) 名前付きクエリを使ってみる。テストでトランザクションも意識する
- Java EE 7 検証環境構築(8) JPA 問い合わせ(2) 動的クエリとCriteria API を試す
- Java EE 7 検証環境構築(9) jBatch 概要をおさえる
- Java EE 7 検証環境構築(10) JBoss Tools で作成した EARプロジェクトをJava EE 6 から 7 に変更する
- Java EE 7 検証環境構築(11) jBatch用 プロジェクトの作成を行う
- Java EE 7 検証環境構築(12) jBatch 簡易サンプル作成と Arquillian でユニットテスト
- Java EE 7 検証環境構築(13) jBatch REST サービス経由で実行する
- Java EE 7 検証環境構築(14) WildFly の管理をGUIで行う
- Java EE 7 検証環境構築(15) WildFly を サービスとして設定する(Windows/Linux)
- Java EE 7 検証環境構築(16) WildFly と Apache を mod_jk で連携させる(Widows)
なんやかんやで、JPAを使ってエンティティを挿入することができたので、問い合わせを行う。
1.JPA問い合わせについて
1.1 JPQL (Java Persistence Query Language)
- JPAでは、SQLのかわりにJPQLを使用する 。
- JPQLは動的、静的、ネイティブSQLも実行可能。
- 静的クエリは、名前付きクエリ(Named Query)ともいい、アノテーション、XMLを使用して定義できる 。
- JPQLではなく、DBMS ネイティブのSQLを指定することもできる (動的、静的)。
1.2 ネイティブクエリ
1.3 名前付きクエリ(Named Query)
- 単独なら@NamedQuery、複数の場合@NamedQueriesでまとめる 。
- または、対応するXMLディスクリプタ内のメタデータで定義する 。
- 同様に@NamedNativeQuery で、ネイティブクエリも名前付きクエリとして定義できる。(@NamedNativeQueries で複数定義できる) 。
- 一般的に、クエリ結果に直接対応するエンティティクラスに記述する。
- クエリ名は永続性ユニットごとにスコープがあり、スコープ内で一意でなければならない(クエリ名の前にエンティティ名をつけるのが一般的)。
- クエリ名文字列でタイプミスによる問題を軽減するために、クエリ名を定数を置き換えることもできる
2. 試す
ということで、まずは、この辺りまで(JPQL名前付きクエリ、ネイティブ名前付きクエリ)を試してみることにする。
動的JPQL、Criteria API(オブジェクト指向クエリ) などは別途確認する。
2.1 エンティティ
挿入の確認で作成した、エンティティに、名前付きクエリ(JPQL、ネイティブ) をアノテーションで付加する。Book エンティティにクエリ名を定数として持たせるのは若干違和感(Bookエンティティには本質的にクエリ名とか関係ないはず)がないではないが、メリットのほうが大きそうなので、定数化してみる。
@Entity
@NamedQueries({
@NamedQuery(name=Book.QUERY_FIND_BY_ID,
query="select b from Book b where b.id = :id"),
@NamedQuery(name=Book.QUERY_SELECT_ALL,
query="select b from Book b"),
@NamedQuery(name=Book.QUERY_SELECT_BY_TITLE,
query="select b from Book b where b.title like :title")})
@NamedNativeQuery(name=Book.QUERY_SELECT_MORE_EXPENSIVE,
query="select * from book where price > :price",
resultClass=Book.class)
public class Book {
public static final String QUERY_FIND_BY_ID = "Book.findBookById";
public static final String QUERY_SELECT_ALL = "Book.selectAllBooks";
public static final String QUERY_SELECT_BY_TITLE = "Book.selectBooksByTitle";
public static final String QUERY_SELECT_MORE_EXPENSIVE = "Book.selectMoreExpensiveBooks";
public Book(){}
public Book(String title, Float price, String description){
this.title = title;
this.price = price;
this.description = description;
}
:省略
}
2.2 サービスを作成
それぞれの名前付きクエリを呼び出すサービスメソッドを定義してみる。
EntityManager の createNamedQueryは、Query を返し、Query のセッターは自分自身を返す。
なので、下記例では、Query のローカル変数を宣言していない。
@Stateless
public class BookService {
@Inject
private Logger log;
@Inject
private EntityManager em;
public void insertBook(Book book) {
em.persist(book);
}
public Book findBookById(Long id) {
return (Book)em.createNamedQuery(Book.QUERY_FIND_BY_ID)
.setParameter("id", id)
.getSingleResult()
;
}
@SuppressWarnings("unchecked")
public List selectAllBooks() {
return em.createNamedQuery(Book.QUERY_SELECT_ALL).getResultList();
}
@SuppressWarnings("unchecked")
public List selectBooksByTitle(String title) {
return em.createNamedQuery(Book.QUERY_SELECT_BY_TITLE)
.setParameter("title", title + "%")
.getResultList()
;
}
@SuppressWarnings("unchecked")
public List selectMoreExpensiveBooks(Float price) {
return em.createNamedQuery(Book.QUERY_SELECT_MORE_EXPENSIVE)
.setParameter("price", price)
.getResultList()
;
}
}
2.3 テストケース
Arquilian Java 永続化のテスト を参照する。Spring だと、@Rollback でテストメソッドのトランザクションを制御できた(デフォルトロールバック、@Rollback(false)でコミット)が。。。
@Inject UserTransaction utx; でユーザートランザクションはインジェクトでき(Mavenに依存関係を記述する必要 2.3.1 参照)、@Before が、テストメソッドの前、@After がテストメソッドの後に呼び出されるので、そこで、トランザクションの開始と、ロールバックをしてみる。
一応想定した、メソッドごとにトランザクション開始、ロールバックという動きはするのだが、もう少し簡単にできるとうれしい。
@RunWith(Arquillian.class)
public class BookServiceTest {
@Deployment
public static Archive<?> createTestArchive() {
return ShrinkWrap.create(WebArchive.class, "test.war")
.addClass(Resources.class)
.addClasses(Book.class,BookService.class)
.addAsResource("META-INF/test-persistence.xml", "META-INF/persistence.xml")
.addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml")
;
}
@Inject
Logger log;
@Inject
BookService bookService;
@Inject
UserTransaction utx;
@Before
public void beforeTest() throws Exception {
utx.begin();
}
@After
public void afterTest() throws Exception {
utx.rollback();
}
@Test
public void insertBookTest() {
Book book = new Book("Insert Test",100f,"Test");
bookService.insertBook(book);
log.info(book.toString());
assertNotNull(book.getId());
}
@Test
public void findBookByIdTest() {
createTestData();
List allBooks = bookService.selectAllBooks();
int pos = (int)(Math.random() * 100d) % allBooks.size();
Book book1 = allBooks.get(pos);
log.info(book1.toString());
Book book2 = bookService.findBookById(book1.getId());
log.info(book2.toString());
assertEquals(book1, book2);
assertTrue(book1 == book2);
}
@Test
public void selectBooksByTitleTest() {
createTestData();
List books = bookService.selectBooksByTitle(SAMPLE_BOOK_TITLE_PREFIX);
for (Book book : books) {
log.info(book.toString());
assertTrue(book.getTitle().startsWith(SAMPLE_BOOK_TITLE_PREFIX));
}
}
@Test
public void selectMoreExpensiveBooksTest() {
createTestData();
final float BASE_PRICE = 300f;
List books = bookService.selectMoreExpensiveBooks(BASE_PRICE);
for (Book book : books) {
log.info(book.toString());
assertTrue(book.getPrice() > BASE_PRICE);
}
}
private final String SAMPLE_BOOK_TITLE_PREFIX = "SampleBook";
private void createTestData() {
for (int i=0; i<5; i++) {
bookService.insertBook(
new Book(
String.format("%s%02d", SAMPLE_BOOK_TITLE_PREFIX, i),
i * 100f,
String.format("Description about %s%02d.", SAMPLE_BOOK_TITLE_PREFIX, i))
);
}
}
}
2.3.1 UserTransaction を利用出来るようにする
pom.xml に以下を追記。
<dependency>
<groupId>javax.transaction</groupId>
<artifactId>jta</artifactId>
<version>1.1</version>
<scope>test</scope>
</dependency>
<\pre>
2.4 テスト結果
ここまで問題なく実行。
