JPAでGenericDao

JavaSE5から導入されたGeneric(型パラメータ)の機能とJPAを使えば各Entity(DBのテーブル)に対するDaoを作成する必要がなくなります。


まずEntityクラスのインターフェースを作成します。

public interface IEntity extends Serializable{

}

この段階ではマーカーインターフェースの役割ですが、次回以降機能拡張する場合にこれが役に立ちます。


次はGenericなDaoのインターフェースを作成します。

public interface IGenericDao<E extends IEntity,PK extends Serializable> {

    E findByPrimaryKey(PK primaryKey);

    void persist(E entity);

    void merge(E entity);

    void remove(E entity);

}

このクラスを文章で説明すると
「IEntityインターフェースを継承したクラスとSerializeインターフェースを実装したクラスの2つをパラメータとしてとるIGenericDaoインターフェース」という意味です。
こう宣言することでEとPKというパラメータをメソッドのシグニチャや戻り値の型として宣言することが可能になります。
例えば

 E findByPrimaryKey(PK primaryKey);

というメソッドは「Serializableを実装したオブジェクトを引数に取り、IEntityを継承したオブジェクトを返す抽象メソッド」という意味になります。

次はこのIGenericDaoを実装するクラスを作成します。
こんな感じ

public class GenericDaoJpa<E extends IEntity, PK extends Serializable> 
                                          implements IGenericDao<E,PK> {

    private EntityManager em;
    private Class<E> clazz;
    
    
    public GenericDaoJpa(EntityManager em, Class<E> clazz){
        this.em = em;
        this.clazz = clazz;
    }

    @Override
    public E findByPrimaryKey(PK primaryKey) {
        return em.find(clazz, primaryKey);
    }

    @Override
    public void merge(E entity) {
        em.merge(entity);
    }

    @Override
    public void persist(E entity) {
        em.persist(entity);
    }

    @Override
    public void remove(E entity) {
        em.remove(entity);
    }
}

このクラスではEntityManagerとIEntityを実装したクラスのClassオブジェクトをコンストラクタの引数にとります。
こうすることでem.findメソッドの引数に必要なClassオブジェクトを指定することができます。また、コンストラクタでなくてもセッターメソッドでももちろん問題ありません。

次はこのGenericDaoJpaのインスタンスを生成するFactoryクラスを作成します。
こんな感じ

public class GenericDaoFactory {

    private EntityManager entityManager;
    
    public IGenericDao get(Class<? extends IEntity> clazz){
        return new GenericDaoJpa(entityManager,clazz);
    }

    public void setEntityManager(EntityManager entityManager) {
        this.entityManager = entityManager;
    }
}

getメソッドでIEntityを継承したクラスのClassオブジェクトを引数にとり、GenericDaoJpaのインスタンスを生成し返します。
これにより型に対して安全なDaoを取得することが可能です。

これらを実際に使用するClientクラスはこんな感じになります。
(EmployeeクラスとCompanyクラスはJPAにおけるEntityクラスで、IEntityを実装しています)

public class GenericDaoMain {

    public static void main(String[] args) {
        EntityManagerFactory emFactory = 
                Persistence.createEntityManagerFactory("GenericDaoUnit");
        EntityManager em = emFactory.createEntityManager();
        em.getTransaction().begin();
        GenericDaoFactory factory = new GenericDaoFactory();
        factory.setEntityManager(em);
        
        IGenericDao<Company,Integer> companyDao = 
                                         factory.get(Company.class);
        IGenericDao<Employee, Integer> employeeDao = 
                                         factory.get(Employee.class);
        
        Company company = companyDao.findByPrimaryKey(1);
        Employee emp = employeeDao.findByPrimaryKey(5);
        emp.setEmployeeName("foo");
        employeeDao.merge(emp);
        
        employeeDao.findByPrimaryKey("1");	//コンパイルエラー!!
        employeeDao.merge(company);			//コンパイルエラー!!
        em.getTransaction().commit();
    }
}

employeeDaoのfindByPrimaryKeyのメソッドにはInteger(int)のみが引数として使用可能で、String等ではコンパイルエラーになります。
また、mergeの場合もemployeeDaoではEmployeeオブジェクトのみが引数として使用可能で、それ以外ではコンパイルエラーになります。
これで今までテーブル単位で大量に作成しなければならなかったDaoを1つも作成することなくシステム構築が可能になります。
しかしこのままでは何かと不便です。例えばWhere句にいろいろ指定する場合とかJoinするSelect文を発行したい場合等です。
そのあたりは次回のエントリーにて書きます。