SHOEISHA iD

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

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

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

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

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

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

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

関連を持ったEntityクラス群を扱うクラスの実装

 前編ではMeigaraクラスというEntityクラスのCRUD操作の仕方を説明しました。今回はMeigaraクラスに加え、Basketクラス、BasketItemクラスも存在し、それらの間には関連が存在します。今回はこのようなEntity群のCRUD操作を説明します。

コンテナ管理のEntityマネージャとJTAトランザクションの組み合わせのメリット

 Session Bean、つまりEJBコンポーネントでは一般的にコンテナ管理のEntityマネージャを使用します。またEJBコンポーネントの場合、自動的にJTAトランザクションのコントロール下に置かれます。つまり、EJBコンポーネントでコンテナ管理のEntityマネージャを使用するということは、コンテナ管理のEntityマネージャもJTAトランザクションのコントロール下に置かれることになります。これはEntityマネージャのライフサイクルがJTAトランザクションと一致することを意味します。要するにJTAトランザクションをcommitするかrollbackすることでEntityマネージャはcloseされます。Entityマネージャがコンテナ管理されると、前編のJavaアプリケーションで行っていたEntityファクトリとEntityマネージャの取得処理は不要になります。ただし、取得処理こそ行いませんが、DI(Dependency Injection:依存性注入)により、EntityマネージャはEJBコンテナによりEJBコンポーネントに設定されます。

 Entityマネージャにはコンテナ管理以外にアプリケーション管理のEntityマネージャが存在します。コンテナ管理のEntityマネージャを使用する場合は必ずJTAトランザクションを使用しなければなりません。

JPQLとは何か

 前編では1つのテーブルに対しCRUD操作を行いました。今回のように関連のある複数のテーブルを操作するにはSQLのような言語が必要になります。そこで誕生したのが、Entityクラスとそのカラムを使用したSQLとほぼ同じ構文を持ったJPQLです。実際のコーディングの中で使い方を説明します。JPQLについての詳細はDownload Enterprise JavaBeans 3.0 Final Releaseのサイトに「ejb-3_0-fr-spec-persistence.pdf」というリンクがあるので、クリックしてダウンロードしてください。そのChapter 4がJPQLの仕様です(日本語訳を見つけることができませんでした。知っている方はコメントをいただければ助かります)。

Entityクラス間の関連を扱うクラスの実装

 前編で説明した設定を行っていれば準備は終了です。前編を読まれていない方で設定を知りたい方は参照ください。

 まずはSession Beanの作成からです。NetBeansの左ペインで[プロジェクト]-[JPASample02-ejb]を開き右クリックします。[新規]-[セッション Bean]をクリックすると図4のように「新規 セッション Bean」画面が表示されます。EJB名に「JPASession01」、パッケージに「jp.kawakubo」と入力します。今回はStateless Session Beanを使用するため[セッションのタイプ]のラジオボタンは「ステートレス」を選択します。[インターフェースを作成]は後に説明するWeb層と接続する必要があるため、「リモート」にチェックを入れます。[完了]ボタンをクリックすると、[JAPSample02-ejb]-[ソースパッケージ]-[jp.kawakubo]の下に、「JPASession01Bean」と「JPASession01Remote」が作成されているはずです。このようにEJB名に「Bean」と「Remote」が自動的に付加されたクラス名になるため、EJB名に「Bean」を入れないことをおすすめします。

図4.「新規セッションBean」画面
図4.「新規セッションBean」画面

JPASample01Beanクラスの実装

 JPASample01Beanクラスの実装のみを説明しますが、JPASample01Remoteクラスにも同じシグネチャを持つメソッドを宣言してください。リスト4がJPASample01Beanの内容です。リスト5がJPASample01Remoteクラスです。

リスト4.JPASample01Beanクラス
001:package jp.kawakubo;
002:
003:import java.util.ArrayList;
004:import java.util.List;
005:import javax.ejb.Stateless;
006:import javax.persistence.EntityManager;
007:import javax.persistence.PersistenceContext;
008:import javax.persistence.Query;
009:
010:@Stateless(name = "JPASession01Remote")
011:public class JPASession01Bean implements JPASession01Remote {
012:    
013:    @PersistenceContext(unitName = "JPASample02-ejbPU")
014:    private EntityManager entityManager;
015:
016:    public JPASession01Bean() {
017:    }
018:
019:    public JPASession01Bean(EntityManager entityManager) {
020:        this.entityManager = entityManager;
021:    }
022:
023:    public List getMeigaras() {
024:        List meigaras = null;
025:        Query query = entityManager.createQuery(
026:                "SELECT m FROM Meigara AS m");
027:        meigaras = query.getResultList();
028:        return meigaras;
029:    }
030:
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:        }
046:
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:        }
069:    }
070:
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:    }
083:
084:
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:    }
096:
097:    public List getTotalCharge(List basketItemContents) {
098:        List totalCharge = new ArrayList();
099:        int sumItems = 0;
100:        int shippingCost = 1500;
101:        int total = 0;
102:        BasketItemContent basketItemContent = null;
103:        // 本数変更後の金額の合計を求める
104:        // getBasketItemsの引数にはuserIdを設定すべきだが、内部で「tomoharu」
105:        // を固定で設定しているため、ここでは空文字を設定
106:        for (int i = 0; i < basketItemContents.size(); i++) {
107:            basketItemContent = basketItemContents.get(i);
108:            sumItems += (basketItemContent.getPrice() * basketItemContent.getQuantity());
109:        }
110:
111:        total = sumItems + shippingCost;
112:        totalCharge.add(sumItems);
113:        totalCharge.add(shippingCost);
114:        totalCharge.add(total);
115:        return totalCharge;
116:    }
117:
118:    public void removeBasket(String userId) {
119:        Query query = entityManager.createQuery(
120:                "SELECT b FROM Basket AS b " +
121:                "WHERE b.userId = 'tomoharu'");
122:        Basket basket = (Basket)query.getSingleResult();
123:        entityManager.remove(basket);
124:    }
125:
126:}

 10行目でこのセッションBeanがステートレスであることをアノテーションで示しています。13行目で前編で説明した持続性ユニットを指定しています。今回もコーディング中に電球マークが出てきて持続性ユニットを作成するように促されます。電球をクリックすると図5のように「持続性ユニットを作成」画面が表示されます。「持続性ユニット名」はデフォルトで表示されているものを使用します。

 問題はデータソースです。本稿では、データソースの作り方は説明していないので「GlassFishからアプローチするJava」の第4回を参考に作成してください。紹介した記事の通りデータソースの作成ができたら、図5のようにデータソースに「jdbc/imoshop」と入力してください。

 13行目から21行目がEntityマネージャのDIに関するコードです。

図5.「持続性ユニットを作成」画面
図5.「持続性ユニットを作成」画面
リスト5.JPASample01Remoteクラス
001:package jp.kawakubo;
002:
003:import java.util.List;
004:import javax.ejb.Remote;
005:
006:@Remote
007:public interface JPASession01Remote {
008:
009:    public List getMeigaras();
010:    public void insertBasketItems(int[] meigaraIds);
011:    public List getBasketItems(String userId);
012:    public void updateBasketItems(int[] basketItemIds, int[] allQuantity);
013:    public List getTotalCharge(List basketItemContents);
014:    public void removeBasket(String userId);
015:    
016:}

次のページ
Servletの実装

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

今からでも遅くない、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」など、さまざまなカンファレンスを企画・運営しています。

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

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

メールバックナンバー