ThreadLocalは接続をカプセル化し、同じスレッドでリソースを共有する

5615 ワード

問題の背景:
        JDBCを使用して開発する場合は、毎回の添削・改ざんごとにデータベースに接続してこそ、データ・アイテムに対応した操作が可能になります.私たちの業務が複雑な場合、1つの方法の中で何度も削除変更を実行する可能性があります.そうすると、この方法の実行過程で、データベースと何度も接続する必要があります.このようなシーンでは、この方法を同時に実行する過程で、データベースとの接続が混乱しないことをどのように保証するか、これらの操作の原子性を保証するか、特に重要に見えます.どうやってこの問題を解決しますか?(データベース接続はステータスのある変数です)
        問題の分析:
考えられるシナリオは2つあります.1つは、この複数回にわたって削除変更を実行する方法で、データベース接続オブジェクトConnectionを格納するためにローカル変数を宣言することです.このように、削除変更方法を呼び出すときに、Connectionオブジェクトをパラメータとして転送することで、これらのサブ操作が同じ接続を使用することを保証します.これにより、すべての操作の連続性と原子性が保証され、データベース接続オブジェクトが混乱して使用されることはありません.
      2つ目は、実際には、ユーザーのリクエストごとに、プログラムに対応するサーブレットが呼び出されます.サーブレットは単一インスタンスのマルチスレッドです.つまり、リクエストプログラムごとにスレッドがユーザーサービスとして起動されます.このスレッドでは、データベースと複数回接続する必要がある複雑な方法を含む多くの方法が呼び出されます.これにより,このスレッドで使用するデータベース接続オブジェクトConnectionが同じであることを保証すれば,これらの動作の連続性と原子性を保証できると考えられる.
        第1のスキームと比較すると、第2のスキームは明らかにより良い.第1のスキームは、削除変更方法を記述する際に、Connectionのパラメータを定義する必要があるため、第2のスキームは使用せずに、直接Connectionをカプセル化すればよい.
/**
 *   ThreadLocal  Connection
 * @author ljw
 *
 */
public class ConnectionManager {
 
	//          ,    connection
	private static ThreadLocal connectionHolder = new ThreadLocal();
	
	/**
	 *   Connection
	 * @return
	 */
	public static Connection getConnection() {
		Connection conn = connectionHolder.get();
		//               Connection
		if (conn == null) {
			try {
				JdbcConfig jdbcConfig = XmlConfigReader.getInstance().getJdbcConfig();
				Class.forName(jdbcConfig.getDriverName());
				conn = DriverManager.getConnection(jdbcConfig.getUrl(), jdbcConfig.getUserName(), jdbcConfig.getPassword());
				// Connection       ThreadLocal 
				connectionHolder.set(conn);
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
				throw new ApplicationException("    ,        ");
			} catch (SQLException e) {
				e.printStackTrace();
				throw new ApplicationException("    ,        ");
			}
		}
		return conn;
	}
	/**
	 *     ,          
	 */
	public static void closeConnection() {
		Connection conn = connectionHolder.get();
		if (conn != null) {
			try {
				conn.close();
				// ThreadLocal   Connection
				connectionHolder.remove();
			} catch (SQLException e) {
				e.printStackTrace();
			}	
		}
	}
	
	public static void close(Connection conn) {
		if (conn != null) {
			try {
				conn.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}
	
	public static void close(Statement pstmt) {
		if (pstmt != null) {
			try {
				pstmt.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}
	
	public static void close(ResultSet rs ) {
		if (rs != null) {
			try {
				rs.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}
	
	public static void beginTransaction(Connection conn) {
		try {
			if (conn != null) {
				if (conn.getAutoCommit()) {
					conn.setAutoCommit(false); //    
				}
			}
		}catch(SQLException e) {}
	}
	
	public static void commitTransaction(Connection conn) {
		try {
			if (conn != null) {
				if (!conn.getAutoCommit()) {
					conn.commit();
				}
			}
		}catch(SQLException e) {}
	}
	
	public static void rollbackTransaction(Connection conn) {
		try {
			if (conn != null) {
				if (!conn.getAutoCommit()) {
					conn.rollback();
				}
			}
		}catch(SQLException e) {}
	}
 
}

次のコードでは、ThreadLocalが同じスレッドでConnectionリソースを共有できる方法を示します.
package com.bjpowernode.drp.flowcard.manager.impl;
 
import java.sql.Connection;
import java.util.Date;
import com.bjpowernode.drp.flowcard.dao.FlowCardDao;
import com.bjpowernode.drp.flowcard.domain.FlowCard;
import com.bjpowernode.drp.flowcard.manager.FlowCardManager;
import com.bjpowernode.drp.util.ApplicationException;
import com.bjpowernode.drp.util.BeanFactory;
import com.bjpowernode.drp.util.ConnectionManager;
import com.bjpowernode.drp.util.DaoException;
import com.bjpowernode.drp.util.PageModel;
 
public class FlowCardManagerImpl implements FlowCardManager {
 
	
	private FlowCardDao flowCardDao;
	//    
	public FlowCardManagerImpl(){
		this.flowCardDao = (FlowCardDao) BeanFactory.getInstance().getDaoObject(FlowCardDao.class);
	}
	
	@Override
	public void addFlowCard(FlowCard flowCard) throws ApplicationException {
		
		Connection conn = null;
		try{
			// ThreadLocal        Connection
			conn = ConnectionManager.getConnection();
			//    
			ConnectionManager.beginTransaction(conn);
			//       
			String flowCardVouNo = flowCardDao.generateVouNo();
			//        
			flowCardDao.addFlowCardMaster(flowCardVouNo, flowCard);
			//         
			flowCardDao.addFlowCardDetail(flowCardVouNo, flowCard.getFlowCardDetailList());
			//    
			ConnectionManager.commitTransaction(conn);		
		}catch(DaoException e){
			//    
			ConnectionManager.rollbackTransaction(conn);
			throw new ApplicationException("       !");
		}finally{
			//  Connection  ThreadLocal     
			ConnectionManager.closeConnection();
		}
	
	}
}

解析:1、このクラスはスレッドのローカル変数を提供し、変数の初期化コピーとは独立している.       ローカル変数については、メンバー変数やグローバル変数ではない理由がよく理解されていない可能性があります.この場合、変数の役割ドメインの問題に関連します.ThreadLocalは、ローカル変数よりも大きな役割ドメインを有し、この役割ドメイン内でリソースを共有することができ、スレッドは安全である.       ThreadLocalはローカルスレッドではなく、ローカル変数を維持するために使用されるスレッド変数であることも理解しています.各スレッドに対して独自の変数バージョンを提供し、マルチスレッドの衝突問題を回避し、各スレッドは自分のバージョンを維持するだけでよく、互いに独立し、相手に影響を与えない.2、各スレッドには独自のThreadLocalがあり、それを修正しても他のスレッドに影響しない
3.スレッドが消えた後、そのスレッドのローカルインスタンスのすべてのコピーはゴミ回収されます(これらのコピーに対する他の参照がない限り). 
      変数のコピーがmapに格納されていることがわかりました.setを呼び出していない場合、参照をこの「map」に指さすのではなく、このスレッドが終了するとリソース回収操作が実行され、申請したリソースを回収します.つまり、参照をnullに設定します.この場合、mapを指す参照は何もないので、ゴミ回収されます.