Top / Hibernate / SpringとGenericを使って、汎用的なDAOを作成する

IBMのサイトにHibernateとSpring AOPで、汎用性と型安全性を備えたDAOを作るという非常に興味深い記事を発見。これは業務アプリを作るときにいつも作成するDAOを効率よく作成する方法をまとめた記事です。通常DAOの作成って、エンティティごとに似たようなコーディングをしなくてはいけなくてひじょーに煩わしいのですが、GenericsとSpringを使うことによってこの面倒な作業から解放されます。目からウロコですね。

やってみる

エンティティなどはHibernate/Springを使ってトランザクション処理を記述するのものをそのまま流用します。

  • DDL
     create table MKINO.USER_ATTR (
     USERID varchar2(100) not null,
     NAME varchar2(1000), primary key (USERID)
    );
  • クラス図
    classdiagram.png

このクラス図で、UserDao?に当たる部分はエンティティごとに実装を書かなくてはいけないのですが、ココを楽することができるようになります。

まずは基底クラスを作成

UserDao?の既定となるクラスを作成します。

import java.io.Serializable;

public interface GenericDao<T, PK extends Serializable> {
  PK create(T newInstance);
  T read(PK id);
  void update(T transientObject);
  void delete(T persistentObject);
}

エンティティに対するCRUDのインタフェースを定義しました。Genericsを使ってプライマリキーとエンティティをパラメータ化しているのがポイントです。

次に実装クラスをHibernateとSpringを使って実装します。

import java.io.Serializable;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

public class GenericDaoHibernateImpl<T, PK extends Serializable> extends
    HibernateDaoSupport implements GenericDao<T, PK> {
  private Class<T> type;

  public GenericDaoHibernateImpl(Class<T> type) {
    this.type = type;
  }

  public PK create(T o) {
    return (PK) getHibernateTemplate().save(o);
  }

  public T read(PK id) {
    return (T) getHibernateTemplate().get(type, id);
  }

  public void update(T o) {
    getHibernateTemplate().update(o);
  }

  public void delete(T o) {
    getHibernateTemplate().delete(o);
  }

}

実装クラスでも型はパラメータ化されたままになってます。またサブクラスで

return (T) getHibernateTemplate().get(type, id);

の部分が

return (Hoge) getHibernateTemplate().get(Hoge.class, id);

とならなくてはならないのでClassクラスを引数に取るコンストラクタ

public GenericDaoHibernateImpl(Class<T> type) {
  this.type = type;
}

が定義されています。んでメソッドは

return (T) getHibernateTemplate().get(type, id);

と記述しておくわけですね。

さて準備はできました。

インタフェースIUserDao?を作成する

次にUserDao?のインタフェースIUserDao?を作成します。これはココのやり方でなくても作成するいわゆるDAOのインタフェースです。通常は

import nu.mine.kino.entity.UserAttr;

public interface IUserDao {
  String create(UserAttr newInstance);
  void delete(UserAttr persistentObject);
  UserAttr read(String id);
  void update(UserAttr transientObject);
}

なんてのを定義するのですが、上のGenericDao?インタフェースを作成したので

import nu.mine.kino.entity.UserAttr;

public interface IUserDao extends GenericDao<UserAttr, String> {
}

とテンプレート化された型を指定するだけでOKとなります。ここはエンティティごとに引数や戻り値をちょこちょこ修正して作成するというインタフェースなので、ココの記述が楽になるのはすごくうれしいですね。

IUserDao?の実装クラスを作成する。

次にIUserDao?の実装を作成します。ココも通常は

import java.util.List;
import nu.mine.kino.entity.UserAttr;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

/**
 * @author Masatomi KINO
 * @version $Revision: 1.2 $
 */
public class UserDao extends HibernateDaoSupport implements IUserDao {
  public String create(UserAttr newInstance) {
    return (String) getHibernateTemplate().save(newInstance);
  }

  public void delete(UserAttr persistentObject) {
    getHibernateTemplate().delete(persistentObject);
  }

  public UserAttr read(String id) {
    return (UserAttr) getHibernateTemplate().get(UserAttr.class, id);
  }

  public void update(UserAttr transientObject) {
    getHibernateTemplate().update(transientObject);
  }
}

などとやるわけですが*1GenericDaoHibernateImpl?があるおかげで

import java.util.List;
import nu.mine.kino.entity.UserAttr;

/**
 * とりあえず、IUserDaoの実装クラスとして作成したDAOクラス。 
 * 実際は、IUserDao (とextends GenericDao<UserAttr,String>)
 * の指定だけで十分なので、このクラスは必要ない。
 * 
 * @author Masatomi KINO
 * @version $Revision: 1.1 $
 */
public class UserDao extends GenericDaoHibernateImpl<UserAttr, String>
    implements IUserDao {

  public UserDao(Class<UserAttr> type) {
    super(type);
  }
}

となるだけです。

さてSpringのBean定義は以下のようになります。

<beans default-autowire="no" default-lazy-init="false"
  default-dependency-check="none">

  <bean id="genericUserBL"
      class="nu.mine.kino.genericbl.UserBL">
    <property name="dao">
      <ref bean="userGenericDao"/>
    </property>
  </bean>

  <bean id="userGenericDao"
      class="nu.mine.kino.genericdao.UserDao">
    <constructor-arg>
      <value>nu.mine.kino.entity.UserAttr</value>
    </constructor-arg>
    <property name="sessionFactory">
      <ref bean="sessionFactory"/>
    </property>
  </bean>
</beans>

このようにBLにInjectionしてあげればよいわけです。

実行する

BLは今まで通り作ればよいです。

public interface IUserBL {
  String create(UserAttr newInstance);
  void delete(UserAttr persistentObject);
  UserAttr read(String id);
  void update(UserAttr transientObject);
}

と実装クラスは

public class UserBL implements IUserBL {
  private IUserDao dao;
  public String create(UserAttr newInstance) {return dao.create(newInstance);}
  public void delete(UserAttr persistentObject) {dao.delete(persistentObject);}
  public UserAttr read(String id) {return dao.read(id);}
  public void update(UserAttr transientObject) {dao.update(transientObject);}
  public void setDao(IUserDao dao) {this.dao = dao;}
}

となります。実行するメインクラスは以下の通り。

public class GenericSpringMain {
  public static void main(String[] args) throws IOException {
    DOMConfigurator.configure("log4j.xml");
    ApplicationContext context = new FileSystemXmlApplicationContext(
        new String[] { "beans.xml", "hibernate-spring_test.xml" });
    String name = getUniqueKey();
    IUserBL bl = (IUserBL) context.getBean("genericUserBL");
    bl.someMethod();//なんかBL 
  }

  /**
   * PKを生成したいだけ。本質的な意味ナシ
   */
  private static String getUniqueKey() throws IOException {
    File temp = File.createTempFile("abc", "");
    temp.deleteOnExit();
    String name = temp.getName();
    return name;
  }
}

これだけでも楽になったけど。

これだけでも楽になったけど実はUserDao?の部分はSpringを使ってるので作成する必要はないみたいです。ようするにBean定義を

  <bean id="userGenericDao"
      class="nu.mine.kino.genericdao.GenericDaoHibernateImpl"> <-変更
    <constructor-arg>
      <value>nu.mine.kino.entity.UserAttr</value>
    </constructor-arg>
    <property name="sessionFactory">
      <ref bean="sessionFactory"/>
    </property>
  </bean>

に変更し、BLを

public class UserBL implements IUserBL {
  private GenericDao<UserAttr, String> dao; //IUserDaoをGenericDaoに変更

  //IUserDaoをGenericDaoに変更
  public void setDao(GenericDao<UserAttr, String> dao) {
    this.dao = dao;
  }
  // あとは同じ
}

と変更すればOKです。もはやUserDao?/IUserDao?は必要ナシです。CRUDだけなら汎用的なDAOとSpringのBean指定だけで十分なんですね。

DAOに個別のメソッドを定義したい

ここまでのサンプルで、DAOにCRUDを実装する限りはDAOの実装クラスは必要ないということ、DAOのインタフェースもそれぞれCRUDを定義しないでもGenericsを使ってパラメータ化できることが分かりました。

さて通常DAOにはそのエンティティ固有の検索機能を実装したいですね。たとえば部分一致する検索などですね。それらはどうするのでしょうか。


この記事は

選択肢 投票
おもしろかった 59  
そうでもない 8  
  • 後半が難しい。。これだけややこしいと、人海戦術で開発してるときは無理かなあ?? -- きの? 2007-05-09 (水) 11:35:38
  • GenericDaoHibernateImpl?のprivate Class<T> typeについてなんですが、コンストラクタの引数で初期化しなくても、this.type = (Class<T>) ( (ParameterizedType?) getClass().getGenericSuperclass?() ).getActualTypeArguments?()[0]を引数なしのコンストラクタの中に書けば初期化できると、[Java Persistence with Hibernate]の本に書かれているらしいです -- しょうしょう? 2008-10-14 (火) 23:34:33
  • GenericDaoHibernateImpl?のprivate Class<T> typeについてなんですが、コンストラクタの引数で初期化しなくても、this.type = (Class<T>) ( (ParameterizedType?) getClass().getGenericSuperclass?() ).getActualTypeArguments?()[0]を引数なしのコンストラクタの中に書けば初期化できると、[Java Persistence with Hibernate]の本に書かれているらしいです -- しょうしょう? 2008-10-15 (水) 00:59:05

Top / Hibernate / SpringとGenericを使って、汎用的なDAOを作成する

現在のアクセス:48669


*1 これ、ホントめんどくさいっ

添付ファイル: fileclassdiagram.png 2412件 [詳細]

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2017-08-03 (木) 18:59:15 (107d)