1. 概要

データアクセスオブジェクト(DAO)パターンは、抽象APIを使用してアプリケーション/ビジネスレイヤーを永続レイヤー(通常はリレーショナルデータベースですが、他の永続メカニズムでもかまいません)から分離できる構造パターンです。

APIは、基盤となるストレージメカニズムでCRUD操作を実行する際の複雑さをすべてアプリケーションから隠します。 これにより、お互いについて何も知らなくても、両方のレイヤーを別々に進化させることができます。

このチュートリアルでは、パターンの実装について深く掘り下げ、JPAエンティティマネージャーへの呼び出しを抽象化するためにそれを使用する方法を学習します。

2. 簡単な実装

DAOパターンがどのように機能するかを理解するために、基本的な例を作成しましょう。

ユーザーを管理するアプリケーションを開発したいとします。 アプリケーションのドメインモデルがデータベースに完全に依存しないようにしたいのです。 そこで、これらのコンポーネントを互いにきちんと切り離しておくための単純なDAOクラスを作成します。

2.1. ドメインクラス

アプリケーションはユーザーと連携するため、ドメインモデルを実装するためのクラスを1つだけ定義する必要があります。

public class User {
    
    private String name;
    private String email;
    
    // constructors / standard setters / getters
}

User クラスは、ユーザーデータの単なるコンテナーであるため、強調する価値のある他の動作を実装していません。

もちろん、ここでの重要な設計上の選択は、このクラスを使用するアプリケーションを、実装可能な永続性メカニズムから分離する方法です。

そして、それはまさにDAOパターンが対処しようとする問題です。

2.2. DAO API

基本的なDAOレイヤーを定義して、ドメインモデルを永続レイヤーから完全に切り離して維持する方法を見てみましょう。

DAOAPIは次のとおりです。

public interface Dao<T> {
    
    Optional<T> get(long id);
    
    List<T> getAll();
    
    void save(T t);
    
    void update(T t, String[] params);
    
    void delete(T t);
}

鳥瞰図から、 Dao インターフェースが、タイプTのオブジェクトに対してCRUD操作を実行する抽象APIを定義していることは明らかです。

インターフェイスが提供する高レベルの抽象化により、Userオブジェクトで機能する具体的できめ細かい実装を簡単に作成できます。

2.3. UserDaoクラス

Daoインターフェースのユーザー固有の実装を定義しましょう。

public class UserDao implements Dao<User> {
    
    private List<User> users = new ArrayList<>();
    
    public UserDao() {
        users.add(new User("John", "john@domain.com"));
        users.add(new User("Susan", "susan@domain.com"));
    }
    
    @Override
    public Optional<User> get(long id) {
        return Optional.ofNullable(users.get((int) id));
    }
    
    @Override
    public List<User> getAll() {
        return users;
    }
    
    @Override
    public void save(User user) {
        users.add(user);
    }
    
    @Override
    public void update(User user, String[] params) {
        user.setName(Objects.requireNonNull(
          params[0], "Name cannot be null"));
        user.setEmail(Objects.requireNonNull(
          params[1], "Email cannot be null"));
        
        users.add(user);
    }
    
    @Override
    public void delete(User user) {
        users.remove(user);
    }
}

UserDao クラスは、 User オブジェクトのフェッチ、更新、および削除に必要なすべての機能を実装します。

簡単にするために、 users List は、コンストラクター内にいくつかのUserオブジェクトが格納されているインメモリデータベースのように機能します。

もちろん、他のメソッドをリファクタリングして、たとえばリレーショナルデータベースで機能できるようにするのは簡単です。

UserクラスとUserDaoクラスの両方が同じアプリケーション内で独立して共存しますが、永続化レイヤーをアプリケーションロジックから隠しておくために、後者をどのように使用できるかを確認する必要があります。

public class UserApplication {

    private static Dao<User> userDao;

    public static void main(String[] args) {
        userDao = new UserDao();
        
        User user1 = getUser(0);
        System.out.println(user1);
        userDao.update(user1, new String[]{"Jake", "jake@domain.com"});
        
        User user2 = getUser(1);
        userDao.delete(user2);
        userDao.save(new User("Julie", "julie@domain.com"));
        
        userDao.getAll().forEach(user -> System.out.println(user.getName()));
    }

    private static User getUser(long id) {
        Optional<User> user = userDao.get(id);
        
        return user.orElseGet(
          () -> new User("non-existing user", "no-email"));
    }
}

この例は考案されていますが、DAOパターンの背後にある動機を一言で示しています。 この場合、 main メソッドは、 UserDao インスタンスを使用して、いくつかのUserオブジェクトに対してCRUD操作を実行します。

このプロセスの最も関連性のある側面は、 UserDao が、オブジェクトの永続化、更新、および削除の方法に関するすべての低レベルの詳細をアプリケーションから隠す方法です。

3. JPAでのパターンの使用

開発者の間では、JPAのリリースがDAOパターンの機能をゼロにダウングレードしたと考える傾向があります。 このパターンは、JPAのエンティティーマネージャーによって提供されるものに加えて、抽象化と複雑さの単なる別のレイヤーになります。

これは、一部のシナリオに当てはまります。 それでも、アプリケーションにエンティティマネージャーのAPIのドメイン固有のメソッドをいくつか公開したい場合があります。このような場合、DAOパターンがその役割を果たします。

3.1. JpaUserDaoクラス

Dao インターフェースの新しい実装を作成して、JPAのエンティティマネージャーがすぐに提供する機能をカプセル化する方法を見てみましょう。

public class JpaUserDao implements Dao<User> {
    
    private EntityManager entityManager;
    
    // standard constructors
    
    @Override
    public Optional<User> get(long id) {
        return Optional.ofNullable(entityManager.find(User.class, id));
    }
    
    @Override
    public List<User> getAll() {
        Query query = entityManager.createQuery("SELECT e FROM User e");
        return query.getResultList();
    }
    
    @Override
    public void save(User user) {
        executeInsideTransaction(entityManager -> entityManager.persist(user));
    }
    
    @Override
    public void update(User user, String[] params) {
        user.setName(Objects.requireNonNull(params[0], "Name cannot be null"));
        user.setEmail(Objects.requireNonNull(params[1], "Email cannot be null"));
        executeInsideTransaction(entityManager -> entityManager.merge(user));
    }
    
    @Override 
    public void delete(User user) {
        executeInsideTransaction(entityManager -> entityManager.remove(user));
    }
    
    private void executeInsideTransaction(Consumer<EntityManager> action) {
        EntityTransaction tx = entityManager.getTransaction();
        try {
            tx.begin();
            action.accept(entityManager);
            tx.commit(); 
        }
        catch (RuntimeException e) {
            tx.rollback();
            throw e;
        }
    }
}

JpaUserDao クラスは、JPA実装でサポートされている任意のリレーショナルデータベースで機能します。

また、クラスをよく見ると、CompositionDependencyInjection を使用すると、アプリケーションに必要なエンティティマネージャーメソッドのみを呼び出すことができることがわかります。

簡単に言うと、エンティティマネージャーのAPI全体ではなく、ドメイン固有のカスタマイズされたAPIがあります。

3.2. Userクラスのリファクタリング

この場合、JPAのデフォルト実装としてHibernateを使用するため、それに応じてUserクラスをリファクタリングします。

@Entity
@Table(name = "users")
public class User {
    
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;
    
    private String name;
    private String email;
    
    // standard constructors / setters / getters
}

3.3. JPAエンティティマネージャのブートストラップ プログラムで

ローカルまたはリモートで実行されているMySQLの作業インスタンスがすでにあり、データベーステーブル「users」にいくつかのユーザーレコードが入力されていると仮定すると、を使用できるようにJPAエンティティマネージャーを取得する必要があります。データベースでCRUD操作を実行するためのJpaUserDaoクラス。

ほとんどの場合、これは通常のpersistence.xmlファイルを介して実現されます。これは標準的なアプローチです。

この場合、XMLを使用しないアプローチを採用し、Hibernateの便利なEntityManagerFactoryBuilderImplクラスを介してプレーンなJavaを持つエンティティマネージャーを取得します。

JavaでJPA実装をブートストラップする方法の詳細については、この記事を確認してください。

3.4. UserApplicationクラス

最後に、最初の UserApplication クラスをリファクタリングして、 JpaUserDao インスタンスと連携し、UserエンティティでCRUD操作を実行できるようにします。

public class UserApplication {

    private static Dao<User> jpaUserDao;

    // standard constructors
    
    public static void main(String[] args) {
        User user1 = getUser(1);
        System.out.println(user1);
        updateUser(user1, new String[]{"Jake", "jake@domain.com"});
        saveUser(new User("Monica", "monica@domain.com"));
        deleteUser(getUser(2));
        getAllUsers().forEach(user -> System.out.println(user.getName()));
    }
    
    public static User getUser(long id) {
        Optional<User> user = jpaUserDao.get(id);
        
        return user.orElseGet(
          () -> new User("non-existing user", "no-email"));
    }
    
    public static List<User> getAllUsers() {
        return jpaUserDao.getAll();
    }
    
    public static void updateUser(User user, String[] params) {
        jpaUserDao.update(user, params);
    }
    
    public static void saveUser(User user) {
        jpaUserDao.save(user);
    }
    
    public static void deleteUser(User user) {
        jpaUserDao.delete(user);
    }
}

ここでの例はかなり限定されています。 ただし、DAOパターンの機能をエンティティマネージャーが提供する機能と統合する方法を示すのに役立ちます。

ほとんどのアプリケーションには、JpaUserDaoインスタンスをUserApplicationクラスに挿入するDIフレームワークがあります。 簡単にするために、このプロセスの詳細は省略しました。

ここで強調する最も重要な点は、 JpaUserDaoクラスが、永続層がCRUD操作を実行する方法についてUserApplicationクラスを完全に認識しないようにするのにどのように役立つかです。

さらに、MySQLを他のRDBMS(さらにはフラットデータベース)と交換することもでき、 Dao インターフェースによって提供される抽象化のレベルのおかげで、アプリケーションは期待どおりに機能し続けます。およびエンティティマネージャ。

4. 結論

この記事では、DAOパターンの主要な概念について詳しく見てきました。 Javaでそれを実装する方法と、JPAのエンティティマネージャー上でそれを使用する方法を見ました。

いつものように、この記事に示されているすべてのコードサンプルは、GitHubから入手できます。