web-dev-qa-db-ja.com

フレームワークなしの純粋なJDBCのDAOまたはサービスレイヤーでのトランザクション処理

純粋なJDBCで動作するアプリケーションがあります。私はジレンマがあります トランザクション処理 行くべきServiceまたは[〜# 〜] dao [〜#〜]レイヤー。 DAOはできるだけシンプルである必要があり、データベースへの接続を提供するためだけに存在するため、ほとんどの場合、それは サービスレイヤーで実装する必要があります であることがわかりました。私はこのアプローチが好きですが、私が見つけたすべてのソリューションと例はSpringまたは他のフレームワークで機能し、注釈を使用して@Transactionalとしてマークします。

私のDAOレイヤーの純粋なJDBCにはDaoFactoryがあり、各DAOクラス(UserDaoCarDao)。接続プーリングを実装し、このオブジェクトを使用してデータベースに接続し、CRUD操作を実行します。 Serviceレイヤーで、必要な特定のDAOのインスタンスを作成し、その上でアクション/計算を実行します。

ここでトランザクション処理をどこに実装しますか?

かなり長い回答で申し訳ありません。

現代のアプリケーションアプローチでは、接続を工場から取得するのではなく、DAOに注入することを検討する必要があると思います。

あなたの問題は、私が理解しているように、サービスレイヤーの観点から、サービスレイヤーでのより複雑なデータベース操作に組み合わせる必要がある小さな粒度のDAO機能(CRUD)が必要であることです。 dbトランザクションを使用して複雑な操作の整合性を確保したい。発生する問題は、DAOクラスの外部でのトランザクション処理とデータベースアクセスの可視性です。ビジネスコードがトランザクションコードと接続処理コードで汚れてしまい、BLの邪魔になってしまいます。

これはORMベースのアプローチでも共通の問題です。たとえば、単純なHibernateアプリケーションでは、すでに実行中のセッションにいるか、新しいセッションを開始する必要があるかを決定する必要があります。休止状態のコードを別のクラスにカプセル化する場合、コードの重複を避けるために、セッションの開始と終了をスマートな方法で処理する必要があります。

もう1つの問題は、メソッドのスタッキングです。各メソッドが接続/トランザクションを処理する場合、あるメソッドから別のメソッドを簡単に呼び出すことはできません。

私はあなたのために考えたいかもしれないいくつかのアイデアを持っています:

スタッキングの問題の最初:

  • 参照カウントを使用した接続/トランザクションメソッドのラップ
    • 接続は、接続/トランザクションプロバイダーを使用して割り当てられます。 commitとrollbackはプロバイダーのメソッドです。トランザクションを2回開いた場合、実際にコミットするには、トランザクションを2回コミットする必要があります。ロールバックでは、戦略を再検討し、参照を無視してすぐにロールバックすることができます。

これにより、コードの任意のレベルでトランザクションと接続を開始できます。データベースに関してbeginTransactionを呼び出すことはべき等ですが、カウンターが発生します。コードのコミットをスキップしないように、十分に注意する必要があります。そうしないと、トランザクション境界が実行されません。

このメソッドを使用すると、obtainConnection/startTransaction/commit/rollback/returnConnection呼び出しにより、すべてのレベルでコードが乱雑になります。ここには定型文がたくさんあります。

しかし、この方法を念頭に置いて、私はこれで私のフレームワークのスタッキング問題を解決しました。ここで、この接続の混乱をコードから削除する方法を見つける必要があります。 3番目のアプローチに重点を置いて、以下のアプローチが有用であることを確認してください。

  1. 接続の依存性注入: DAOはシングルトンではなく、使い捨てオブジェクトであり、作成時に接続を受け取ります。呼び出しコードが接続の作成を制御します。

    • PRO:実装が簡単
    • CON:ビジネス層でのDB接続の準備/エラー処理
    • CON:DAOはシングルトンではなく、ヒープに大量のゴミを生成します(実装言語はここで異なる場合があります)
    • CON:サービスメソッドのスタックを許可しない
  2. 接続/トランザクションを実行中のスレッドにバインドします: DAOはシングルトンにすることができます。ランタイム情報は、スレッドローカルコンテキストオブジェクトにバインドします。ビジネス層は、DAOのコンテキストを初期化します。 Webアプリケーションの場合、これはリクエストの開始時に発生する可能性があります。

    • PRO:DAOはシングルトンにすることができます
    • PRO:実装が簡単
    • PRO:一致するコールライフサイクルがある場合、データベースセットアップはサービスレイヤーに表示されません
    • CON:コールライフサイクルに適切な初期化ポイントがない場合、これはより複雑になります。
    • CON:各呼び出しライフサイクルには1つのトランザクションがあり、トランザクションの停止と再開は混乱を招く可能性があります。
    • CON:DAOレイヤーの外でも接続処理が表示される
    • CON:同じスレッド内で呼び出しライフサイクルをネストすると、再びスタックの問題が発生します。
  3. プロキシクラスを使用して、サービスメソッドの呼び出し中にスレッド内のコンテキストオブジェクトに透過的に接続処理を実装します。

    • DAOおよびServiceオブジェクトはシングルトンであり、サービスはプロキシーでラップされています。プロキシの呼び出しハンドラは、サービスクラスの外部で接続オブジェクトを準備します。
    • サービスクラスは、1つのトランザクションまたは即時コミットが必要かどうかをメソッドのアノテーションで決定します。
    • サービスは、ラムダ関数を使用してサービスクラス内にトランザクション境界を定義できます。
    • PRO/CON
    • PRO:DAOまたはServiceクラス内での接続/トランザクション処理は不要
    • PRO:サービスは、メソッド呼び出しの注釈によってトランザクション境界を完全に制御します。
    • PRO:DAOはコンテキスト内で接続を検出し、トランザクション境界を気にしません。
    • PRO:DAO関数は細かく設定でき、追加のコーディングなしで複雑な操作に組み合わせることができます。
    • PRO:サービスとDAOはシングルトンです。
    • PRO:スタッキングは、参照カウントを行う上記のアプローチによって解決されます。呼び出しハンドラは接続を取得する場所であるため、プロキシされたオブジェクトから戻るときに呼び出しハンドラが処理するので、コミット/クローズが失われることはありません。
    • PRO:Webアプリケーションのようなコールライフサイクルで動作します
    • PRO:これはフレームワークがそれを行う方法です:-)
    • CON:複雑でありながら簡単な実装(注釈、プロキシ、インターフェース、コンテキスト、プロキシサービスの実装のためのファクトリ、場合によってはコードの主要なリファクタリングが必要)

私は3つすべてのアプローチを実装しました(Spring etalなし)。最後の1つは特に非常に強力であり、サービスインフラストラクチャを大幅にクリーンアップします。あなたはフレームワークを分かりやすく説明します-するのは楽しいです:-)

しかし、必要に応じて、いくつかのプロジェクトによって提供される軽量フレームワークを使用したほうがよい場合があります。

トランザクションのないサービス関数内にトランザクション境界を実装する必要がある場合は、次のことができます。

  • 適切なトランザクションアノテーションを使用して、関数を独自のサービスメソッドにリファクタリングする
  • トランザクションコンテキストでラムダ関数を実行し、結果を返すクラスを記述します
7
thst

通常、サービス境界にトランザクション境界を設定することをお勧めします。ビジネスルールを実装するために、複数のDAO(または他の中間層)を必要とするサービスを簡単に作成できます。

注意として、これをプログラムで自分で行うべきではありません。 SpringとJEEは、これをそのまま使用できます。

2
marciopd

私はプロジェクトに参加しており、人々は春のクールエイドを飲みません...私は以下のパターンが私にうまくいくことを発見しました...

Service(A singleton)-->*Manager( singletons )-->DAOs

単一のトランザクション内で更新する必要がある3つの異なるテーブルがあるとします(すべてまたは何もない)。テーブルの1つは「親」テーブルで、親の保存中に更新する必要がある子テーブルがあります。

public class ParentServive
    private ParentManager parentManager; 

    public void save(Parent parent){
         parentManager.save(parent)
    }
}

すべての* Managerクラスが拡張するDAOManagerを作成する

public class DAOManager{
...
..
  public Connection getConnection()throws DAOException{
  try{
    dataSource.getConnection();
    }catch(SQLException e){
    throw new DAOExceptoin(e);
    }
  }

    public Connection getTXConnection()throws DAOException{
     Connection connection  = dataSource.connection;
     connection.setAutoCommit(false);
     return connection;
  }

  public void close(Connection connectoin) throws DAOException{

  try{
      if ( !connection.getAutoCommit() ){
         connection.setAutoCommit(true);
     }
     connection.close();
     }catch(SQLException e){
      throw new DAOExceptoin(e);
    }
  }
}

これで、保存を有効にするために親テーブルを更新する必要がある2つのDAOが最初に作成され、抽象DAOManagerが作成されます。1つの接続のみを開き、すべてのテーブルでロールバックまたはコミットできます...

 public class ParentManager extends DAOManager{

    public static getInstance(){
         ..
         ... 
     }
   public save(Parent parent) throw DAOException{
      Connection connection = null;

      try{
      connection = getTXConnection(); 
      ParentDAO parentDAO   = new ParentDAO(connection);
      ChildOneDAO childOneDAO = new ChildOneDAO(connection);
      ChildTwoDAO childTwoDAO = new ChildTwoDAO(connection);

      parentDAO.save(...)

      childOneDAO.save(...)

      childTwoDAO.save(..)    


      connection.commit();

      }catch(Exception e){    
        connection.rollback();
      }finally{
      close(connection);
      }
   }
}

次に、サービスクラスはManagerクラスのみを使用する必要があります。接続管理について心配する必要はありません... Managerは接続を管理し、使用するDAOをその場で作成します。

1
Jeryl Cook