SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

今からでも遅くない、JPAを学ぼう!

今からでも遅くない JPAを学ぼう!(後編)
オブジェクト間の関連を理解し、JPQLを使用する

ディレクション、カーディナリティは難しくない

ダウンロード サンプルコード (14.2 KB)

JPASample01Beanクラスのメソッドの説明

getMeigarasメソッド

 JPASesion01RemoteはServletから呼び出されます。ServletからEJBの呼び出し方は同じであるため、当メソッドのみ呼び出し方を説明します。他のメソッドについても同様に呼び出してください。リスト6のようにしてImoshochuCatalogServletクラスからJPASession01RemoteインターフェースのgetMeigarasを呼び出します。JNDIを直接記述せずアノテーションで呼び出せるようになりました。

リスト6.ImoshochuCatalogServletの抜粋
001:public class ImoshochuCatalogServlet extends HttpServlet {
002:
003:    @EJB
004:    private JPASession01Remote jpaBean;

 3行目のEJBアノテーションで実装クラスのインターフェースを指定するだけでServletからEJBを呼び出すことができます。したがって、ServletではjpaBean.getMeigaras()で簡単にEJBのメソッドを呼び出すことができます。Servletについては他に特筆すべきことはありません。サンプルコードで確認してください。

 少し脇道にそれましたが、JPASession01BeanクラスのgetMeigarasメソッドは24行目で返り値のMeigaraオブジェクトのListを宣言しています。25、26行目はEntityManagerのcreateQueryメソッドを使ってQueryオブジェクトを生成しています。引数はJPQL文となります。当メソッドはMeigaraクラスを通してMEIGARAテーブルより明細を取得し、24行目で宣言したmeigarasに設定し戻しています。

 関心はJPQLです。SELECT m FROM Meigara AS mがJPQLそのものですが、「SQLでテーブルを指定するべきところにそのテーブルに相当するEntityクラスを使用する点」、また当JPQLには出てきませんが「カラムを指定すべきところにEntityクラスのフィールドを使用している点」が大きく異なるところです。したがって、27行目のquery.getResultList()によりMeigaraオブジェクトのListを取得しています。28行目でそのリストを返しています。

insertBasketItemsメソッド

 カタログのチェックボックスにチェックを入れ[買い物かごに追加する]ボタンをクリックすると、買い物かごに新しく銘柄を追加するためのメソッドです。カタログでは複数の銘柄にチェックを入れることができます。また、チェックを入れたものが既に買い物かごにある場合は、まったく更新せずそのままにします。

リスト7.Basketの存在を判定するコード
031:public void insertBasketItems(int[] meigaraIds) {
032:    Basket basket = null;
033:    Query query = entityManager.createQuery(
034:            "SELECT b FROM Basket AS b " +
035:            "WHERE b.userId = :userId")
036:            .setParameter("userId", "tomoharu");
037:    List baskets = query.getResultList();
038:    if (baskets.size() == 0) {
039:        basket = new Basket();
040:        basket.setUserId("tomoharu");
041:        entityManager.persist(basket);
042:        entityManager.flush();
043:    } else {
044:        basket = baskets.get(0);
045:    }

 32行目から45行目までのコードは、買い物かごがない場合は買い物かごを作成し、ある場合は既存の買い物かごを取得する処理です。31行目のメソッドの引数がintの配列になっているのは、複数銘柄の選択に対応するためです。

 34行目と35行目がJPQL文です。先述したようにJPQLではテーブルやカラムの代りにEntityクラスとそのフィールド変数を使います。このJPQL文は直感的に理解可能です。35行目の:userIdはプレースホルダーです。36行目でそのプレースホルダーに値を設定しています。本来であれば、プレースホルダーに設定する値も変数でなければあまり意味がありませんが、今回は説明を簡単にするために直接値を設定しています。

 37行目と38行目は「tomoharu」というuserIdを持ったBasketが存在するかを判断しています。37行目でgetSingleResultメソッドを使えばいいように思えますが、存在しない場合、Entityが存在しない旨の例外(javax.persistence.NoResultException:getSingleResult() did not retrieve any entities)が発生します。この例外が発生した場合に38行目から45行目に相当する処理を行うことも可能です。しかし、例外を使って通常の処理を記述するのは本来の例外の意味から外れてしまいます。getSingleResultメソッドはあくまでも1件のみ存在することが分かっている場合のみ使用するメソッドです。存在を確認する場合は、getResultListメソッドの戻り値のListのサイズを判定する方が例外を使用しない作りになります。

 39行目から42行目で新しくBasketオブジェクトを作成しています。44行目で既存のBasketオブジェクトを取り出しています。

リスト8.BasketItemの存在を判定するコード
047:BasketItem basketItem = null;
048:Meigara meigara = null;
049:for (int i = 0; i < meigaraIds.length; i++) {
050:    query = entityManager.createQuery(
051:            "SELECT bi " +
052:            "FROM BasketItem AS bi INNER JOIN bi.meigara AS m, " +
053:            "Basket AS b " +
054:            "WHERE m.id = :meigaraId " +
055:            "AND b.userId = :userId")
056:            .setParameter("meigaraId", meigaraIds[i])
057:            .setParameter("userId", "tomoharu");
058:    List basketItems = query.getResultList();
059:    // 買い物かごに同じ銘柄がない場合のみ追加
060:    if (basketItems.size() == 0) {
061:        basketItem = new BasketItem();
062:        meigara = entityManager.find(Meigara.class, meigaraIds[i]);
063:        basketItem.setBasket(basket);
064:        basketItem.setMeigara(meigara);
065:        basketItem.setQuantity(1);
066:        entityManager.persist(basketItem);
067:    }
068:}

 47行目から68行目までのコードは、取得したBasketオブジェクトに銘柄が存在しない場合のみ、BasketItemオブジェクトを作成する処理です。

 51行目から55行目がJPQL文です。52行目はBasketItemクラスに直接meigaraIdを持っていないため、Meigaraクラスと内部結合しています。この内部結合により、54行目でinsertBasketItemsの引数として渡されたMeigaraのidと一致するかの判定が可能になります。53行目でBasketクラスが出てくるのは唐突に思えますが、BasketクラスとBasketItemクラスでその関連を定義しているため、52行目のBasketItemクラスが53行目のBasketクラスの中に存在していることが分かる仕組みになっています。

getBasketItemsメソッド
リスト9.買い物かごの内容を保持するオブジェクトを返すコード
071:public List getBasketItems(String userId) {
072:    // userIdは固定で「tomoharu」を設定
073:    Query query = entityManager.createQuery(
074:            "SELECT NEW jp.kawakubo.BasketItemContent" +
075:            "(bi.id, m.name, m.price, bi.quantity) " +
076:            "FROM BasketItem AS bi INNER JOIN bi.meigara AS m, " +
077:            "Basket AS b " +
078:            "WHERE b.userId = :userId")
079:            .setParameter("userId", "tomoharu");
080:    List basketItemContents = query.getResultList();
081:    return basketItemContents;
082:}

 BasketItemクラスを見て、買い物かごの明細と一致していないのではと思われた方も多いと思います。BasketItemクラスであえてMeigaraクラスを参照しているのは、Meigaraクラスの内容が変化した場合、柔軟に買い物かごの明細を変化させたいがためです。したがって、getBasketItemsメソッドは画面に表示するBasketItemクラスからidとquantityを、Meigaraクラスからnameとpriceを取得し、Servletに返すという処理を行っています。しかし、これら4つのフィールドを保持するEntityクラスは存在しません。このような場合、リスト10のようにこれら4つのフィールドを持ったクラスを作成し、JPQLのSELECTの直後にNEWでそのクラスを指定します(74行目)。75行目は検索結果です。NEWで指定したクラスのフィールドと75行目は一致する必要があります。

リスト10.BasketItemContentクラスの抜粋
001:package jp.kawakubo;
002:
003:import java.io.Serializable;
004:
005:public class BasketItemContent implements Serializable {
006:
007:    private int id = 0;
008:    private String name = null;
009:    private int price = 0;
010:    private int quantity = 0;
011:    private int itemTotal = 0;
012:
013:    public BasketItemContent() {
014:    }
015:
016:    public BasketItemContent(
017:            int id,
018:            String name,
019:            int price,
020:            int quantity) {
021:        this.id = id;
022:        this.name = name;
023:        this.price = price;
024:        this.quantity = quantity;
025:        this.itemTotal = price * quantity;
026:    }
027:
028:    public int getId() {
029:        return id;
030:    }
updateBasketItemsメソッド

 当メソッドは[買い物かごを再表示する]ボタンをクリックした場合呼び出されるメソッドです。つまり買い物かごの本数の変更をBASKET_ITEMテーブルに反映させるためのメソッドです。

リスト11.BasketItemのquantityを更新するためのコード
085:public void updateBasketItems(int[] basketItemIds, int[] allQuantity) {
086:    for (int i = 0; i < basketItemIds.length; i++) {
087:        Query query = entityManager.createQuery(
088:                "UPDATE BasketItem AS b " +
089:                "SET b.quantity = :quantity " +
090:                "WHERE b.id = :id")
091:                .setParameter("quantity", allQuantity[i])
092:                .setParameter("id", basketItemIds[i]);
093:        query.executeUpdate();
094:    }
095:}

 これまで説明してきたメソッドはSELECT文だけでしたが、今回はUPDATE文です。JPQL文はほぼSQL文と同じなので説明を割愛します。更新の場合、93行目のようにexecuteUpdateメソッドを使用します。

getTotalChargeメソッド

 買い物かごの下の「明細合計」「送料」「総合計」を計算するメソッドです。簡単な処理のためコードは割愛します。

removeBasketメソッド

 userIdでBasketオブジェクトを検索し、EntityマネージャのremoveメソッドでBasketオブジェクトを削除するためのメソッドです。BASKET_ITEMテーブルの定義でCASCADE削除を指定しているため、検索したBasketオブジェクトに関連するBasketItemオブジェクトもすべて削除されます。簡単な処理のためコードは割愛します。

Servletの実装

 ServletからEJBのメソッドを呼び出すには、EJBアノテーションでJPASession01RemoteがEJBであることを宣言します。その変数であるjpaBeanを使って、JPASession01Beanのメソッドを呼び出します。処理自体は簡単なため、サンプルコードで確認していただければと思います。

次のページ
知っていることと知っていないことの差は大きい

この記事は参考になりましたか?

今からでも遅くない、JPAを学ぼう!連載記事一覧
この記事の著者

川久保 智晴(カワクボ トモハル)

haruプログラミング教室(https://haru-idea.jp/)主宰。COBOL、FORTRANで13年、Javaを中心としたWeb開発で11年。3つしか言語知らないのかというとそうでもなく、sed/awk、Perl、Python, PHP,  C#, JavaScriptなども一時期は業...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/5061 2010/05/14 14:00

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー