ThreadLocal封装Connection,实现同一线程共享资源 小鱼儿 2021-06-24 15:59 285阅读 0赞 问题背景: 使用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,故而会被垃圾回收。
还没有评论,来说两句吧...