JavaのDAOパターン
1. 概要
データアクセスオブジェクト(DAO)パターンは、isolate the application/business layer from the persistence layer (usually a relational database, but it could be any other persistence mechanism) using an abstract APIを可能にする構造パターンです。
このAPIの機能は、基盤となるストレージメカニズムでCRUD操作を実行する際に伴う複雑さをすべてアプリケーションから隠すことです。 これにより、両方の層が互いに何も知らなくても別々に進化できます。
このチュートリアルでは、パターンの実装について詳しく説明し、JPA entity managerへの呼び出しを抽象化するためにパターンを使用する方法を学習します。
参考文献:
Spring Data JPAの紹介
Spring 4を使用したSpring Data JPAの概要-Spring構成、DAO、手動および生成されたクエリとトランザクション管理。
2. 簡単な実装
DAOパターンがどのように機能するかを理解するために、基本的な例を作成しましょう。
ユーザーを管理するアプリケーションを開発したいとします。 アプリケーションのドメインモデルがデータベースに完全に依存しないようにするために、a simple DAO class that will take care of keeping these components neatly decoupled from each otherを作成します。
2.1. ドメインクラス
アプリケーションはユーザーと連携するため、ドメインモデルを実装するクラスを1つだけ定義する必要があります。
public class User {
private String name;
private String email;
// constructors / standard setters / getters
}
Userクラスは、ユーザーデータの単なるコンテナであるため、強調する価値のある他の動作は実装されていません。
もちろん、ここで行う必要がある最も適切な設計の選択は、このクラスを使用するアプリケーションを、ある時点で実装できる永続化メカニズムから分離しておく方法です。
まあ、それはまさにDAOパターンが対処しようとしている問題です。
2.2. DAO API
基本的なDAOレイヤーを定義して、keep the domain model completely decoupled from the persistence layer.がどのようにできるかを見てみましょう。
DAOAPIは次のとおりです。
public interface Dao {
Optional get(long id);
List 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 {
private List users = new ArrayList<>();
public UserDao() {
users.add(new User("John", "[email protected]"));
users.add(new User("Susan", "[email protected]"));
}
@Override
public Optional get(long id) {
return Optional.ofNullable(users.get((int) id));
}
@Override
public List 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オブジェクトのフェッチ、更新、および削除に必要なすべての機能を実装します。
For simplicity’s sake, the users List acts like an in-memory database, which is populated with a couple of User objects in the constructor。
もちろん、他のメソッドをリファクタリングするのは簡単なので、たとえばリレーショナルデータベースで機能します。
UserクラスとUserDaoクラスの両方が同じアプリケーション内で独立して共存しますが、永続層をアプリケーションロジックから隠しておくために後者をどのように使用できるかを確認する必要があります。
public class UserApplication {
private static Dao userDao;
public static void main(String[] args) {
userDao = new UserDao();
User user1 = getUser(0);
System.out.println(user1);
userDao.update(user1, new String[]{"Jake", "[email protected]"});
User user2 = getUser(1);
userDao.delete(user2);
userDao.save(new User("Julie", "[email protected]"));
userDao.getAll().forEach(user -> System.out.println(user.getName()));
}
private static User getUser(long id) {
Optional user = userDao.get(id);
return user.orElseGet(
() -> new User("non-existing user", "no-email"));
}
}
この例は不自然ですが、一言で言えば、DAOパターンの背後にある動機を示しています。 この場合、mainメソッドはUserDaoインスタンスを使用して、いくつかのUserオブジェクトに対してCRUD操作を実行します。
The most relevant facet of this process is how UserDao hides from the application all the low-level details on how the objects are persisted, updated, and deleted。
3. JPAでのパターンの使用
開発者の間では、JPAのリリースがDAOパターンの機能をゼロにダウングレードしたと考える傾向があります。これは、パターンがJPAのエンティティマネージャーによって提供されるものの上に実装される抽象化と複雑さの単なる別のレイヤーになるためです。
疑いもなく、いくつかのシナリオではこれは真実です。 それでも, sometimes we just want to expose to our application only a few domain-specific methods of the entity manager’s API.このような場合、DAOパターンがその役割を果たします。
3.1. JpaUserDaoクラス
そうは言っても、Daoインターフェースの新しい実装を作成して、JPAのエンティティマネージャーがすぐに提供する機能をカプセル化する方法を見てみましょう。
public class JpaUserDao implements Dao {
private EntityManager entityManager;
// standard constructors
@Override
public Optional get(long id) {
return Optional.ofNullable(entityManager.find(User.class, id));
}
@Override
public List 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 action) {
EntityTransaction tx = entityManager.getTransaction();
try {
tx.begin();
action.accept(entityManager);
tx.commit();
}
catch (RuntimeException e) {
tx.rollback();
throw e;
}
}
}
JpaUserDaoクラスは、JPA実装でサポートされている任意のリレーショナルデータベースを操作できます。
さらに、クラスをよく見ると、CompositionとDependency Injectionを使用すると、アプリケーションに必要なエンティティマネージャメソッドのみを呼び出すことができることがわかります。
簡単に言うと、エンティティマネージャーの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”にいくつかのユーザーレコードが入力されていると仮定すると、JpaUserDaoクラスを使用できるようにJPAエンティティマネージャーを取得する必要がありますデータベースでCRUD操作を実行するため。
ほとんどの場合、これは標準的なアプローチである一般的な“persistence.xml”ファイルを介して実行されます。
この場合、“xml-less”アプローチを採用し、Hibernateの便利なEntityManagerFactoryBuilderImplクラスを介してプレーンJavaを使用するエンティティマネージャーを取得します。
JavaでJPA実装をブートストラップする方法の詳細については、this articleを確認してください。
3.4. UserApplicationクラス
最後に、最初のUserApplicationクラスをリファクタリングして、JpaUserDaoインスタンスで動作し、UserエンティティでCRUD操作を実行できるようにします。
public class UserApplication {
private static Dao jpaUserDao;
// standard constructors
public static void main(String[] args) {
User user1 = getUser(1);
System.out.println(user1);
updateUser(user1, new String[]{"Jake", "[email protected]"});
saveUser(new User("Monica", "[email protected]"));
deleteUser(getUser(2));
getAllUsers().forEach(user -> System.out.println(user.getName()));
}
public static User getUser(long id) {
Optional user = jpaUserDao.get(id);
return user.orElseGet(
() -> new User("non-existing user", "no-email"));
}
public static List 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フレームワークがあります。 簡単にするために、このプロセスの詳細は省略しました。
ここで強調する最も重要なポイントは、the JpaUserDao class helps to keep the UserApplication class completely agnostic about how the persistence layer performs CRUD operationsの方法です。
さらに、MySQLを他のRDBMS(さらにはフラットデータベース)と交換することもできますが、Daoインターフェイスによって提供される抽象化レベルのおかげで、アプリケーションは期待どおりに機能し続けます。およびエンティティマネージャ。
4. 結論
この記事では、DAOパターンの主要な概念、Javaでの実装方法、およびJPAのエンティティマネージャー上での使用方法について詳しく説明しました。
いつものように、この記事に示されているすべてのコードサンプルは利用可能なover on GitHubです。