N:1マッピング
S2DAOのN:1マッピングを利用すると、SQLをかかずにJOINできます。
まず実験用にテーブルを作ります。ここではDBはMySQLを使いました。
CDテーブルとARTISTテーブルはN:1の関連を持ちます(ARTIST1人が複数のCDを出してると)。
CREATE TABLE ARTIST( ID INTEGER NOT NULL, NAME VARCHAR(100), PRIMARY KEY(ID) ); CREATE TABLE CD( ID INTEGER NOT NULL, TITLE VARCHAR(100), ARTIST_ID INTEGER, PRIMARY KEY(ID), FOREIGN KEY (ARTIST_ID) REFERENCES ARTIST(ID) ); INSERT INTO ARTIST VALUES(1, '人1'); INSERT INTO ARTIST VALUES(2, '人2'); INSERT INTO CD(ID, TITLE,ARTIST_ID) VALUES(1, 'タイトル1', 1); INSERT INTO CD(ID, TITLE,ARTIST_ID) VALUES(2, 'タイトル2', 1); INSERT INTO CD(ID, TITLE,ARTIST_ID) VALUES(3, 'タイトル3', 2);
それぞれのテーブルに対応したビーンを作ります。
まずARTISTテーブルに対応したArtistクラス。これは普通に作ります。
package dao; public class Artist { private int id; private String name; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
次にCDテーブルに対応したCdクラスを作成します。
package dao; public class Cd { private int id; private String title; // private int artistId; private Artist artist; public static final int artist_RELNO = 0; public static final String artist_RELKEYS = "ARTIST_ID:ID"; public String toString(){ return super.toString() + ",id=" + id + ",title=" + title + ",artistId=" + artist.getId() + ",artistName=" + artist.getName() ; } public Artist getArtist() { return artist; } public void setArtist(Artist artist) { this.artist = artist; } // public int getArtistId() { // return artistId; // } // public void setArtistId(int artistId) { // this.artistId = artistId; // } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } }
このCdクラスでN:1用の設定を行います。
やってることは、
ということです。
このN:1のアノテーションがちょっとハマりました。
public static final int artist_RELNO = 0; public static final String artist_RELKEYS = "ARTIST_ID:ID";
RELNOはなんだかイマイチわかりませんが、とりあえず外部キーは1つなので0にするといいようです(2つ、3つ結合する場合、1、2と増やしていく)。
そして、RELKEYSにN:1のテーブルのそれぞれのカラム名を指定します。
左側がN側、つまりCDテーブルのカラムARTIST_IDです。
右側が1側、つまりARTISTテーブルのカラムIDです。
このあたりの情報って、JDBCのDBメタデータから取れそうな気もするので、もしかしたら、必須のアノテーションじゃないかも?でも書かないと動かない。
あとは普通にDaoを作って、diconファイルに登録すればよしです。
Daoクラス、CdDao.javaはこんな感じ。
package dao; import java.util.List; public interface CdDao { public final static Class BEAN = Cd.class; public List getAllCds(); }
diconファイルはこんな感じ
<?xml version="1.0" encoding="Shift_JIS"?> <!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container//EN" "http://www.seasar.org/dtd/components.dtd"> <components namespace="dao"> <include path="j2ee.dicon"/> <component class="org.seasar.dao.impl.DaoMetaDataFactoryImpl"/> <component name="daointerceptor" class="org.seasar.dao.interceptors.S2DaoInterceptor"/> <component class="dao.CdDao"> <aspect>daointerceptor</aspect> </component> </components>
これを呼び出すクライアントを作成して、
package dao; import java.util.List; import org.seasar.framework.container.S2Container; import org.seasar.framework.container.factory.S2ContainerFactory; public class Main { public static void main(String[] args) { S2Container container = S2ContainerFactory.create("dao.dicon"); CdDao cdDao = (CdDao)container.getComponent(CdDao.class); List cdList = cdDao.getAllCds(); for(int i=0; i<cdList.size(); i++){ Cd cd = (Cd)cdList.get(i); System.out.println(cd); } } }
えい、実行!
DEBUG 2004-10-15 00:25:34,009 [main] 物理的なコネクションを取得しました DEBUG 2004-10-15 00:25:34,009 [main] 論理的なコネクションを取得しました DEBUG 2004-10-15 00:25:34,079 [main] 論理的なコネクションを閉じました DEBUG 2004-10-15 00:25:34,099 [main] SELECT Cd.id, Cd.title, artist.name AS name_0, artist.id AS id_0 FROM Cd LEFT OUTER JOIN Artist artist ON Cd.ARTIST_ID = artist.ID DEBUG 2004-10-15 00:25:34,099 [main] 論理的なコネクションを取得しました DEBUG 2004-10-15 00:25:34,129 [main] 論理的なコネクションを閉じました dao.Cd@142bece,id=1,title=タイトル1,artistId=1,artistName=人1 dao.Cd@fcfa52,id=2,title=タイトル2,artistId=1,artistName=人1 dao.Cd@961dff,id=3,title=タイトル3,artistId=2,artistName=人2
ちゃんとJOINするSQLが発効され、関連テーブル含めたCdクラスのインスタンスが取得できます。
SQLは書いてません。
SQLの自動生成で全部こなせるとは思いませんが、大半の単調なコードは省略できそうです。
なお、上のCd.javaでartistIdプロパティーには自動生成のSQLでは、値が入ってこないようです(関連クラスから取得できるんで入らないのかな?)。
あと、N:1のサンプルを作るのに
WEB DB PRESS Vol.23
http://www.gihyo.co.jp/magazines/wdpress/archive/Vol22
のS2DAOの記事を参考にしました。
最初2ページぶちぬきソースか!?なんじゃそりゃと思いましたが、
ソース大変役立ちました。
(グーグってもS2DAOのN:1のサンプル出てこないのよねー)
DAOとかVOとかDTOとか
設計、コーディングで用いられる言葉の整理。こいつらって何よ?
これらは、EJBの出現により(?)メジャーになった言葉たちです。
DAOは、DB(永続ストア)にアクセスするためのクラスです。
たいてい、VOを引数に受け取って、INSERT、UPDATEを行ったり、
SELECTしてVOやVOのリストを返したりします。
DTOはVOとほぼ同義ですが、微妙に違います。
何が微妙に違うかはメンドクサイので忘れます。
エンティティは、DAO+VO(DTO)のようなペアでDB操作を行わず、
エンティティクラス自体のメソッドにINSERTやSELECTを行うメソッドを定義します。
(SELECTは別のクラスにしたり、staticメソッドにしたりもする)
VO/DTOにDAOの機能も一緒になっちゃったみたいなのが、エンティティです。
EJBのエンティティビーンというやつがエンティティですね。
ちなみに、VOやDTOは、セッタ、ゲッタしか持たなかったりして、
手続き型言語の構造体みたいなので、OOマンセーな人たちは、
これらの構造体チックなクラスは嫌う傾向にあるようです。
オブジェクトは自分で保存するのか別のオブジェクトに保存されるべきか。
保存時は、オブジェクトの内部情報を扱うので自分で保存した方がシックリくるとは思います(JavaのデフォルトのSerializableと、ちょっと違うことするときは、readObject/writeObjectみたいな)。
でもDBがバックエンドのWebアプリケーションを作ってると、
表も裏もDTO風なので(というかマップか)、どうしてもDAO+DTOという設計パターンになりがちです。
まあ、なりがちなものはなりがちな設計が無理がないのかもしれません。
昔は、リクエストをDTOにつめなおす、ResultSetをDTOにつめなおすという、
つまんない上にミスしやすいことをイチイチ全部手書きしてたりしましたが、
最近はリフレクション使ったユーティリティクラスで全部やっちゃうので、
非効率なことは大分なくなりましたし。
ちなみに私は数年前まで、こいつらの言葉をごっちゃにしてました。
なので、save、loadメソッドを持つなんとかVOとかいう変なクラス名のクラスをたくさん作ってしまいました。ごめんなさい。。。まだ間違ってたりして。