1. 概要

この記事では、システム内のすべてのエンティティに単一の一般化されたデータアクセスオブジェクトを使用することにより、 DAOレイヤーを簡素化することに焦点を当てます。これにより、不要な混乱や冗長性のないエレガントなデータアクセスが実現します。

以前の記事のSpringとHibernateで見たAbstractDAOクラスに基づいて構築し、ジェネリックスのサポートを追加します。

2. HibernateおよびJPADAO

ほとんどの本番コードベースには、ある種のDAOレイヤーがあります。 通常、実装は、抽象基本クラスのない複数のクラスから、ある種の生成されたクラスにまで及びます。 ただし、一貫していることが1つあります。それは、が常に複数あるということです。 ほとんどの場合、DAOとシステム内のエンティティの間には1対1の関係があります。

また、関連するジェネリックスのレベルに応じて、実際の実装は、重複の多いコードからほとんど空のコードまでさまざまであり、ロジックの大部分は基本抽象クラスにグループ化されます。

これらの複数の実装は、通常、単一のパラメーター化されたDAOに置き換えることができます。 Java Genericsが提供する型安全性を最大限に活用することで、機能が失われないように実装できます。

次に、この概念の2つの実装を示します。1つはHibernate中心の永続層用で、もう1つはJPAに焦点を当てています。 これらの実装は完全ではありませんが、含まれているデータアクセス方法を簡単に追加できます。

2.1. 抽象HibernateDAO

AbstractHibernateDaoクラスを簡単に見てみましょう。

public abstract class AbstractHibernateDao<T extends Serializable> {
    private Class<T> clazz;

    @Autowired
    protected SessionFactory sessionFactory;

    public void setClazz(final Class<T> clazzToSet) {
        clazz = Preconditions.checkNotNull(clazzToSet);
    }

    public T findOne(final long id) {
        return (T) getCurrentSession().get(clazz, id);
    }

    public List<T> findAll() {
        return getCurrentSession().createQuery("from " + clazz.getName()).list();
    }

    public T create(final T entity) {
        Preconditions.checkNotNull(entity);
        getCurrentSession().saveOrUpdate(entity);
        return entity;
    }

    public T update(final T entity) {
        Preconditions.checkNotNull(entity);
        return (T) getCurrentSession().merge(entity);
    }

    public void delete(final T entity) {
        Preconditions.checkNotNull(entity);
        getCurrentSession().delete(entity);
    }

    public void deleteById(final long entityId) {
        final T entity = findOne(entityId);
        Preconditions.checkState(entity != null);
        delete(entity);
    }

    protected Session getCurrentSession() {
        return sessionFactory.getCurrentSession();
    }
}

これは、エンティティを操作するために SessionFactory を使用する、いくつかのデータアクセスメソッドを持つ抽象クラスです。

ここでは、Google Guavaの前提条件を使用して、メソッドまたはコンストラクターが有効なパラメーター値で呼び出されるようにしています。 前提条件が失敗した場合、調整された例外がスローされます。

2.2. Generic Hibernate DAO

抽象DAOクラスができたので、これを1回だけ拡張できます。 一般的なDAO実装は、必要な唯一の実装になります。

@Repository
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class GenericHibernateDao<T extends Serializable>
  extends AbstractHibernateDao<T> implements IGenericDao<T>{
   //
}

まず、汎用実装自体がパラメーター化されていることに注意してください。これにより、クライアントはケースバイケースで正しいパラメーターを選択できます。 これは、クライアントがエンティティごとに複数のアーティファクトを作成する必要なしに、型安全性のすべての利点を享受できることを意味します。

次に、この汎用DAO実装のプロトタイプスコープに注目してください。 このスコープを使用すると、Springコンテナーは、要求されるたびに(自動配線を含む)、DAOの新しいインスタンスを作成します。 これにより、サービスは、必要に応じて、エンティティごとに異なるパラメータを持つ複数のDAOを使用できるようになります。

このスコープが非常に重要である理由は、Springがコンテナー内のBeanを初期化する方法によるものです。 スコープなしで汎用DAOを残すということは、デフォルトのシングルトンスコープを使用することを意味します。これにより、DAOの単一インスタンスがコンテナに存在することになります。 これは明らかに、あらゆる種類のより複雑なシナリオでは主に制限されます。

IGenericDao は、必要な実装を注入できるように、すべてのDAOメソッドの単なるインターフェイスです。

public interface IGenericDao<T extends Serializable> {
    void setClazz(Class< T > clazzToSet);

    T findOne(final long id);

    List<T> findAll();

    T create(final T entity);

    T update(final T entity);

    void delete(final T entity);

    void deleteById(final long entityId);
}

2.3. 抽象JPADAO

AbstractJpaDao は、 AbstractHibernateDao:と非常によく似ています。

public abstract class AbstractJpaDAO<T extends Serializable> {
    private Class<T> clazz;

    @PersistenceContext(unitName = "entityManagerFactory")
    private EntityManager entityManager;

    public final void setClazz(final Class<T> clazzToSet) {
        this.clazz = clazzToSet;
    }

    public T findOne(final long id) {
        return entityManager.find(clazz, id);
    }

    @SuppressWarnings("unchecked")
    public List<T> findAll() {
        return entityManager.createQuery("from " + clazz.getName()).getResultList();
    }

    public T create(final T entity) {
        entityManager.persist(entity);
        return entity;
    }

    public T update(final T entity) {
        return entityManager.merge(entity);
    }

    public void delete(final T entity) {
        entityManager.remove(entity);
    }

    public void deleteById(final long entityId) {
        final T entity = findOne(entityId);
        delete(entity);
    }
}

Hibernate DAOの実装と同様に、現在廃止されているSpring JpaTemplate に依存することなく、JavaPersistenceAPIを直接使用しています。

2.4. 汎用JPADAO

Hibernateの実装と同様に、JPAデータアクセスオブジェクトも簡単です。

@Repository
@Scope( BeanDefinition.SCOPE_PROTOTYPE )
public class GenericJpaDao< T extends Serializable >
 extends AbstractJpaDao< T > implements IGenericDao< T >{
   //
}

3. このDAOを注入する

注入できるDAOインターフェイスが1つになりました。クラスを指定する必要もあります:

@Service
class FooService implements IFooService{

   IGenericDao<Foo> dao;

   @Autowired
   public void setDao(IGenericDao<Foo> daoToSet) {
      dao = daoToSet;
      dao.setClazz(Foo.class);
   }

   // ...
}

Spring は、セッターインジェクションを使用して新しいDAOインスタンスを自動配線し、Classオブジェクトを使用して実装をカスタマイズできるようにします。 この時点以降、DAOは完全にパラメーター化され、サービスで使用できるようになります。

もちろん、クラスをDAOに指定する方法は他にもあります。リフレクションを介して、またはXMLでさえもです。 私の好みは、反射を使用する場合に比べて読みやすさと透明性が向上しているため、この単純なソリューションを優先します。

4. 結論

この記事では、汎用DAOの単一の再利用可能な実装を提供することによるデータアクセス層の簡素化について説明しました。 HibernateとJPAベースの両方の環境での実装を示しました。 その結果、不要な混乱がなく、合理化された永続層が得られます。

Javaベースの構成とプロジェクトの基本的なMavenpomを使用したSpringコンテキストのセットアップに関するステップバイステップの紹介については、この記事を参照してください。

最後に、この記事のコードはGitHubプロジェクトにあります。