Javaにおけるデータアクセスオブジェクトパターン:データベース操作の効率化
別名
- データアクセスレイヤ
- DAO
データアクセスオブジェクトデザインパターンの目的
データアクセスオブジェクト(DAO)デザインパターンは、アプリケーションのビジネスロジックと永続化レイヤ(通常はデータベースまたはその他のストレージメカニズム)を分離することを目的としています。DAOを使用することで、アプリケーションは特定のデータベース実装の詳細に依存することなく、データにアクセスして操作できます。
現実世界の例を用いたデータアクセスオブジェクトパターンの詳細な説明
現実世界の例
図書館システムを想像してください。メインアプリケーションは、本の貸し出し、ユーザーアカウント、在庫を管理します。このコンテキストにおけるデータアクセスオブジェクト(DAO)パターンは、本の詳細の取得、ユーザーレコードの更新、在庫の確認などのデータベース操作を、貸し出しやアカウント管理のビジネスロジックから分離するために使用されます。たとえば、`BookDAO`クラスは、ISBNによる本の検索や可用性状況の更新など、本に関するすべてのデータベース操作を担当します。この抽象化により、図書館システムのメインアプリケーションコードはビジネスルールとワークフローに集中でき、`BookDAO`は複雑なSQLクエリとデータ管理を処理します。この分離により、データソースまたはビジネスロジックの変更を独立して管理できるため、システムの保守とテストが容易になります。
簡単に言うと
DAOは、基本的な永続化メカニズムの上に提供するインターフェースです。
Wikipediaによると
コンピューターソフトウェアにおいて、データアクセスオブジェクト(DAO)は、あるタイプのデータベースまたはその他の永続化メカニズムへの抽象的なインターフェースを提供するパターンです。
JavaにおけるDAOパターンのプログラミング例
データベースに永続化する必要がある顧客のセットがあります。さらに、CRUD(作成/読み取り/更新/削除)操作の全体セットが必要なので、顧客を簡単に操作できます。
顧客の例を通して説明します。これは基本的な`Customer`エンティティです。
@Setter
@Getter
@ToString
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@AllArgsConstructor
public class Customer {
@EqualsAndHashCode.Include
private int id;
private String firstName;
private String lastName;
}
以下は、`CustomerDao`インターフェースとその2つの異なる実装です。`InMemoryCustomerDao`はメモリ内に顧客の単純なマップを保持し、`DBCustomerDao`は実際のRDBMS実装です。
public interface CustomerDao {
Stream<Customer> getAll() throws Exception;
Optional<Customer> getById(int id) throws Exception;
boolean add(Customer customer) throws Exception;
boolean update(Customer customer) throws Exception;
boolean delete(Customer customer) throws Exception;
}
public class InMemoryCustomerDao implements CustomerDao {
private final Map<Integer, Customer> idToCustomer = new HashMap<>();
// implement the interface using the map
}
@Slf4j
@RequiredArgsConstructor
public class DbCustomerDao implements CustomerDao {
private final DataSource dataSource;
// implement the interface using the data source
}
最後に、DAOを使用して顧客を管理する方法を示します。
@Slf4j
public class App {
private static final String DB_URL = "jdbc:h2:mem:dao;DB_CLOSE_DELAY=-1";
private static final String ALL_CUSTOMERS = "customerDao.getAllCustomers(): ";
public static void main(final String[] args) throws Exception {
final var inMemoryDao = new InMemoryCustomerDao();
performOperationsUsing(inMemoryDao);
final var dataSource = createDataSource();
createSchema(dataSource);
final var dbDao = new DbCustomerDao(dataSource);
performOperationsUsing(dbDao);
deleteSchema(dataSource);
}
private static void deleteSchema(DataSource dataSource) throws SQLException {
try (var connection = dataSource.getConnection();
var statement = connection.createStatement()) {
statement.execute(CustomerSchemaSql.DELETE_SCHEMA_SQL);
}
}
private static void createSchema(DataSource dataSource) throws SQLException {
try (var connection = dataSource.getConnection();
var statement = connection.createStatement()) {
statement.execute(CustomerSchemaSql.CREATE_SCHEMA_SQL);
}
}
private static DataSource createDataSource() {
var dataSource = new JdbcDataSource();
dataSource.setURL(DB_URL);
return dataSource;
}
private static void performOperationsUsing(final CustomerDao customerDao) throws Exception {
addCustomers(customerDao);
LOGGER.info(ALL_CUSTOMERS);
try (var customerStream = customerDao.getAll()) {
customerStream.forEach(customer -> LOGGER.info(customer.toString()));
}
LOGGER.info("customerDao.getCustomerById(2): " + customerDao.getById(2));
final var customer = new Customer(4, "Dan", "Danson");
customerDao.add(customer);
LOGGER.info(ALL_CUSTOMERS + customerDao.getAll());
customer.setFirstName("Daniel");
customer.setLastName("Danielson");
customerDao.update(customer);
LOGGER.info(ALL_CUSTOMERS);
try (var customerStream = customerDao.getAll()) {
customerStream.forEach(cust -> LOGGER.info(cust.toString()));
}
customerDao.delete(customer);
LOGGER.info(ALL_CUSTOMERS + customerDao.getAll());
}
private static void addCustomers(CustomerDao customerDao) throws Exception {
for (var customer : generateSampleCustomers()) {
customerDao.add(customer);
}
}
public static List<Customer> generateSampleCustomers() {
final var customer1 = new Customer(1, "Adam", "Adamson");
final var customer2 = new Customer(2, "Bob", "Bobson");
final var customer3 = new Customer(3, "Carl", "Carlson");
return List.of(customer1, customer2, customer3);
}
}
プログラム出力
10:02:09.788 [main] INFO com.iluwatar.dao.App -- customerDao.getAllCustomers():
10:02:09.793 [main] INFO com.iluwatar.dao.App -- Customer(id=1, firstName=Adam, lastName=Adamson)
10:02:09.793 [main] INFO com.iluwatar.dao.App -- Customer(id=2, firstName=Bob, lastName=Bobson)
10:02:09.793 [main] INFO com.iluwatar.dao.App -- Customer(id=3, firstName=Carl, lastName=Carlson)
10:02:09.794 [main] INFO com.iluwatar.dao.App -- customerDao.getCustomerById(2): Optional[Customer(id=2, firstName=Bob, lastName=Bobson)]
10:02:09.794 [main] INFO com.iluwatar.dao.App -- customerDao.getAllCustomers(): java.util.stream.ReferencePipeline$Head@4c3e4790
10:02:09.794 [main] INFO com.iluwatar.dao.App -- customerDao.getAllCustomers():
10:02:09.795 [main] INFO com.iluwatar.dao.App -- Customer(id=1, firstName=Adam, lastName=Adamson)
10:02:09.795 [main] INFO com.iluwatar.dao.App -- Customer(id=2, firstName=Bob, lastName=Bobson)
10:02:09.795 [main] INFO com.iluwatar.dao.App -- Customer(id=3, firstName=Carl, lastName=Carlson)
10:02:09.795 [main] INFO com.iluwatar.dao.App -- Customer(id=4, firstName=Daniel, lastName=Danielson)
10:02:09.795 [main] INFO com.iluwatar.dao.App -- customerDao.getAllCustomers(): java.util.stream.ReferencePipeline$Head@5679c6c6
10:02:09.894 [main] INFO com.iluwatar.dao.App -- customerDao.getAllCustomers():
10:02:09.895 [main] INFO com.iluwatar.dao.App -- Customer(id=1, firstName=Adam, lastName=Adamson)
10:02:09.895 [main] INFO com.iluwatar.dao.App -- Customer(id=2, firstName=Bob, lastName=Bobson)
10:02:09.895 [main] INFO com.iluwatar.dao.App -- Customer(id=3, firstName=Carl, lastName=Carlson)
10:02:09.895 [main] INFO com.iluwatar.dao.App -- customerDao.getCustomerById(2): Optional[Customer(id=2, firstName=Bob, lastName=Bobson)]
10:02:09.896 [main] INFO com.iluwatar.dao.App -- customerDao.getAllCustomers(): java.util.stream.ReferencePipeline$Head@23282c25
10:02:09.897 [main] INFO com.iluwatar.dao.App -- customerDao.getAllCustomers():
10:02:09.897 [main] INFO com.iluwatar.dao.App -- Customer(id=1, firstName=Adam, lastName=Adamson)
10:02:09.897 [main] INFO com.iluwatar.dao.App -- Customer(id=2, firstName=Bob, lastName=Bobson)
10:02:09.898 [main] INFO com.iluwatar.dao.App -- Customer(id=3, firstName=Carl, lastName=Carlson)
10:02:09.898 [main] INFO com.iluwatar.dao.App -- Customer(id=4, firstName=Daniel, lastName=Danielson)
10:02:09.898 [main] INFO com.iluwatar.dao.App -- customerDao.getAllCustomers(): java.util.stream.ReferencePipeline$Head@f2f2cc1
現実世界の例を用いたデータアクセスオブジェクトパターンの詳細な説明

Javaでデータアクセスオブジェクトパターンを使用する場面
以下の状況でデータアクセスオブジェクトを使用します
- データソースへのすべてのアクセスを抽象化してカプセル化する必要がある場合。
- アプリケーションが、大幅なコード変更なしに複数の種類のデータベースまたはストレージメカニズムをサポートする必要がある場合。
- データベースアクセスをクリーンでシンプルに保ち、ビジネスロジックから分離したい場合。
データアクセスオブジェクトパターンJavaチュートリアル
JavaにおけるDAOパターンの現実世界のアプリケーション
- データベースとのインタラクションを必要とするエンタープライズアプリケーション。
- 複数のストレージタイプ(リレーショナルデータベース、XMLファイル、フラットファイルなど)へのデータアクセスを適応させる必要があるアプリケーション。
- 一般的なデータアクセス機能を提供するフレームワーク。
データアクセスオブジェクトパターンのメリットとデメリット
メリット
- デカップリング:データアクセスロジックとビジネスロジックを分離し、モジュール性と明確性を高めます。
- 再利用性:DAOは、アプリケーションの異なる部分、または異なるプロジェクトで再利用できます。
- テスト容易性:ビジネスロジックをデータアクセスロジックから分離してテストできるため、テストが容易になります。
- 柔軟性:アプリケーションコードへの影響を最小限に抑えながら、基盤となるストレージメカニズムを簡単に切り替えることができます。
デメリット
- レイヤの複雑さ:アプリケーションにレイヤを追加するため、複雑さと開発時間が増加する可能性があります。
- オーバーヘッド:単純なアプリケーションでは、DAOパターンによって必要以上のオーバーヘッドが発生する可能性があります。
- 学習曲線:特に複雑なプロジェクトでは、開発者がパターンを効果的に理解して実装するのに時間がかかる可能性があります。
関連するJavaデザインパターン
- 抽象ファクトリ:特に複数のデータベースまたはストレージメカニズムをサポートする場合、DAOの作成を抽象化するのに役立ちます。
- ファクトリ:DAOを動的にインスタンス化するために使用でき、実装の選択に柔軟性を与えます。
- サービスレイヤ:アプリケーションの境界とその利用可能な操作セットを定義するために、DAOパターンと組み合わせて使用されることがよくあります。
- ストラテジー:コンテキストに応じて、実行時にデータアクセス戦略を変更するために使用される場合があります。