JavaのDAOパターン
1概要
データアクセスオブジェクト(DAO)パターンは、抽象APIを使用して** アプリケーション/ビジネス層を永続層(通常はリレーショナルデータベースですが、他の永続メカニズムでも構いません)から分離することを可能にする構造パターンです。
このAPIの機能は、基礎となるストレージメカニズムでCRUD操作を実行することに関連するすべての複雑さをアプリケーションから隠すことです。これにより、互いについて何も知らずに両方の層を別々に進化させることができます。
このチュートリアルでは、パターンの実装について深く掘り下げ、https://docs.oracle.com/javaee/7/api/javax/persistence/への呼び出しを抽象化するための使用方法を学びます。 EntityManager.html[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層を定義しましょう。そうすれば、ドメインモデルを永続層から完全に切り離すことができるかどうかがわかります。
これがDAO APIです。
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", "[email protected]"));
users.add(new User("Susan", "[email protected]"));
}
@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 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> user = userDao.get(id);
return user.orElseGet(
() -> new User("non-existing user", "no-email"));
}
}
例は人為的ですが、一言で言えば、DAOパターンの背後にある動機を示しています。この場合、
main
メソッドは
UserDao
インスタンスを使用して、いくつかの
User
オブジェクトに対してCRUD操作を実行します。
-
このプロセスの最も重要な側面は、
UserDao
がオブジェクトの永続化、更新、および削除に関するすべての低レベルの詳細をアプリケーションから隠す方法です。
3 JPAでパターンを使用する
パターンはJPAのエンティティマネージャによって提供されるものの上に実装された抽象化および複雑さの単なる別の層になるので、JPAのリリースはDAOパターンの機能性をゼロにすると考える一般的な傾向があります。
確かに、いくつかのシナリオではこれは本当です。そうであっても
、エンティティマネージャのAPIのいくつかのドメイン固有のメソッドだけを私たちのアプリケーションに公開したいことがあります。
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実装でサポートされている任意のリレーショナルデータベースと連携できます。
さらに、このクラスを詳しく見ると、https://en.wikipedia.org/wiki/Composition
over
inheritance[Composition]およびhttps://en.wikipedia.org/wiki/Dependency__injection[Dependencyの使用方法がわかります。インジェクション]を使用すると、アプリケーションで必要なエンティティマネージャメソッドのみを呼び出すことができます。
簡単に言うと、エンティティマネージャの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エンティティマネージャを取得する必要があるデータベース内。
ほとんどの場合、標準的なアプローチである典型的な
“ persistence.xml”
ファイルを介してこれを実現します。
この場合は、
“xml-less”
アプローチを取り、Hibernateの便利な
https://docs.jboss.org/hibernate/orm/5.0/javadocs/org/hibernate/を介して、プレーンJavaでエンティティマネージャを取得します。
jpa/boot/internal/EntityManagerFactoryBuilderImpl.html[EntityManagerFactoryBuilderImpl]
クラス。
JavaでJPA実装をブートストラップする方法の詳細な説明については、リンク/java-bootstrap-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", "[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> 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パターンの機能をエンティティマネージャが提供する機能と統合する方法を説明するのに役立ちます。
ほとんどのアプリケーションには、
UpaApplication
クラスに
JpaUserDao
インスタンスを挿入する役割を担うDIフレームワークがあります。簡単にするために、このプロセスの詳細は省略しました。
ここで強調しなければならない最も重要な点は、
JpaUserDao
クラスがどのように
UserApplication
クラスを永続層がCRUD操作を実行するかについて完全に不可知論者であることを保つのを助けることです。
さらに、MySQLを他のRDBMSと(そしてフラットデータベースでさえも)入れ替えることができましたが、
Dao
インターフェースとエンティティマネージャによって提供される抽象化レベルのおかげで、アプリケーションは期待通りに機能し続けます。
4結論
この記事では、DAOパターンの重要な概念、それをJavaで実装する方法、およびJPAのエンティティー・マネージャーの上でそれを使用する方法について詳しく説明しました。
いつものように、この記事で示したすべてのコードサンプルはhttps://github.com/eugenp/tutorials/tree/master/patterns/design-patterns/src/main/java/com/baeldung/daopattern[over on GitHubから入手できます]。