Java开源笔记:Spring源代码解析

夜半待客客不至,闲敲棋子落灯花; 一个好的开源软件代码就像一卷优雅的棋谱,让我们好好享受一下吧!

2007年6月5日星期二

下面我们看看Spring JDBC相关的实现,
在Spring中,JdbcTemplate是经常被使用的类来帮助用户程序操作数据库,在JdbcTemplate为用户程序提供了许多便利的数据库操作方法,比如查询,更新等,而且在Spring中,也许多类似JdbcTemplate的Template,比如HibernateTemplate等等 - 看来这是Rod.Johnson的惯用手法,一般而言这种Template中都是通过回调函数CallBack类的使用来重新对需要客户订制的行为进行定制,比如使用客户想要用的SQL语句(Spring又不是神仙,它只是做好模板来减少用户程序的工作量,至于具体要做什么要查询删除什么,那还得劳您大驾),一般来说回调函数的用法采用一下这种一个匿名类的方式,比如:
JdbcTemplate = new JdbcTemplate(datasource);
jdbcTemplate.execute(new CallBack(){
public CallbackInterface(){
......
//用户定义的代码或者说Spring替我们实现的代码
}
}
实际在Template的过程中,用户定义的代码就是模板代码的实现,在模板中嵌入客户代码把模板客户化事项客户程序要求的功能。下面让我们具体看看在JdbcTemplate中的代码是怎样具体完成自己的使命的,我们举出JdbcTemplate.execute()为例,这个方法是在JdbcTemplate中被其他方法调用的基本方法之一来执行基本的SQL语句:
public Object execute(ConnectionCallback action) throws DataAccessException {
//这里得到数据库联接
Connection con = DataSourceUtils.getConnection(getDataSource());
try {
Connection conToUse = con;
//有些特殊的数据库需要我们使用特别的方法取得datasource
if (this.nativeJdbcExtractor != null) {
// Extract native JDBC Connection, castable to OracleConnection or the like.
conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
}
else {
// Create close-suppressing Connection proxy, also preparing returned Statements.
conToUse = createConnectionProxy(con);
}
//这里调用的是传递进来的匿名类的方法,也就是用户程序需要实现CallBack接口的地方。
return action.doInConnection(conToUse);
}
catch (SQLException ex) {
//如果捕捉到数据库异常,把数据库联接释放,同时抛出一个经过Spring转换过的Spring数据库异常,
//我们知道,Spring做了一个有意义的工作是把这些数据库异常统一到自己的异常体系里了。
DataSourceUtils.releaseConnection(con, getDataSource());
con = null;
throw getExceptionTranslator().translate("ConnectionCallback", getSql(action), ex);
}
finally {
DataSourceUtils.releaseConnection(con, getDataSource());
}
}
对于JdbcTemplate中给出的其他方法,比如query,update,execute,我们看看query的基本处理:
public Object query(
PreparedStatementCreator psc, final PreparedStatementSetter pss, final ResultSetExtractor rse)
throws DataAccessException {
..........
//这里调用了我们上面看到的execute()基本方法;这里基本上是Spring为我们代劳
return execute(psc, new PreparedStatementCallback() {
public Object 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();
}
}
}
});
}

其中同过辅助类DataSourceUtils来对数据库连接进行管理,比如打开和关闭等操作。
public static Connection doGetConnection(DataSource dataSource) throws SQLException {
Assert.notNull(dataSource, "No DataSource specified");
//把对数据库连接放到事务管理里面进行管理
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.

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;
}
那我们实际的DataSource对象是怎样得到的?我们对JdbcTemplate配置DataSource,它是在JdbcTemplate的父类中被定义的:
public abstract class JdbcAccessor implements InitializingBean {

protected final Log logger = LogFactory.getLog(getClass());

/** Used to obtain connections throughout the lifecycle of this object */
private DataSource dataSource;

/** Helper to translate SQL exceptions to DataAccessExceptions */
private SQLExceptionTranslator exceptionTranslator;

private boolean lazyInit = true;

........
}
而对于DataSource, 我们可以通过定义Apache Jakarta Commons DBCP或者C3P0提供的DataSource实现来完成,然后只要让JdbcTemplate保持对它的引用就可以直接使用了。 JdbcTemplate提供了简单查询和更新功能,但是可能需要更高层次的抽象,以及更面向对象的方法来访问数据库。这种功能是由 org.springframework.jdbc.object来提供的,包含了SqlQuery,SqlMappingQuery, SqlUpdate和StoredProcedure类,这些类都是Spring JDBC应用程序的主要类,使用这些类,用户需要为他们配置好一个JdbcTemplate,因为他们在内部使用JdbcTemplate用于数据库访 问。
比如说我们使用MappingSqlQuery来将表数据直接映射到一个对象集合:
1.我们需要建立DataSource和sql语句并建立持有这些对象的MappingSqlQuery对象
2.然后我们需要定义传递的SqlParameter,具体的实现我们在MappingSqlQuery的父类RdbmsOperation中可以找到:
public void declareParameter(SqlParameter param) throws InvalidDataAccessApiUsageException {
if (isCompiled()) {
throw new InvalidDataAccessApiUsageException("Cannot add parameters once query is compiled");
}
this.declaredParameters.add(param);
}
而这个declareParameters定义的是一个:
/** List of SqlParameter objects */
private List declaredParameters = new LinkedList();
在以后compile的过程中我们会使用到他。
3.然后实现MappingSqlQuery的mapRow接口,将具体的ResultSet数据生成我们需要的对象,这是我们迭代使用的方法。1,2,3步实际上为我们定义好了一个操作模板。
4.在应用程序,我们直接调用execute()方法得到我们需要的对象列表,列表中的每一个对象的数据来自于执行SQL语句得到记录集的每一条记录,事实上执行的方法在父类SqlQuery中:
public List executeByNamedParam(Map paramMap, Map context) throws DataAccessException {
validateNamedParameters(paramMap);
Object[] parameters = NamedParameterUtils.buildValueArray(getSql(), paramMap);
RowMapper rowMapper = newRowMapper(parameters, context);
String sqlToUse = NamedParameterUtils.substituteNamedParameters(getSql(), new MapSqlParameterSource(paramMap));
//我们又看到了JdbcTemplate,这里使用JdbcTemplate来完成对数据库的查询操作,所以我们说JdbcTemplate是基本的操作类。
return getJdbcTemplate().query(newPreparedStatementCreator(sqlToUse, parameters), rowMapper);
}
在这里我 们可以看到template模式的精彩应用和对JdbcTemplate的灵活使用。通过使用它,我们免去了手工迭代ResultSet并将其中的数据转 化为对象列表的重复过程。在这里我们只需要定义SQL语句和SqlParameter - 如果需要的话,往往SQL语句就常常能够满足我们的要求了。
Spring还为其他数据库操作提供了许多服务,比如使用SqlUpdate插入和更新数据库,使用UpdatableSqlQuery更新ResultSet,生成主键,调用存储过程等。
书中还给出了对BLOB数据和CLOB数据进行数据库操作的例子:
对BLOB数据的操作通过LobHander来完成,通过调用JdbcTemplate和RDBMS都可以进行操作:
在JdbcTemplate中,具体的调用可以参考书中的例子 - 是通过以下调用起作用的:
public Object execute(String sql, PreparedStatementCallback action) throws DataAccessException {
return execute(new SimplePreparedStatementCreator(sql), action);
}
然后通过对实现PreparedStatementCallback接口的AbstractLobCreatingPreparedStatementCallback的回调函数来完成:
public final Object doInPreparedStatement(PreparedStatement ps) throws SQLException, DataAccessException {
LobCreator lobCreator = this.lobHandler.getLobCreator();
try {
setValues(ps, lobCreator);
return new Integer(ps.executeUpdate());
}
finally {
lobCreator.close();
}
}

protected abstract void setValues(PreparedStatement ps, LobCreator lobCreator)
throws SQLException, DataAccessException;
而我们注意到setValues()是一个需要实现的抽象方法,应用程序通过实现setValues来定义自己的操作 - 在setValues中调用lobCreator.setBlobAsBinaryStrem()。让我们看看具体的BLOB操作在LobCreator是怎样完 成的,在DefaultLobCreator中的实现:
public void setBlobAsBinaryStream(
PreparedStatement ps, int paramIndex, InputStream binaryStream, int contentLength)
throws SQLException {
//通过JDBC来完成对BLOB数据的操作,对Oracle,Spring提供了OracleLobHandler来支持BLOB操作。
ps.setBinaryStream(paramIndex, binaryStream, contentLength);
if (logger.isDebugEnabled()) {
logger.debug(binaryStream != null ? "Set binary stream for BLOB with length " + contentLength :
"Set BLOB to null");
}
}
书中还提到关于execute和update方法之间的区别,update方法返回的是受影响的记录数目的 一个计数,并且如果传入参数的话,使用的是java.sql.PreparedStatement,而execute方法总是使用 java.sql.Statement,不接受参数,而且他不返回受影响记录的计数,更适合于创建和丢弃表的语句,而update方法更适合于插入,更新 和删除操作,这也是我们在使用时需要注意的。

没有评论:

博客归档