ThreadLocal封装Connection,实现同一线程共享资源

问题背景:

        使用JDBC进行开发的时候,每一次的增删改查都必须和数据库建立连接,才可以对数据项进行相应的操作。当我们的业务比较复杂的情况下,可能会出现在一个方法中多次的执行增删改查,这样的话,在这个方法的执行过程中,就需要与数据库建立多次的连接,在这种场景中,如何保证在并发执行这个方法的过程中,与数据库的连接不会混乱,保证这些操作的原子性,就显得尤为重要了。如何解决这个问题呢?(数据库连接是一个有状态的变量)

        问题分析:

我能想到的方案有两个,其一:在这个多次执行增删改查的方法内,声明一个局部变量用于存放数据库连接对象Connection,这样在调用增删改查方法的时候,将Connection对象作为参数传进去,这样就保证了这些子操作都使用的是同一个连接,从而保证了所有操作的连续性和原子性,不会出现数据库连接对象混乱使用的情况。

      其二:我们仔细想想,其实用户的每一次请求,都会调用程序相应的Servlet,而Servlet是单实例多线程的,也就是说每一次的请求程序都启动一个线程为用户服务,在这个线程中会调用很多的方法,包括我们上面提到的那个需要和数据库进行多次连接的复杂方法,由此我们可以这样想,只要保证在这个线程中,我们所使用的数据库连接对象Connection都是同一个,一样可以保证这些操作的连续性和原子性。

        相比较第一种方案而言,第二种显然要更好一些,因为第一种需要我们在编写增删改查方法时,定义Connection的参数,第二种则不用,直接将Connection进行一下封装即可。

/**
 * 采用ThreadLocal封装Connection
 * @author ljw
 *
 */
public class ConnectionManager {
 
	//声明一个本地线程变量,用于存放connection
	private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>();
	
	/**
	 * 得到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,故而会被垃圾回收。

猜你喜欢

转载自blog.csdn.net/liuxiao723846/article/details/81234424
今日推荐