zoukankan      html  css  js  c++  java
  • Spring源码解析-JdbcTemplate

    JdbcTemplate类图

    从类继承关系上来看,JdbcTemplate继承了基类JdbcAccessor和接口类JdbcOperation,在基类JdbcAccessor的设计中,对DataSource数据源的管理和配置,在JdbcOperation接口中,定义了通过JDBC操作数据库的基本操作方法,而JdbcTemplate提供了这些接口的实现,例如 execute(), query() , update()等方法。

    回顾JDBC的简单使用

    @Test
    public void testJDBC(){
      String url = "jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf-8";
      String username = "root";
      String password = "123456";
      //1.加载驱动程序
      Class.forName("com.mysql.jdbc.Driver");
      //2.获取连接
      Connection conn = DriverManager.getConnection(url, username, password);
      Statement stmt = conn.createStatement();
      //3.执行SQL,获取结果
      ResultSet  rs = stmt.executeQuery("select * from user");
      while(rs.next()){
        System.out.println("username="+rs.getStriing("username")+" age="+rs.getInt("age"));
      }
      //关闭资源
      rs.close();
      stmt.close();
      conn.close();
    }
    

    JdbcTemplate源码分析

        JdbcTemplate的execute实现

    execute有很多重载方法,看void execute(final String sql)方法

    //执行SQL语句
    @Override
    	public void execute(final String sql) throws DataAccessException {
    		if (logger.isDebugEnabled()) {
    			logger.debug("Executing SQL statement [" + sql + "]");
    		}
            //回调类
    		class ExecuteStatementCallback implements StatementCallback<Object>, SqlProvider {
    			@Override
    			public Object doInStatement(Statement stmt) throws SQLException {
    				stmt.execute(sql);
    				return null;
    			}
    			@Override
    			public String getSql() {
    				return sql;
    			}
    		}
    		execute(new ExecuteStatementCallback());
    	}
    

      

      //静态处理SQL
        @Override
    	public <T> T execute(StatementCallback<T> action) throws DataAccessException {
    		Assert.notNull(action, "Callback object must not be null");
    		//获取数据库连接
    		Connection con = DataSourceUtils.getConnection(getDataSource());
    		Statement stmt = null;
    		try {
    			Connection conToUse = con;
    			if (this.nativeJdbcExtractor != null &&
    					this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativeStatements()) {
    				conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
    			}
                //创建Statement
    			stmt = conToUse.createStatement();
    			applyStatementSettings(stmt);
    			Statement stmtToUse = stmt;
    			if (this.nativeJdbcExtractor != null) {
    				stmtToUse = this.nativeJdbcExtractor.getNativeStatement(stmt);
    			}
                //回调函数
    			T result = action.doInStatement(stmtToUse);
    			handleWarnings(stmt);
    			return result;
    		}
    		catch (SQLException ex) {
                //释放数据库连接,同时抛出一个Spring转换过的Spring数据库异常
    			// Release Connection early, to avoid potential connection pool deadlock
    			// in the case when the exception translator hasn't been initialized yet.
    			JdbcUtils.closeStatement(stmt);
    			stmt = null;
    			DataSourceUtils.releaseConnection(con, getDataSource());
    			con = null;
    			throw getExceptionTranslator().translate("StatementCallback", getSql(action), ex);
    		}
    		finally {
                //释放连接
    			JdbcUtils.closeStatement(stmt);
    			DataSourceUtils.releaseConnection(con, getDataSource());
    		}
    	}
    

    execute的流程图如下:

    JdbcTemplate的query实现

    query也有很多重载方法,void query(String sql, PreparedStatementSetter pss, RowCallbackHandler rch)

    @Override
    	public void query(String sql, PreparedStatementSetter pss, RowCallbackHandler rch) throws DataAccessException {
    		query(sql, pss, new RowCallbackHandlerResultSetExtractor(rch));
    	}
    @Override
    	public <T> T query(String sql, PreparedStatementSetter pss, ResultSetExtractor<T> rse) throws DataAccessException {
          //创建Statement
    		return query(new SimplePreparedStatementCreator(sql), pss, rse);
    	}
    public <T> T query(
    			PreparedStatementCreator psc, final PreparedStatementSetter pss, final ResultSetExtractor<T> rse)
    			throws DataAccessException {
    
    		Assert.notNull(rse, "ResultSetExtractor must not be null");
    		logger.debug("Executing prepared SQL query");
    
    		return execute(psc, new PreparedStatementCallback<T>() {
    			@Override
    			public T doInPreparedStatement(PreparedStatement ps) throws SQLException {
    				ResultSet rs = null;
    				try {
    					if (pss != null) {
    						pss.setValues(ps);
    					}
                        //执行查询SQL
    					rs = ps.executeQuery();
    					ResultSet rsToUse = rs;
    					if (nativeJdbcExtractor != null) {
    						rsToUse = nativeJdbcExtractor.getNativeResultSet(rs);
    					}
                        //抽取结果集数据
    					return rse.extractData(rsToUse);
    				}
    				finally {
                        //关闭结果集
    					JdbcUtils.closeResultSet(rs);
    					if (pss instanceof ParameterDisposer) {
    						((ParameterDisposer) pss).cleanupParameters();
    					}
    				}
    			}
    		});
    	}
    

    主要来看一下RowCallbackHandlerResultSetExtractor类

    private static class RowCallbackHandlerResultSetExtractor implements ResultSetExtractor<Object> {
    
    		private final RowCallbackHandler rch;
    
    		public RowCallbackHandlerResultSetExtractor(RowCallbackHandler rch) {
    			this.rch = rch;
    		}
    
    		@Override
    		public Object extractData(ResultSet rs) throws SQLException {
              //循环获取每一行数据
    			while (rs.next()) {
    				this.rch.processRow(rs);
    			}
    			return null;
    		}
    	}
    

    通过processRow获取每行中的每一列的数据

    @Override
    	public final void processRow(ResultSet rs) throws SQLException {
    		if (this.rowCount == 0) {
    			ResultSetMetaData rsmd = rs.getMetaData();
    			this.columnCount = rsmd.getColumnCount();
    			this.columnTypes = new int[this.columnCount];
    			this.columnNames = new String[this.columnCount];
    			for (int i = 0; i < this.columnCount; i++) {
    				this.columnTypes[i] = rsmd.getColumnType(i + 1);
    				this.columnNames[i] = JdbcUtils.lookupColumnName(rsmd, i + 1);
    			}
    			// could also get column names
    		}
          //由子类来实现
    		processRow(rs, this.rowCount++);
    	}
    

      流程图如下:

    DataSourceUtils对数据库连接的管理

    获取连接

    public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {
    		try {
    			return doGetConnection(dataSource);
    		}
    		catch (SQLException ex) {
    			throw new CannotGetJdbcConnectionException("Could not get JDBC Connection", ex);
    		}
    	}
    

    doGetConnection方法

    public static Connection doGetConnection(DataSource dataSource) throws SQLException {
    		Assert.notNull(dataSource, "No DataSource specified");
    		/*把数据库的Connection放到事务管理中进行管理,这里采用是TransactionSynchronizationManager
    		中的ThreadLocal变量和线程绑定数据库连接
    		 */
    		ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
    		if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
    			conHolder.requested();
    			if (!conHolder.hasConnection()) {
    				logger.debug("Fetching resumed JDBC Connection from DataSource");
    				conHolder.setConnection(dataSource.getConnection());
    			}
    			return conHolder.getConnection();
    		}
    		// Else we either got no holder or an empty thread-bound holder here.
    		//如果从holder中没有获取连接,那就从数据源中获取连接
    		logger.debug("Fetching JDBC Connection from DataSource");
    		Connection con = dataSource.getConnection();
    
    		if (TransactionSynchronizationManager.isSynchronizationActive()) {
    			logger.debug("Registering transaction synchronization for JDBC Connection");
    			// Use same Connection for further JDBC actions within the transaction.
    			// Thread-bound object will get removed by synchronization at transaction completion.
    			ConnectionHolder holderToUse = conHolder;
    			if (holderToUse == null) {
    				holderToUse = new ConnectionHolder(con);
    			}
    			else {
    				holderToUse.setConnection(con);
    			}
    			holderToUse.requested();
                //注册到事务管理器中
    			TransactionSynchronizationManager.registerSynchronization(
    					new ConnectionSynchronization(holderToUse, dataSource));
    			holderToUse.setSynchronizedWithTransaction(true);
    			if (holderToUse != conHolder) {
    				TransactionSynchronizationManager.bindResource(dataSource, holderToUse);
    			}
    		}
    		return con;
    	}
    

    继续来看一下TransactionSynchronizationManager是如何返回ConnectionHolder,最终的方法是在

    private static Object doGetResource(Object actualKey) {
            Map<Object, Object> map = (Map)resources.get();
            if(map == null) {
                return null;
            } else {
                Object value = map.get(actualKey);
                if(value instanceof ResourceHolder && ((ResourceHolder)value).isVoid()) {
                    map.remove(actualKey);
                    if(map.isEmpty()) {
                        resources.remove();
                    }
    
                    value = null;
                }
    
                return value;
            }
        }
    

    是从resource中获取一个Map,这个Map中的key就是dataSource,value为ConnectionHolder,而Connection就是包装在里面,resource又是什么类型的?

    private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal("Transactional resources");

          从上面可以了解到事务是和Connection绑定在一起的,同时使用了LocalThread来保存,使得线程安全。

    最后问dataSource(数据源)又是从哪里来的?dataSource作为JdbcTemplate的基类JdbcAccessor的属性是通过Ioc容器注入,可以看一下项目数据源都需要在spring.xml或配置类中进行配置,由spring容器来管理。对于dataSource也可以使用连接池,这里就需要采用第三方dbcp或者c3p0来完成,然后又容器将dataSource交给JdbcTemplate使用。

  • 相关阅读:
    重构的体会——类属性优先移动
    jQuery实现无限循环滚动公告
    jquery菜单左右翻屏效果
    44种IE css bug实例测试总结
    IE6不支持position:fixed的解决方法
    DedeCMS会员排行调用代码,实现连接到会员空间
    程序员们 不要想一辈子靠技术混饭吃
    Load JSON data with jQuery, PHP and MySQL
    mysql 实现行号的方法——如何获取当前记录所在行号
    jQuery精仿手机上的翻牌效果菜单
  • 原文地址:https://www.cnblogs.com/lzeffort/p/7882104.html
Copyright © 2011-2022 走看看