0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

📘 Vol.10.2:DAOのインタフェース設計と実装パターン (※DTO設計の次に押さえるべきDAO層の設計と実装パターンを完全解説)

Last updated at Posted at 2025-05-25

🧭 目次

  • 1.DAO層とは?アーキテクチャ上の役割

  • 2.DAOインタフェースと実装の分離(+マッピング処理の設計)

  • 3.DAOの責務と粒度の考え方

  • 4.DAO層でよくあるアンチパターンとその改善策

  • 5.まとめ・次回予告


🧩 1. DAO層とは?アーキテクチャ上の役割

  • DTOが「データ構造」なら、DAOは「永続化ロジック」の責任を持つ

  • DB操作のロジックをActionやService層に持たせないことで、責務分離を徹底

  • 主な役割:

    • SQLの発行と実行

    • DTOとのデータマッピング

    • トランザクション制御(複数DAOが絡む場合はService層へ)


🧩 2. DAOインタフェースと実装の分離

✅ 実装の基本構造

// UserDao.java(インタフェース)
public interface UserDao {
    User findById(int userId);
    List<User> findAll();
    void insert(User user);
    void update(User user);
    void delete(int userId);
}

// UserDaoImpl.java(実装)
public class UserDaoImpl implements UserDao {
    public User findById(int userId) {
        // DB接続、SQL実行、マッピング処理
    }
    // その他実装...
}

✅ なぜインタフェースと分離するのか?

  • 実装の切り替えを柔軟に(MockDao、JdbcDao、JpaDaoなど)

  • テストやDI(依存性注入)との親和性向上

✅ ResultSet → DTO のマッピング処理

public User mapToUser(ResultSet rs) throws SQLException {
    User user = new User();
    user.setUserId(rs.getInt("user_id"));
    user.setUsername(rs.getString("username"));
    user.setEmail(rs.getString("email"));
    return user;
}

  • mapToUser() のようなマッピング処理は 専用メソッドに切り出す

  • 複数DAOで共通化できる処理は、BaseDao のような親クラスにまとめるのもアリ


🧩 補足:ResultSet → DTO マッピング処理は分離するのがベストプラクティス

DAO 実装クラスの中で頻出する「ResultSet → DTO への変換」処理は、専用のマッピングクラスに分離するのがベストプラクティスです。以下のように切り出すことで、責務が明確になり、再利用性も高まります。

✅ 実装例:UserMapper クラスに分離

// UserMapper.java
public class UserMapper {
    public static User map(ResultSet rs) throws SQLException {
        User user = new User();
        user.setUserId(rs.getInt("user_id"));
        user.setUsername(rs.getString("username"));
        user.setEmail(rs.getString("email"));
        return user;
    }
}

DAO の中ではこのように呼び出します:

// UserDaoImpl.java
public class UserDaoImpl implements UserDao {
    public User findById(int id) {
        try (Connection conn = ..., PreparedStatement stmt = ...) {
            ResultSet rs = stmt.executeQuery();
            if (rs.next()) {
                return UserMapper.map(rs); // ✅ マッパークラスで変換
            }
        } catch (SQLException e) {
            ...
        }
        return null;
    }
}

✅ なぜ分離するのか?

  • DAO の役割は「SQLを実行してDTOに渡す」までに限定したほうがよい

  • ResultSet → DTO の変換処理は、マッピングロジックとして切り離すと可読性・保守性が向上する

  • 複数DAOで同じDTOを扱う場合も、共通のMapperを使い回せる

✅ より高度な設計に発展可能

  • 全DAOで使える汎用的な BaseMapper インタフェースの定義

  • Java 8 以降であれば、ラムダ式 + 関数型インタフェース化も可能(例:Function)

設計の柔軟性を高めるためにも、こうしたマッピングの責務分離はぜひ検討したいポイントです。


🧩 3. DAOの責務と粒度の考え方

✅ DAOの責務

  • 1DAO = 1テーブル操作が基本

  • 複雑なJOINや業務ロジックはDAOではなくService層へ分離

✅ 粒度のバランス感覚

よくある誤り 改善指針
UserDaoに全てのユーザー系機能を書く UserDao, UserRoleDao, UserSettingDao などに分割
SQLが長大で可読性が悪い ViewやStored Procedure、Repositoryパターンの活用を検討

🧩 4. DAO層でよくあるアンチパターンとその改善策

アンチパターン 問題点 改善策
SQLをActionクラス内に直接書く 責務の混在、再利用性低下 DAO層に委譲する
ResultSetの直接操作を繰り返す コピペ地獄、バグ温床 マッピング共通化・専用メソッドの活用
例外を握りつぶす 原因特定不能 適切にスロー or ログ記録
1DAOで複数テーブルを操作 単一責務原則に反する DAOを分割し、Service層で統合制御

📌 まとめ

  • DAOは「DB操作に責任を持つ層」であり、DTOと密接に連携

  • 実装クラスは可能な限りマッピング処理を切り出し、再利用性と保守性を高める

  • 粒度設計と責務分離を意識することで、大規模化にも耐えられる設計に


▶️ 次回予告

📘 Vol.10.3:DB接続クラスの共通化とドライバ設計

  • ConnectionManager の共通化

  • トランザクション管理戦略

  • JDBCドライバの初期化と設定管理 などを扱います!


✨ シリーズまとめ(Vol.10.x バリデーション編)


0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?