Entity間の関連を実装する
Entity間の関連を実装するには、それなりのEntityが必要になります。1から構築するのは手間がかかるため、筆者の連載「GlassFishからアプローチするJava」の第6回を参考にします。参考記事はJavaのWebアプリケーションですが、今回はSession Beanを作成するため、プロジェクトは[エンタープライズアプリケーション]を指定する必要があります。
プロジェクトの作成
NetBeansで[ファイル]-[新規プロジェクト]をクリックすると[新規プロジェクト]画面が開きます。[カテゴリ]で「Java EE」を選択すると、[プロジェクト]のリストに[エンタープライズアプリケーション]が表示されるので選択し、[次へ]ボタンをクリックします。[新規エンタープライズアプリケーション]の[名前と場所]画面が開きます。[プロジェクト名]は任意の名前で構いません(ここでは「JPASample02」と入力します)。
他の項目はそのままで[次へ]ボタンをクリックすると、[サーバーと設定]画面が開きます。デフォルトのままでいいと思いますが、[EJBモジュールを作成]と[Webアプリケーションモジュールを作成]のチェックボックスにチェックが入っていることを確認してください。確認後、[完了]ボタンをクリックすると3つのプロジェクトが作成されます。「JPASample02」「JPASample02-ejb」「JPASample02-war」が作成されていることを、左ペインの[プロジェクト]タブで確認してください。
アプリケーションの要件
アプリケーションの要件は「GlassFishからアプローチするJava」の第6回とほぼ同じです。「芋焼酎酒店」という架空のネットショップを作成します。
- 画面上部に芋焼酎のカタログを表示する
- 銘柄の左側にあるチェックボックスにチェックを入れ、カタログの下にある[買い物かごに追加する]ボタンをクリックすると買い物かごに銘柄が追加される。本数の初期値は1とする
- 買い物かごの本数を更新し[買い物かごを再表示する]ボタンをクリックすると、銘柄ごとの金額が計算し表示する
- 買い物かごの下部に明細合計(銘柄の金額の合計)、送料、総合計(明細合計に送料を加えたもの)を表示する
- [レジへ進む]ボタンをクリックすると買い物かごが空になる
買い物かごが空の状態の「芋焼酎酒店」のイメージは図1の通りです。
ER図
参考アプリケーションのER図は図2になります。

クラス図

BasketクラスとBasketItemクラスは1対多の双方向の関連、BasketItemクラスとMeigaraクラスは多対1の単方向の関連になります。BasketクラスとBasketItemクラスはコンポジットの関係のため、Basketオブジェクトが削除される場合、BasketItemオブジェクトが存在しても意味がなくなるため、BasketItemオブジェクトを全削除する必要があります。
Entityクラスの実装
上記のクラス図を踏まえEntityクラスを実装します。以下の3つのクラスはNetBeansの左ペインで[プロジェクト]-[JPASample02-ejb]を選択し右クリックします。[新規]-[Javaクラス]を選択すると、クラス名とパッケージ名を入力する画面が開きます。クラス名は以下の「Basket」「BasketItem」「Meigara」をおのおの作成するときに入力します。パッケージ名は何を入力しても結構ですが、ここでは「jp.kawakubo」と入力します(これは筆者の持っているドメイン名を逆さにしたものです。ドメインを持っている方はそれを使ってください)。
Basketクラスの実装
リスト1がBASKETテーブルに対応したBasketクラスです。
001:@Entity
002:@Table(name = "BASKET")
003:public class Basket implements Serializable {
004:
005: private int id = 0;
006: private String userId = null;
007: private List basketItems = null;
008:
009: public Basket(){
010: }
011:
012: public Basket(int id,
013: String userId,
014: List basketItems) {
015: this.id = id;
016: this.userId = userId;
017: this.basketItems = basketItems;
018: }
019:
020: @Id
021: @GeneratedValue(strategy=GenerationType.IDENTITY)
022: @Column(name = "ID")
023: public int getId() {
024: return id;
025: }
026:
027: public void setId(int id) {
028: this.id = id;
029: }
030:
031: @Column(name = "USER_ID")
032: public String getUserId() {
033: return userId;
034: }
035:
036: public void setUserId(String userId) {
037: this.userId = userId;
038: }
039:
040: @OneToMany(targetEntity = BasketItem.class,
041: mappedBy = "basket")
042: public List getBasketItems() {
043: return basketItems;
044: }
045:
046: public void setBasketItems(List basketItems) {
047: this.basketItems = basketItems;
048: }
049:
050:}
1行目のEntityアノテーションと2行目のTableアノテーションは前編と同じです。このクラスがEntityクラスであり、RDBのBASKETテーブルに対応していることを宣言しています。前編と比較して新しい箇所は、21、40、41行目です。
BASKETテーブルのIDはAUTO_INCREMENTを指定しており、自動的に採番されています。そのようなカラムに対応するフィールド変数の場合、21行目のように、getterメソッドの前に以下を付けます。
@GeneratedValue(strategy=GenerationType.IDENTITY)
40行目と41行目が今回のテーマである関連をどう表現するかに相当する箇所です。図2の通り、BasketクラスとBasketItemクラスはコンポジットの関連を持っています。黒いひし形がコンポジットを表しています。コンポジットとは集約よりもさらに強い結びつきを持っており、BasketItemオブジェクトはBasketオブジェクトの存在がなければ意味を持たないことを意味しています。実際に、買い物かごがなく、買い物かごの中身だけ存在していても意味はありません。40行目でBasketクラスとBasketItemクラスが1対多であることを指定しています。41行目でBasketItemクラスで宣言しているBasketのフィールド変数名(リスト2の6行目)であることを指定しています。
BasketItemクラスの実装
リスト2がBASKET_ITEMテーブルに対応したBasketItemクラスです。
001:@Entity
002:@Table(name = "BASKET_ITEM")
003:public class BasketItem implements Serializable {
004:
005: private int id = 0;
006: private Basket basket = null;
007: private Meigara meigara = null;
008: private int quantity = 0;
009:
010: public BasketItem() {
011: }
012:
013: public BasketItem(int id,
014: Basket basket,
015: Meigara meigara,
016: int quantity) {
017: this.id = id;
018: this.basket = basket;
019: this.meigara = meigara;
020: this.quantity = quantity;
021: }
022:
023: @Id
024: @GeneratedValue(strategy=GenerationType.IDENTITY)
025: @Column(name = "ID")
026: public int getId() {
027: return id;
028: }
029:
030: public void setId(int id) {
031: this.id = id;
032: }
033:
034: @ManyToOne(targetEntity = Basket.class)
035: @JoinColumn(name = "BASKET_ID",
036: referencedColumnName = "ID")
037: public Basket getBasket() {
038: return basket;
039: }
040:
041: public void setBasket(Basket basket) {
042: this.basket = basket;
043: }
044:
045: @ManyToOne(targetEntity = Meigara.class)
046: @JoinColumn(name = "MEIGARA_ID",
047: referencedColumnName = "ID")
048: public Meigara getMeigara() {
049: return meigara;
050: }
051:
052: public void setMeigara(Meigara meigara) {
053: this.meigara = meigara;
054: }
055:
056: @Column(name = "QUANTITY")
057: public int getQuantity() {
058: return quantity;
059: }
060:
061: public void setQuantity(int quantity) {
062: this.quantity = quantity;
063: }
064:
065:}
24行目はBasketクラスの説明と同様です。34行目から36行目がBasketItemクラスとBasketクラスの関連を表現しています。34行目でBasketItemクラスとBasketクラスが多対1であることを指定しています。35行目でBASKET_ITEMテーブルのカラム名を、36行目でBASKETテーブルのカラム名を指定します。
45行目から47行目はMeigaraクラスとの関連を表現しています。45行目でBasketItemクラスとMeigaraクラスは多対1であることを指定しています。46行目ではBASKET_ITEMテーブルのカラム名を、47行目ではMEIGARAテーブルのカラム名を指定します。
関連に直接関係のないフィールドは、前編同様Columnアノテーションを指定するだけです。

