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 ListselectAllBooks() { 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(); ListallBooks = 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 テスト結果
ここまで問題なく実行。