您的位置 首页 >  博文

Spring源码解析之JDBC

引用自博客:http://jiwenke-spring.blogspot.com/

下面我们看看Spring JDBC相关的实现,在Spring中,JdbcTemplate是经常被使用的类来帮助用户程序操作数据库,在JdbcTemplate为用户程序提供了许多便利的数据库操作方法,比如查询,更新等,而且在Spring中,有许多类似 JdbcTemplate的模板,比如HibernateTemplate等等 - 看来这是Rod.Johnson的惯用手法,一般而言这种Template中都是通过回调函数CallBack类的使用来完成功能的,客户需要在回调接口中实现自己需要的定制行为,比如使用客户想要用的SQL语句等。不过往往Spring通过这种回调函数的实现已经为我们提供了许多现成的方法供客户使用。一般来说回调函数的用法采用匿名类的方式来实现,比如:

1JdbcTemplate = new JdbcTemplate(datasource); 
2jdbcTemplate.execute(new CallBack(){ 
3    public CallbackInterfacedoInAction()
4        ...
5        //用户定义的代码或者说 Spring 替我们实现的代码 
6    } 
7

在模板中嵌入的是需要客户化的代码,由 Spring 来作或者需要客户程序亲自动手完成。下面让我们具体看看在 JdbcTemplate 中的代码是怎样完成使命的,我们举 JdbcTemplate.execute()为例,这个方法是在 JdbcTemplate 中被其他方法调用的基本方法之一,客户程序往往用这个方法来执行基本的 SQL 语句:

 1public Object execute(ConnectionCallback action) throws DataAccessException 
2    //这里得到数据库联接 
3    Connection con = DataSourceUtils.getConnection(getDataSource()); 
4    try { 
5        Connection conToUse = con; 
6        //有些特殊的数据库,需要我们使用特别的方法取得 datasource 
7        if (this.nativeJdbcExtractor != null) { 
8            // Extract native JDBC Connection, castable to OracleConnection or the like. 
9            conToUse = this.nativeJdbcExtractor.getNativeConnection(con); 
10        } 
11        else { 
12            // Create close-suppressing Connection proxy, also preparing returned Statements. 
13            conToUse = createConnectionProxy(con); 
14        } 
15        //这里调用的是传递进来的匿名类的方法,也就是用户程序需要实现 CallBack 接口的地方。 
16        return action.doInConnection(conToUse); 
17    } 
18    catch (SQLException ex) { 
19        //如果捕捉到数据库异常,把数据库联接释放,同时抛出一个经过 Spring 转换过的 Spring 数据库异常, 
20        //我们知道,Spring 做了一个有意义的工作是把这些数据库异常统一到自己的异常体系里了。 
21        DataSourceUtils.releaseConnection(con, getDataSource()); 
22        con = null
23        throw getExceptionTranslator().translate("ConnectionCallback", getSql(action), ex); 
24    } 
25    finally { 
26        //最后不管怎样都会把数据库连接释放 
27        DataSourceUtils.releaseConnection(con, getDataSource()); 
28    } 
29

对于 JdbcTemplate 中给出的其他方法,比如 query,update,execute 等的实现,我们看看 query():

 1public Object query(PreparedStatementCreator psc, final PreparedStatementSetter pss, final ResultSetExtractor rse) throws DataAccessException 
2    ...
3    //这里调用了我们上面看到的 execute()基本方法,然而这里的回调实现是 Spring 为我们完成的查询过程 
4    return execute(psc, new PreparedStatementCallback() { 
5        public Object doInPreparedStatement(PreparedStatement ps) throws SQLException 
6            //准备查询结果集 
7            ResultSet rs = null
8            try { 
9                //这里配置 SQL 参数 
10                if (pss != null) { 
11                    pss.setValues(ps); 
12                } 
13                //这里执行的 SQL 查询 
14                rs = ps.executeQuery(); 
15                ResultSet rsToUse = rs; 
16                if (nativeJdbcExtractor != null) { 
17                    rsToUse = nativeJdbcExtractor.getNativeResultSet(rs); 
18                } 
19                //返回需要的记录集合 
20                return rse.extractData(rsToUse); 
21            }finally { 
22                //最后关闭查询的纪录集,对数据库连接的释放在 execute()中释放,就像我们在上面分析的看到那样。 
23                JdbcUtils.closeResultSet(rs); 
24                if (pss instanceof ParameterDisposer) { 
25                    ((ParameterDisposer) pss).cleanupParameters(); 
26                } 
27            } 
28        } 
29    }); 
30

辅助类 DataSourceUtils 来用来对数据库连接进行管理的主要工具,比如打开和关闭数据库连接等基本操作:

 1public static Connection doGetConnection(DataSource dataSource) throws SQLException 
2    //把对数据库连接放到事务管理里面进行管理 
3    ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource); 
4    if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) { 
5        conHolder.requested(); 
6        if (!conHolder.hasConnection()) { 
7            logger.debug("Fetching resumed JDBC Connection from DataSource"); 
8            conHolder.setConnection(dataSource.getConnection()); 
9        } 
10        return conHolder.getConnection(); 
11    } 
12    // 这里得到需要的数据库连接,在配置文件中定义好的。 
13    logger.debug("Fetching JDBC Connection from DataSource"); 
14    Connection con = dataSource.getConnection(); 
15
16    if (TransactionSynchronizationManager.isSynchronizationActive()) { 
17        logger.debug("Registering transaction synchronization for JDBC Connection"); 
18        // Use same Connection for further JDBC actions within the transaction. 
19        // Thread-bound object will get removed by synchronization at transaction completion. 
20        ConnectionHolder holderToUse = conHolder; 
21        if (holderToUse == null) { 
22            holderToUse = new ConnectionHolder(con); 
23        } 
24        else { 
25            holderToUse.setConnection(con); 
26        } 
27        holderToUse.requested(); 
28        TransactionSynchronizationManager.registerSynchronization(new ConnectionSynchronization(holderToUse, dataSource)); 
29        holderToUse.setSynchronizedWithTransaction(true); 
30        if (holderToUse != conHolder) { 
31            TransactionSynchronizationManager.bindResource(dataSource, holderToUse); 
32        } 
33    } 
34
35    return con; 
36

那我们实际的 DataSource 对象是怎样得到的?很清楚我们需要在上下文中进行配置:它作为 JdbcTemplate 父类 JdbcAccessor 的属性存在:

 1public abstract class JdbcAccessor implements InitializingBean 
2    /** 这里是我们依赖注入数据库数据源的地方。 */ 
3    private DataSource dataSource; 
4
5    /** Helper to translate SQL exceptions to DataAccessExceptions */ 
6    private SQLExceptionTranslator exceptionTranslator; 
7
8    private boolean lazyInit = true
9    ...
10

而对于 DataSource 的缓冲池实现,我们通过定义 Apache Jakarta Commons DBCP 或者 C3P0 提供的 DataSource 来完成,然后只要在上下文中配置好就可以使用了。从上面我们看到 JdbcTemplate 提供了许多简单查询和更新功能,但是如果需要更高层次的抽象,以及更面向对象的方法来访问数据库。Spring 为我们提供了org.springframework.jdbc.object 包,这里面包含了 SqlQuery,SqlMappingQuery,
SqlUpdate 和 StoredProcedure 等类,这些类都是 Spring JDBC 应用程序可以使用的主要类,但我们要注意使用这些类的时候,用户需要为他们配置好一个 JdbcTemplate 作为其基本的操作的实现。

比如说我们使用 MappingSqlQuery 来将表数据直接映射到一个对象集合 - 具体可以参考书中的例子

  1. 我们需要建立 DataSource 和 sql 语句并建立持有这些对象的 MappingSqlQuery 对象

  2. 然后我们需要定义传递的 SqlParameter,具体的实现我们在 MappingSqlQuery 的父类 RdbmsOperation 中可以找到:

1public void declareParameter(SqlParameter param) throws InvalidDataAccessApiUsageException 
2    //如果声明已经被编译过,则该声明无效 
3    if (isCompiled()) { 
4        throw new InvalidDataAccessApiUsageException("Cannot add parameters once query is compiled"); 
5    } 
6    //这里对参数值进行声明定义 
7    this.declaredParameters.add(param); 

而这个 declareParameters 维护的是一个列表:

1/** List of SqlParameter objects */ 
2private List declaredParameters = new LinkedList(); 

这个列表在以后 compile 的过程中会被使用。

  1. 然后用户程序需要实现 MappingSqlQuery 的 mapRow 接口,将具体的 ResultSet 数据生成我们需要的对象,这是我们迭代使用的方法。1,2,3 步实际上为我们定义好了一个迭代的基本单元作为操作模板。

  2. 在应用程序,我们直接调用 execute()方法得到我们需要的对象列表,列表中的每一个对象的数据来自于执行 SQL 语句得到记录集的每一条记录,事实上执行的 execute 在父类 SqlQuery 中起作用:

1public List executeByNamedParam(Map paramMap, Map context) throws DataAccessException 
2    validateNamedParameters(paramMap); 
3    Object[] parameters = NamedParameterUtils.buildValueArray(getSql(), paramMap); 
4    RowMapper rowMapper = newRowMapper(parameters, context); 
5    String sqlToUse = NamedParameterUtils.substituteNamedParameters(getSql(), new MapSqlParameterSource(paramMap)); 
6    //我们又看到了 JdbcTemplate,这里使用 JdbcTemplate 来完成对数据库的查询操作,所以我们说 JdbcTemplate 是基本的操作类。 
7    return getJdbcTemplate().query(newPreparedStatementCreator(sqlToUse, parameters), rowMapper); 
8

在这里我们可以看到 template 模式的精彩应用和对 JdbcTemplate 的灵活使用。通过使用它,我们免去了手工迭代 ResultSet 并将其中的数据转化为对象列表的重复过程。在这里我们只需要定义 SQL 语句和 SqlParameter - 如果需要的话,往往 SQL 语句就常常能够满足我们的要求了。这是灵活使用 JdbcTemplate 的一个很好的例子。

Spring 还为其他数据库操作提供了许多服务,比如使用 SqlUpdate 插入和更新数据库,使用 UpdatableSqlQuery 更新 ResultSet,生成主键,调用存储过程等。

书中还给出了对 BLOB 数据和 CLOB 数据进行数据库操作的例子:
对 BLOB 数据的操作通过 LobHander 来完成,通过调用 JdbcTemplate 和 RDBMS 都可以进行操作:
在 JdbcTemplate 中,具体的调用可以参考书中的例子 - 是通过以下调用起作用的:

1public Object execute(String sql, PreparedStatementCallback action) throws DataAccessException 
2    return execute(new SimplePreparedStatementCreator(sql), action); 
3

然后通过对实现 PreparedStatementCallback 接口的 AbstractLobCreatingPreparedStatementCallback 的回调函数来完成:

 1public final Object doInPreparedStatement(PreparedStatement ps) throws SQLException, DataAccessException 
2    LobCreator lobCreator = this.lobHandler.getLobCreator(); 
3    try { 
4        //这是一个模板方法,具体需要由客户程序实现 
5        setValues(ps, lobCreator); 
6        return new Integer(ps.executeUpdate()); 
7    }finally { 
8        lobCreator.close(); 
9    } 
10
11//定义的需要客户程序实现的虚函数 
12protected abstract void setValues(PreparedStatement ps, LobCreator lobCreator) throws SQLException, DataAccessException

而我们注意到 setValues()是一个需要实现的抽象方法,应用程序通过实现 setValues 来定义自己的操作 - 在 setValues 中调用lobCreator.setBlobAsBinaryStrem()。让我们看看具体的 BLOB 操作在 LobCreator 是怎样完成的,我们一般使用 DefaultLobCreator 作为BLOB 操作的驱动:

1public void setBlobAsBinaryStream(PreparedStatement ps, int paramIndex, InputStream binaryStream, int contentLength) throws SQLException 
2    //通过 JDBC 来完成对 BLOB 数据的操作,对 Oracle,Spring 提供了 OracleLobHandler 来支持 BLOB 操作。 
3    ps.setBinaryStream(paramIndex, binaryStream, contentLength); 
4    ...
5

上面提到的是零零碎碎的 Spring JDBC 使用的例子,可以看到使用 Spring JDBC 可以帮助我们完成许多数据库的操作。Spring 对数据库操作最基本的服务是通过 JdbcTeamplate 和他常用的回调函数来实现的,在此之上,又提供了许多 RMDB 的操作来帮助我们更便利的对数据库的数据进行操作 - 注意这里没有引入向 Hibernate 这样的 O/R 方案。对这些 O/R 方案的支持,Spring 由其他包来完成服务。

书中还提到关于 execute 和 update 方法之间的区别,update 方法返回的是受影响的记录数目的一个计数,并且如果传入参数的话,使用的是 java.sql.PreparedStatement,而 execute 方法总是使用 java.sql.Statement,不接受参数,而且他不返回受影响记录的计数,更适合于创建和丢弃表的语句,而 update 方法更适合于插入,更新和删除操作,这也是我们在使用时需要注意的。


关于作者: 王俊南(Jonas)

昨夜寒蛩不住鸣。惊回千里梦,已三更。起来独自绕阶行。人悄悄,帘外月胧明。 白首为功名。旧山松竹老,阻归程。欲将心事付瑶琴。知音少,弦断有谁听。

热门文章