springでdao 20070413
DESCRIPTION
大昔の資料が出てきたTRANSCRIPT
Spring で DAO
船戸隆(エーティーエルシステムズ)鈴木雄介(アークランプ)
目的
• Spring を使って DAO を使う方法を理解する• DAO を使う場合の注意点を理解する• 皆がどんな風に DAO を使っているのか共有する
アジェンダ
• DAO とは• DAO の設計• Spring で DAO
– 実装– 設定– テスト
• GenericDao with Spring
• 気になること
DAO とは
DAO とは
• Data Access Object の略– J2EE パターンで
紹介– 永続化層におい
て様々なデータソースを抽象化する
– 今回はRDB ( JDBC)前提
http://java.sun.com/blueprints/corej2eepatterns/Patterns/DataAccessObject.html
DAO とは
• メリット– SQL が集約されるためメンテナンス性が高い– TransferObject になるので Java コードから
扱いやすい。型重要• デメリット
– 他層からデータアクセスの柔軟性が失われる– TransferObject を作成するコストが高い
DAO の設計
DAO の設計
• DAO のインターフェース設計– CRUD + Finder メソッド– パラメタオブジェクトを利用する– Tiger 便利
• TypeSafeEnum• Generics
DAO の設計
• CRUD + Finder メソッド– CRUD ( Create 、 Read 、 Update 、 Delet
e )は、 TransferObject をまるごとやりとり• Read は ID による検索
– ID 以外の検索は Finder メソッドとして、ある程度の目的別に用意
DAO の設計
• パラメタオブジェクトを利用する– DAO の Finder メソッドにおいて、パラメタ
が増えるたびにインターフェースが変わるのはいや
– そこでパラメタオブジェクトとして抽象化• パタメタが増えても、 DAO のインターフェースに
影響を与えないpublic interface BasicDao { List findByCriteria(BasicFindCriteria criteria);}
public class BasicFindCriteria { private String someKey;}
DAO の設計
• Tiger 便利( JavaSE5 )– 型重要– TypeSafeEnum
– Genericspublic interface BasicDao { List<BasicTransferObject> findByCriteria(BasicFindCriteria criteria);}
public enum AnyEnum { type1, type2}
Spring で DAO
Spring で DAO
• 実装のコツ– 基本は ORM を使う– Spring の ORM サポートクラスを使う
• 設定のコツ– AOP でトランザクション設定
• テストのコツ– Spring で用意されたテストサポートクラスを使う– DAO を使うクラスをテストする場合はモックを
使う
Spring で DAO – 実装
Spring で DAO – 実装
• 基本は ORM を使う– とりあえず便利– でも無理に使うことはない
• Spring の ORM サポートクラスを使う– Exception の違いを吸収
• Hibernate→HibernateException• iBATIS→SQLException
– try catch を書かなくて済むのでコードがすっきり– 例外で処理を判定するコードは書かない
• org.hibernate.NonUniqueObjectException で判定するなど
Spring で DAO – 実装
• 実装の流れ– TransferObject を作る– DAO のインターフェースを作る– DAO の実装をする– マッピングファイル書く– Spring に Bean 定義する
• ORM で実装サンプル– 今回は Hibernate 、 iBATIS そして・・・
Spring で DAO – 実装
• 実際に DAO を実装してみる
CREATE TABLE ELECTRIC_GUITAR ( ID varchar(40) NOT NULL, NAME varchar(200) , MANUFACTURE varchar(20), CRAFTED_DATE datetime, PRICE int(11), PRIMARY KEY (ID))
Spring で DAO – 実装
• TransferObject を作るpublic class ElectricGuitar {
private String id;
private String name;
private ManufactureEnum manufacture;
private Date craftedDate;
private Integer price;・・・
Java5 のEnum
Spring で DAO – 実装
• DAO のインターフェースを作るpublic interface ElectricGuitarDao {
List<ElectricGuitar> findAll();
ElectricGuitar load(String id);
String insert(ElectricGuitar electricGuitar);
void update(ElectricGuitar electricGuitar);
void delete(ElectricGuitar electricGuitar);
}
Spring で DAO – 実装
• Hibernate のサンプルpublic class ElectricGuitarHibernateDaoImpl extends
HibernateDaoSupport implements ElectricGuitarDao {
public List<ElectricGuitar> findAll() { return getHibernateTemplate().loadAll(ElectricGuitar.class); }
・・・
Spring で DAO – 実装
• マッピングファイルを書く– Hibernate のマッピングファイル
src/main/resources/ElectricGuitar.hbm.xml
<hibernate-mapping package="jp.springframework.vol2.domain"> <class name="ElectricGuitar" table="ELECTRIC_GUITAR"> <id name="id" column="ID" type="string" length="40"> <generator class="uuid" /> </id> <property name="name" column="NAME" type="string" length="200"/> <property name="manufacture" column="MANUFACTURE" type="jp.springframework.vol2.domain.enums.hibernate.ManufactureType“ length="20"/> <property name="craftedDate" column="CRAFTED_DATE" type="timestamp" /> <property name="price" column="PRICE" type="integer" /> </class>
Spring で DAO – 実装
• Spring の設定をする–Spring の ApplicationContext の設定
src/main/resources/context/applicationContextHibernate.xml <bean id="electricGuitarDao" class="jp.springframework.vol2.dao.hibernate.ElectricGuitarHibernateDaoImpl"> <property name="sessionFactory" ref="sessionFactory"/> </bean> <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="mappingLocations"> <list> <value>classpath:hibernate/ElectricGuitar.hbm.xml</value> </list> </property>
org.hibernate.SessionFactory を組み立てくれる
Spring で DAO – 実装
• iBATIS のサンプル
public class ElectricGuitarIbatisDaoImpl extends SqlMapClientDaoSupport implements ElectricGuitarDao {
public List<ElectricGuitar> findAll() { return getSqlMapClientTemplate().queryForList("ElectricGuitar.findAll"); }
public ElectricGuitar load(String id) { return (ElectricGuitar) getSqlMapClientTemplate().queryForObject("ElectricGuitar.load", id); } ・ ・ ・
Spring で DAO – 実装
• マッピングファイルを書く– iBATIS のマッピングファイル
<sqlMap namespace="ElectricGuitar">
<typeAlias alias="guitarParam" type="jp.springframework.vol2.domain.ElectricGuitar"/>
<resultMap id="guitar" class="jp.springframework.vol2.domain.ElectricGuitar"> <result property="id" column="ID" /> <result property="name" column="NAME" /> <result property="manufacture" column="MANUFACTURE" typeHandler="ManufactureEnum"/> <result property="craftedDate" column="CRAFTED_DATE" /> <result property="price" column="PRICE" /> </resultMap> <select id="findAll" resultMap="guitar">SELECT ID, NAME, MANUFACTURE, CRAFTED_DATE, PRICE FROM ELECTRIC_GUITAR </select> ・ ・ ・
src/main/resources/ibatis/ElectricGuitar.sqlmap.xml
Spring で DAO – 実装
• iBATIS の Spring 設定
<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean"> <property name="configLocation" value="classpath:ibatis/sqlmap-config.xml" /> <property name="dataSource" ref="dataSource" /> </bean> <bean id="electricGuitarDao" class="jp.springframework.vol2.dao.ibatis.ElectricGuitarIbatisDaoImpl"> <property name="sqlMapClient" ref="sqlMapClient"/> </bean>
src/main/resources/context/applicationContextIbatis.xml
com.ibatis.sqlmap.client. SqlMapClient を組み立てくれる
Spring で DAO – 実装
• Spring-S2DAO のサンプル– S2DAO はインターフェースのみで動作する
ため実装クラスはありません
Spring-S2DaoVia:http://d.hatena.ne.jp/n-ichimura/
Spring で DAO – 実装
• S2DAO の設定– 基本的に設定レス– Java ソースコードにメタデータを記述するだ
け – メソッドを命名規則に合わせることで SQL 文
の記述が不要
Spring で DAO – 実装
• Spring-S2DAO の Spring の設定 <bean class="framework.autoregister.FileSystemBeanAutoRegister"> <property name="addPackageName"> <value>jp.springframework.vol2.dao</value> </property> <property name="addClassNames"> <value>.*Dao</value> </property> <property name="ignorePackageName"> <value>jp.springframework.vol2.dao</value> </property> <property name="ignoreClassNames"> <value>ElectricGuitarGenericDao,IteratorDao</value> </property> </bean>
S2Container の AutoRegister に似た機能( org.seasar.framework.container.autoregister.FileSystemComponentAutoRegister )なので個別に DAO の Bean 定義をしません
Spring で DAO – 実装
• ORM サポートクラスは他にもいろいろ– org.springframework.orm パッケージ以下– Hibernate 、 iBATIS 、 TopLink 、 JPA 、 etc…
詳しくは
Spring で DAO – 設定
Spring で DAO – 設定• AOP でトランザクション設定
– トランザクション単位を考えようsrc/main/resources/context/applicationContext-transaction.xml
<aop:config> <aop:advisor pointcut="execution(* *..*MyBusinessLogic.*(..))" advice-ref="txAdvice"/> </aop:config>
<tx:advice id="txAdvice"> <tx:attributes> <tx:method name="calculateAmount" read-only="true"/> </tx:attributes> </tx:advice>
Spring 2.Xから使える
ビジネスロジックにトランザクションをかけた場合その中の DAOのトランザクションはすべて同一になる
Spring で DAO – テスト
• Spring で用意されたテストサポートクラスを使う( org.springframework.test.AbstractTransactionalDataSourceSpringContextTests )– Spring のコンテナを自動で初期化してくれる
– Setter があれば自動で Inject してくれる
– 自動でトランザクションの開始&ロールバック– テスト用 SQL 実行メソッドがある
Spring で DAO – テスト
executeSqlScript("classpath:testdata.sql", false);
@Override protected String[] getConfigLocations() { return new String[] {"classpath:context/applicationContextHibernate.xml" }; }
private ElectricGuitarDao electricGuitarDao;public void setElectricGuitarDao(ElectricGuitarDao newElectricGuitarDao) { this.electricGuitarDao = newElectricGuitarDao; }
Spring で DAO – テストHibernate 用 DAO のサンプルコード
public class ElectricGuitarHibernateDaoImplTest extends AbstractTransactionalDataSourceSpringContextTests {
private ElectricGuitarDao electricGuitarDao;
public void setElectricGuitarDao(ElectricGuitarDao newElectricGuitarDao) { this.electricGuitarDao = newElectricGuitarDao; }
@Override protected String[] getConfigLocations() { return new String[] {"classpath:context/applicationContextHibernate.xml" }; }
public void testInsert() { ElectricGuitar guitar = new ElectricGuitar(); guitar.setManufacture(ManufactureEnum.fender); guitar.setName("'56 Stratocaster"); guitar.setCraftedDate(new Date()); guitar.setPrice(264320); String id = this.electricGuitarDao.insert(guitar);
assertEquals(id, this.electricGuitarDao.load(id).getId()); } ・ ・
Spring で DAO- テスト• DAO を使うクラスをテストする場
合はモックを使う– 圧倒的なパフォーマンスの差
• インテグレーションテストを行う際に威力を発揮
• MyBusinessLogicDaoTest
• MyBusinessLogicMockTest
– 作業の非同期化• DAO の実装なしでもビジネスロジッ
クのテストを書ける
Spring で DAO – テスト
• モックを使った実装サンプル– ビジネスロジック( MyBusinessLogic )では、割
引入り合計金額計算( calcuateAmount )を行う
Spring で DAO – テスト
• easymock ( http://www.easymock.org/)• 手順
1.モックを準備2.モックの動きを用意3.テストを実施4.モックの動きを確認
Spring で DAO – テスト
• 手順 1: モックを準備private MyBusinessLogicImpl myBusinessLogicImpl;private ElectricGuitarDao mock;
protected void setUp() { mock = createMock(ElectricGuitarDao.class); myBusinessLogicImpl = new MyBusinessLogicImpl(); myBusinessLogicImpl.setElectricGuitarDao(mock);}
インターフェースを指定してモックを作る( static importも便利だよ)
あとは普通に Injectするだけ
Spring で DAO – テスト
• 手順 2: モックの動きを用意
ElectricGuitar a = new ElectricGuitar();a.setManufacture(ManufactureEnum.gretsch);a.setPrice(100000);expect(mock.load("1")).andReturn(a);expectLastCall().times(2);
ElectricGuitar b = new ElectricGuitar();b.setManufacture(ManufactureEnum.fender);b.setPrice(200000);expect(mock.load("2")).andReturn(b);
replay(mock);
これを呼ばれると、 これが返るよ
それを 2回
これで用意完了
Spring で DAO – テスト
• 手順 3: テストを実施– 普通にテストするだけ
long amount = myBusinessLogicImpl.calculateAmount(new String[] {"1" });assertEquals(80000, amount);
amount = myBusinessLogicImpl.calculateAmount(new String[] {"1", "2" });assertEquals(260000, amount);
Spring で DAO – テスト
• 手順 4: モックの動きを確認– 用意した通りに動いたことを確認
verify(mock);
Spring で DAO – テスト
GenericDao with Spring
GenericDao with Spring
• GenericDao とは– Generics と AOP を使った DAO 支援フレー
ムワーク• http://www-06.ibm.com/jp/developerworks/java/060705/j_j-genericdao.shtml
– 実装クラスレスで DAO が作れちゃう。 O/Rマッパのラッパとして非常に便利
– Spring と一緒に使うべし• 今回は Hibernate を利用
GenericDao with Spring
• 実装サンプル– Generics を使うことで、型つきの CRUD メ
ソッドが準備される– Finder メソッドは、メソッド名と同じ query
を用意する
GenericDao with Spring
• インターフェースとマッピングファイルだけpublic interface ElectricGuitarGenericDao
extends GenericDao<ElectricGuitar, String>{ public ElectricGuitar findByName(String name); public List<ElectricGuitar> findAll();}
<hibernate-mapping package="jp.springframework.vol2.domain"> <class name="ElectricGuitar" table="ELECTRIC_GUITAR"> <id name="id" column="ID" type="string" length="40"> … </class> <query name="ElectricGuitar.findByName"> <![CDATA[select e from ElectricGuitar e where e.name = ? ]]> </query> <query name="ElectricGuitar.findAll"> <![CDATA[select e from ElectricGuitar e order by e.price]]> </query> </hibernate-mapping>
GenericDao with Spring
• 仕組み– CRUD の実装は普通– Finder の実装は AOP
• コード見てね
気になること
気になること
• そもそも O/R マッパって遅い– マッピングコストが高い
• 不必要な項目も取得してしまう• 現状では解決は難しい
気になること
• 返値 List の Finder で OutOfMemory になる– バッチ処理やファイルダウンロード処理であ
りがち– レコードの数だけマッピングしてしまう
• そういう場合は Iterator を使おう– next() するごとにマッピング
気になること
• 複数レコードの更新・削除処理が遅い– CRUD しかないと、レコードの数だけ Updat
e や Delete を繰り返し• 専用メソッドを用意しよう
– Update 文や Delete 文– StoredProcedure
気になること
• キャッシュしたい– DAOでキャッシュ
• Hibernate は自動でやってくれる• OSCache などで自分でキャッシュ
気になること
• パフォーマンス計測したい– AOP をつかうと楽
• Bean 定義したクラスのメソッドの実行時間計測public class TraceInterceptor implements MethodInterceptor { private static Log log = LogFactory.getLog(TraceInterceptor.class); public static ThreadLocal<String> local = new ThreadLocal<String>(); public Object invoke(MethodInvocation invocation) throws Throwable { String key = local.get(); if ( StringUtils.isEmpty(key) || "null".equals(key) ) { key = UUID.randomUUID().toString(); local.set(key); } log.debug("," + key + "," + System.currentTimeMillis() + "," + getInvocationDescription(invocation) + ",start"); Object value = invocation.proceed(); log.debug("," + key + "," + System.currentTimeMillis() + "," + getInvocationDescription(invocation) + ",end"); return value; }
気になること
• N+ 1問題– 親レコードの数だけ子レコードを取得
する SQL が発行されてしまう• 注文と注文商品みたいな関係
• Lazy ロードで回避できるが・・・– トランザクション注意!
• トランザクション終了前に子レコードを取得する
気になること
• SQL インジェクション攻撃• プリペアドステートメント推奨
– SQL の構造が変わらない– とはいっても素のステートメントも使
いたい・・・• MySQL のクエリーキャッシュが効かない• 使う場合きちんとエスケープする
気になること
• エンティティ露出問題– ビュー層で必要とされるモデルとのミスマッチ
• ビュー層でマスターデータ参照したい– マスターにある項目を表示したいのだけど TransferObj
ect には ID しかはいっていない・・・
– 詰め替え問題• DTO に誰がどこでつめなおすの?• ビジネスロジック層で泣きながら DTO につめる
• 設計上の問題なので解決策があるわけではない
Q&A
ライセンスについて• JSUG マスコットアイコン(本スライド左下)が残されている場合に限り、本作品
(またそれを元にした派生作品)の複製・頒布・表示・上演を認めます。
• 非商用目的に限り、本作品(またそれを元にした派生作品)の複製・頒布・表示・上演を認めます。
• 本作品のライセンスを遵守する限り、派生作品を頒布することを許可します。