Javaトランザクションの完全な解析(4)-成功したケース

7011 ワード

このシリーズの前の記事では、同じトランザクションで同じConnectionオブジェクトを使用することを実現するには、Connectionオブジェクトを渡すことで共有の目的を達成することができますが、この方法は醜いです.この記事では、トランザクション管理を完了するための別のメカニズム(ConnectionHolder)を導入します.
Javaトランザクションに関する一連の記事です.githubソースコードをダウンロードしてください.
git clone https://github.com/davenkin/java_transaction_workshop.git

ConnectionHolderの動作メカニズムは、Connectionオブジェクトをグローバル共通の場所に配置し、異なる操作でこの場所からConnectionを取得し、Connection共有の目的を達成することです.これもServiceLocatorモードで、JNDIに似ています.ConnectionHolderクラスを次のように定義します.
public class ConnectionHolder
{
    private Map connectionMap = new HashMap();

    public Connection getConnection(DataSource dataSource) throws SQLException
    {
        Connection connection = connectionMap.get(dataSource);
        if (connection == null || connection.isClosed())
        {
            connection = dataSource.getConnection();
            connectionMap.put(dataSource, connection);
        }

        return connection;
    }

    public void removeConnection(DataSource dataSource)
    {
        connectionMap.remove(dataSource);
    }
}

ConnectionHolderクラスでは、主にConnectionHolderが複数のDataSourceにサービスできるようにするためのキーがDataSource、値がConnectionのMapを維持しています.getConnectionメソッドを呼び出すとDataSourceオブジェクトが渡されます.MapにすでにそのDataSourceに対応するConnectionが存在する場合は、直接そのConnectionに戻ります.そうでない場合は、DataSourceを呼び出すgetConnectionメソッドで新しいConnectionが得られ、Mapに追加され、最後にそのConnectionに戻ります.これにより、ConnectionHolderから取得したConnectionは、途中でConnectionHolderを呼び出すremoveConnectionメソッドが現在のConnectionを削除したり、Connectionを呼び出す場合を除き、同じトランザクションの過程で同じです.close()はConnectionを閉じ、その後の操作でConnectionHolderのgetConnectionメソッドを再度呼び出します.この場合、新しいConnectionオブジェクトが返され、トランザクションが失敗し、途中でConnectionを削除したり閉じたりすることはありません.
しかし、コンディションオブジェクトを手動で途中で削除したり閉じたりすることはありませんが(もちろん、トランザクションの最後にConnctionをオフにする必要があります)他のスレッドをブロックすることはできません.たとえば、ConnectionHolderクラスは複数のスレッドで同時に使用でき、これらのスレッドは同じDataSourceを使用しており、そのうちの1つのスレッドがConnectionを使い切った後にオフにしていますが、別のスレッドがこのConnectioを使用しようとしています.n,問題が出てきた.したがって、上のConnectionHolderはスレッドが安全ではありません.
スレッドの安全なConnectionHolderクラスを得るために、Javaが提供するThreadLocalクラスを導入することができます.このクラスは、クラスのインスタンス変数が各スレッドに個別にコピーされ、他のスレッドのインスタンス変数に影響を与えないことを保証します.SingleThreadConnectionHolderクラスを次のように定義します.
public class SingleThreadConnectionHolder
{
    private static ThreadLocal localConnectionHolder = new ThreadLocal();

    public static Connection getConnection(DataSource dataSource) throws SQLException
    {
        return getConnectionHolder().getConnection(dataSource);
    }

    public static void removeConnection(DataSource dataSource)
    {
        getConnectionHolder().removeConnection(dataSource);
    }

    private static ConnectionHolder getConnectionHolder()
    {
        ConnectionHolder connectionHolder = localConnectionHolder.get();
        if (connectionHolder == null)
        {
            connectionHolder = new ConnectionHolder();
            localConnectionHolder.set(connectionHolder);
        }
        return connectionHolder;
    }

}

スレッドが安全なSingleThreadConnectionHolderクラスがあり、サービス層と各DAOでこのクラスを使用してConnectionオブジェクトを取得できます.
 Connection connection = SingleThreadConnectionHolder.getConnection(dataSource);

もちろん、この場合、DAOクラスのインスタンス変数として存在するデータソースを入力する必要があります.したがって、前の記事のようにConnectionオブジェクトを直接DAOに渡す方法はありません.ここでは、DataSourceをインスタンス変数として使用できる以上、前の記事でConnectionもインスタンス変数として使用できないのはなぜでしょうか.これで醜いAPIにならないのではないでしょうか.なぜなら、Connectionオブジェクトをインスタンス変数として使用すると、同じDAOクラスが複数のスレッドで同時に使用されている場合、1つのスレッドがConnectionを閉じ、もう1つが使用されているという問題は、前述したConnectionHolderのスレッドセキュリティの問題と同じです.
Bank DAOとInsurance DAOクラスのソースコードについてはここではリストされませんが、前述の記事とはConnectionオブジェクトを取得する方法が異なるだけなのでgithubソースコードを参照してください.
次に、TransactionManagerクラスを見てみましょう.前の記事では、サービスクラスにトランザクションに関連するコードを直接書きますが、TransactionMangerクラスがトランザクションに関連する作業を集中的に管理することを宣言するのがより良い方法です.
public class TransactionManager
{
    private DataSource dataSource;

    public TransactionManager(DataSource dataSource)
    {
        this.dataSource = dataSource;
    }

    public final void start() throws SQLException
    {
        Connection connection = getConnection();
        connection.setAutoCommit(false);
    }

    public final void commit() throws SQLException
    {
        Connection connection = getConnection();
        connection.commit();
    }

    public final void rollback()
    {
        Connection connection = null;
        try
        {
            connection = getConnection();
            connection.rollback();

        } catch (SQLException e)
        {
            throw new RuntimeException("Couldn't rollback on connection[" + connection + "].", e);
        }
    }

    public final void close()
    {
        Connection connection = null;
        try
        {
            connection = getConnection();
            connection.setAutoCommit(true);
            connection.setReadOnly(false);
            connection.close();
            SingleThreadConnectionHolder.removeConnection(dataSource);
        } catch (SQLException e)
        {
            throw new RuntimeException("Couldn't close connection[" + connection + "].", e);
        }
    }

    private Connection getConnection() throws SQLException
    {
        return SingleThreadConnectionHolder.getConnection(dataSource);
    }
}

TransactionManagerオブジェクトは、DataSourceインスタンス変数を保持し、SingleThreadConnectionHolderによってConnectionオブジェクトを取得していることがわかります.次にサービスクラスでTransactionManagerを使用します.
public class ConnectionHolderBankService implements BankService
{
    private TransactionManager transactionManager;
    private ConnectionHolderBankDao connectionHolderBankDao;
    private ConnectionHolderInsuranceDao connectionHolderInsuranceDao;

    public ConnectionHolderBankService(DataSource dataSource)
    {
        transactionManager = new TransactionManager(dataSource);
        connectionHolderBankDao = new ConnectionHolderBankDao(dataSource);
        connectionHolderInsuranceDao = new ConnectionHolderInsuranceDao(dataSource);

    }

    public void transfer(int fromId, int toId, int amount)
    {
        try
        {
            transactionManager.start();
            connectionHolderBankDao.withdraw(fromId, amount);
            connectionHolderInsuranceDao.deposit(toId, amount);
            transactionManager.commit();
        } catch (Exception e)
        {
            transactionManager.rollback();
        } finally
        {
            transactionManager.close();
        }
    }
}

ConnectionHolderBankServiceでは、TransactionManagerを使用してトランザクションを管理しています.TransactionManagerと2つのDAOクラスはSingleThreadConnectionHolderを使用してConnectionを取得しているため、トランザクション全体で同じConnectionイメージを使用してトランザクションが成功しました.また,2つのDAOのwithdrawとdeposit法が業務に関係のないオブジェクトを受け入れず,API汚染を除去していることも見られる.また、TransactionManagerを使用してトランザクションを管理することで、サービス層コードも簡潔になります.
次の記事では、Templateモードを使用してトランザクションを完了することについて説明します.