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 テスト結果
ここまで問題なく実行。