论坛首页 Java企业应用论坛

Spring技术内幕——深入解析Spring架构与设计原理(三)数据库的操作实现

浏览 9919 次
精华帖 (17) :: 良好帖 (0) :: 新手帖 (1) :: 隐藏帖 (0)
作者 正文
   发表时间:2009-11-02   最后修改:2009-11-18
最近事情实在是比较多,没有及时更新帖子,还望大家见谅啊。今天,一起讨论讨论Spring JDBC的实现吧。

关于Spring JDBC
还是从Spring JDBC说起吧,虽然现在应用很多都是直接使用Hibernate或者其他的ORM工具。但JDBC毕竟还是很基本的,其中的JdbcTemplate就是我们经常使用的,比如JDBCTemplate的execute方法,就是一个基本的方法,在这个方法的实现中,可以看到对数据库操作的基本过程。
	//execute方法执行的是输入的sql语句
	public void execute(final String sql) throws DataAccessException {
		if (logger.isDebugEnabled()) {
			logger.debug("Executing SQL statement [" + sql + "]");
		}
		class ExecuteStatementCallback implements StatementCallback<Object>, SqlProvider {
			public Object doInStatement(Statement stmt) throws SQLException {
				stmt.execute(sql);
				return null;
			}
			public String getSql() {
				return sql;
			}
		}
		execute(new ExecuteStatementCallback());
	}
	//这是使用java.sql.Statement处理静态SQL语句的方法
	public <T> T execute(StatementCallback<T> action) throws DataAccessException {
		Assert.notNull(action, "Callback object must not be null");
		//这里取得数据库的Connection,这个数据库的Connection已经在Spring的事务管理之下
		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) {
			// Release Connection early, to avoid potential connection pool deadlock
			// in the case when the exception translator hasn't been initialized yet.
			//如果捕捉到数据库异常,把数据库Connection释放,同时抛出一个经过Spring转换过的Spring数据库异常
			//Spring做了一项有意义的工作,就是把这些数据库异常统一到自己的异常体系里了
			JdbcUtils.closeStatement(stmt);
			stmt = null;
			DataSourceUtils.releaseConnection(con, getDataSource());
			con = null;
			throw getExceptionTranslator().translate("StatementCallback", getSql(action), ex);
		}
		finally {
			JdbcUtils.closeStatement(stmt);
			//释放数据库connection
			DataSourceUtils.releaseConnection(con, getDataSource());
		}
	}


在使用数据库的时候,有一个很重要的地方就是对数据库连接的管理,在这里,是由DataSourceUtils来完成的。Spring通过这个辅助类来对数据的Connection进行管理。比如通过它来完成打开和关闭Connection等操作。DataSourceUtils对这些数据库Connection管理的实现, 如以下代码所示。
	//这是取得数据库连接的调用,实现是通过调用doGetConnection完成的,这里执行了异常的转换操作
	public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {
		try {
			return doGetConnection(dataSource);
		}
		catch (SQLException ex) {
			throw new CannotGetJdbcConnectionException("Could not get JDBC Connection", ex);
		}
	}
	public static Connection doGetConnection(DataSource dataSource) throws SQLException {
		Assert.notNull(dataSource, "No DataSource specified");
		//把对数据库的Connection放到事务管理中进行管理,这里使用TransactionSynchronizationManager中定义的ThreadLocal变量来和线程绑定数据库连接
		//如果在TransactionSynchronizationManager中已经有与当前线程绑定数据库连接,那就直接取出来使用
		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.
		// 这里得到需要的数据库Connection,在Bean配置文件中定义好的,
		// 同时最后把新打开的数据库Connection通过TransactionSynchronizationManager和当前线程绑定起来。
		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;
	}



关于数据库操作类RDBMS
从JdbcTemplate中,我们看到,他提供了许多简单查询和更新的功能。但是,如果需要更高层次的抽象,以及更面向对象的方法来访问数据库,Spring为我们提供了org.springframework.jdbc.object包,里面包含了SqlQuery、SqlMappingQuery、SqlUpdate和StoredProcedure等类,这些类都是Spring JDBC应用程序可以使用的。但要注意,在使用这些类时需要为它们配置好JdbcTemplate作为其基本的操作实现,因为在它们的功能实现中,对数据库操作的那部分实现基本上还是依赖于JdbcTemplate来完成的。

比如,对MappingSqlQuery使用的过程,是非常简洁的;在设计好数据的映射代码之后,查询得到的记录已经按照前面的设计转换为对象List了,一条查询记录对应于一个数据对象,可以把数据库的数据记录直接映射成Java对象在程序中使用,同时又可避免使用第三方ORM工具的配置,对于简单的数据映射场合是非常方便的;在mapRow方法的实现中提供的数据转换规则,和我们使用Hibernate时,Hibernate的hbm文件起到的作用是非常类似的。这个MappingSqlQuery需要的对设置进行compile,这些compile是这样完成的,如以下代码所示:
	protected final void compileInternal() {
		//这里是对参数的compile过程,所有的参数都在getDeclaredParameters里面,生成了一个PreparedStatementCreatorFactory
		this.preparedStatementFactory = new PreparedStatementCreatorFactory(getSql(), getDeclaredParameters());
		this.preparedStatementFactory.setResultSetType(getResultSetType());
		this.preparedStatementFactory.setUpdatableResults(isUpdatableResults());
		this.preparedStatementFactory.setReturnGeneratedKeys(isReturnGeneratedKeys());
		if (getGeneratedKeysColumnNames() != null) {
			this.preparedStatementFactory.setGeneratedKeysColumnNames(getGeneratedKeysColumnNames());
		}
this.preparedStatementFactory.setNativeJdbcExtractor(getJdbcTemplate().getNativeJdbcExtractor());
		onCompileInternal();
	}
在执行查询时,执行的实际上是SqlQuery的executeByNamedParam方法,这个方法需要完成的工作包括配置SQL语句,配置数据记录到数据对象的转换的RowMapper,然后使用JdbcTemplate来完成数据的查询,并启动数据记录到Java数据对象的转换,如以下代码所示:
	public List<T> executeByNamedParam(Map<String, ?> paramMap, Map context) throws DataAccessException {
		validateNamedParameters(paramMap);
		//得到需要执行的SQL语句
		ParsedSql parsedSql = getParsedSql();
		MapSqlParameterSource paramSource = new MapSqlParameterSource(paramMap);
		String sqlToUse = NamedParameterUtils.substituteNamedParameters(parsedSql, paramSource);
		//配置好SQL语句需要的Parameters及rowMapper,这个rowMapper完成数据记录到对象的转换
		Object[] params = NamedParameterUtils.buildValueArray(parsedSql, paramSource, getDeclaredParameters());
		RowMapper<T> rowMapper = newRowMapper(params, context);
		//我们又看到了JdbcTemplate,这里使用JdbcTemplate来完成对数据库的查询操作,所以我们说JdbcTemplate是非常基本的操作类
 		return getJdbcTemplate().query(newPreparedStatementCreator(sqlToUse, params), rowMapper);
	}


在Spring对JDBC的操作中,基本上是对JDBC/Hibernate基础上API的封装。这些封装可以直接使用,也可以在IoC容器中配置好了再使用,当结合IoC容器的基础上进行使用的时候,可以看到许多和事务管理相关的处理部分,都是非常值得学习的,在那里,可以看到对数据源的管理 - Hibernate中session的管理,与线程的结合等等。
   发表时间:2009-11-04  
建议楼主出书吧
0 请登录后投票
   发表时间:2009-11-04  
lz 能把这些整理一下,做成ptf 应该会很好。。
看了分析,有点模糊,但觉得不错。。
0 请登录后投票
   发表时间:2009-11-04  
貌似回调的这个写法也可适用于事务管理
1 请登录后投票
   发表时间:2009-11-04  
lengyun3566 写道
建议楼主出书吧


谢谢您的建议和对楼主文章的肯定,楼主这么好的文章,出版社肯定是不会错过的。

提前爆料一下,楼主将基于Spring的源代码来解析Spring的架构以及设计原理,出版一本叫做《Spring技术内幕——深入解析Spring架构与设计原理》的书,旨在给以下几个方面的读者以启发:

1. 学习 Java语言和Java EE技术的中高级读者
Spring是使用Java语言实现的,它的很多功能的源代码的设计和实现都极其优秀,非常具有研究价值。对这部分读者来说,不仅可以从本书中了解到Spring的实现原理,而且还能通过Spring的源代码,掌握大量的Java编码技巧和Java EE开发技术。

2. Spring应用开发人员
如果要利用Spring进行高级应用开发,抑或是进行相关的优化和扩展工作,仅仅掌握Spring的配置和基本使用是远远不够的,我们必须要对Spring框架的设计原理、架构和运作机制有一定的了解。对这部分读者而言,本书将带领他们全面了解Spring实现原理,从而加深对Spring框架的理解,提高自己的开发水平。同时,本书可以作为他们定制和扩展Spring框架的参考资料。

3. 开源软件爱好者
Spring是开源软件中的佼佼者,它在实现的过程中吸收了很多开源领域的优秀思想,同时也有很多值得其他开源软件学习的创新。尤为值得一提的是,本书分析Spring源代码的方式,也许值得所有想分析开源软件源代码的开源软件爱好者们学习和借鉴。通过阅读本书,这部分读者不仅能领略到开源软件的优秀思想,而且掌握分析开源软件源代码的方法和技巧,从而进一步提高使用开源软件的效率和质量。

[b]4. 平台开发人员和架构师[/b]
前面已经反复强调,Spring的设计思想、架构和实现都是非常优秀的,是平台开发人员和架构师们不可多得的参考资料。


1 请登录后投票
   发表时间:2009-11-04  
分析源码 只有 自己看

看文章 难看懂
0 请登录后投票
   发表时间:2009-11-04  
这样书难写
写简单了没有人要
写复杂 还是没有人要

要么看不懂
看懂的人就去看源码了

好处难出

最近写了什么spring揭秘 其实 就是 口水话翻译国外的书
0 请登录后投票
   发表时间:2009-11-04   最后修改:2009-11-04
rrsy23 写道
这样书难写
写简单了没有人要
写复杂 还是没有人要

要么看不懂
看懂的人就去看源码了


你的看法很对啊,我也是这样想的。不过,我觉得如果看书的话,比较适合梳理知识,毕竟我们的阅读习惯都是在书本中养成的,然后遇到详细和难解的地方,再去hack代码,这样就相得益彰了。一开始就去直接看代码,还是需要一点勇气的,如果有文档能够起到一个引领入门的作用,那就很好了,这也是在下正在努力的地方。
0 请登录后投票
   发表时间:2009-11-04  
分析源码的书更重要的是授人以渔!
0 请登录后投票
   发表时间:2009-11-05  
我也觉得回调的这个写法也可适用于事务管理
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics