下面我們看看Spring JDBC相關(guān)的實(shí)現(xiàn),
在Spring中,JdbcTemplate是經(jīng)常被使用的類來幫助用戶程序操作數(shù)據(jù)庫(kù),在JdbcTemplate為用戶程序提供了許多便利的數(shù)據(jù)庫(kù)操作方法,比如查詢,更新等,而且在Spring中,有許多類似 JdbcTemplate的模板,比如HibernateTemplate等等 - 看來這是Rod.Johnson的慣用手法,一般而言這種Template中都是通過回調(diào)函數(shù)CallBack類的使用來完成功能的,客戶需要在回調(diào)接口中實(shí)現(xiàn)自己需要的定制行為,比如使用客戶想要用的SQL語句等。不過往往Spring通過這種回調(diào)函數(shù)的實(shí)現(xiàn)已經(jīng)為我們提供了許多現(xiàn)成的方法供客戶使用。一般來說回調(diào)函數(shù)的用法采用匿名類的方式來實(shí)現(xiàn),比如:
- JdbcTemplate = new JdbcTemplate(datasource);
- jdbcTemplate.execute(new CallBack(){
- public CallbackInterfacedoInAction(){
- ......
-
- }
- }
JdbcTemplate = new JdbcTemplate(datasource);
jdbcTemplate.execute(new CallBack(){
public CallbackInterfacedoInAction(){
......
//用戶定義的代碼或者說Spring替我們實(shí)現(xiàn)的代碼
}
}
在模板中嵌入的是需要客戶化的代碼,由Spring來作或者需要客戶程序親自動(dòng)手完成。下面讓我們具體看看在JdbcTemplate中的代碼是怎樣完成使命的,我們舉JdbcTemplate.execute()為例,這個(gè)方法是在JdbcTemplate中被其他方法調(diào)用的基本方法之一,客戶程序往往用這個(gè)方法來執(zhí)行基本的SQL語句:
- public Object execute(ConnectionCallback action) throws DataAccessException {
-
- Connection con = DataSourceUtils.getConnection(getDataSource());
- try {
- Connection conToUse = con;
-
- if (this.nativeJdbcExtractor != null) {
-
- conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
- }
- else {
-
- conToUse = createConnectionProxy(con);
- }
-
- return action.doInConnection(conToUse);
- }
- catch (SQLException ex) {
-
-
- DataSourceUtils.releaseConnection(con, getDataSource());
- con = null;
- throw getExceptionTranslator().translate("ConnectionCallback", getSql(action), ex);
- }
- finally {
-
- DataSourceUtils.releaseConnection(con, getDataSource());
- }
- }
public Object execute(ConnectionCallback action) throws DataAccessException {
//這里得到數(shù)據(jù)庫(kù)聯(lián)接
Connection con = DataSourceUtils.getConnection(getDataSource());
try {
Connection conToUse = con;
//有些特殊的數(shù)據(jù)庫(kù),需要我們使用特別的方法取得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);
}
//這里調(diào)用的是傳遞進(jìn)來的匿名類的方法,也就是用戶程序需要實(shí)現(xiàn)CallBack接口的地方。
return action.doInConnection(conToUse);
}
catch (SQLException ex) {
//如果捕捉到數(shù)據(jù)庫(kù)異常,把數(shù)據(jù)庫(kù)聯(lián)接釋放,同時(shí)拋出一個(gè)經(jīng)過Spring轉(zhuǎn)換過的Spring數(shù)據(jù)庫(kù)異常,
//我們知道,Spring做了一個(gè)有意義的工作是把這些數(shù)據(jù)庫(kù)異常統(tǒng)一到自己的異常體系里了。
DataSourceUtils.releaseConnection(con, getDataSource());
con = null;
throw getExceptionTranslator().translate("ConnectionCallback", getSql(action), ex);
}
finally {
//最后不管怎樣都會(huì)把數(shù)據(jù)庫(kù)連接釋放
DataSourceUtils.releaseConnection(con, getDataSource());
}
}
對(duì)于JdbcTemplate中給出的其他方法,比如query,update,execute等的實(shí)現(xiàn),我們看看query():
- public Object query(PreparedStatementCreator psc, final PreparedStatementSetter pss, final ResultSetExtractor rse)
- throws DataAccessException {
- ..........
-
- return execute(psc, new PreparedStatementCallback() {
- public Object doInPreparedStatement(PreparedStatement ps) throws SQLException {
-
- ResultSet rs = null;
- try {
-
- if (pss != null) {
- pss.setValues(ps);
- }
-
- 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();
- }
- }
- }
- });
- }
public Object query(PreparedStatementCreator psc, final PreparedStatementSetter pss, final ResultSetExtractor rse)
throws DataAccessException {
..........
//這里調(diào)用了我們上面看到的execute()基本方法,然而這里的回調(diào)實(shí)現(xiàn)是Spring為我們完成的查詢過程
return execute(psc, new PreparedStatementCallback() {
public Object doInPreparedStatement(PreparedStatement ps) throws SQLException {
//準(zhǔn)備查詢結(jié)果集
ResultSet rs = null;
try {
//這里配置SQL參數(shù)
if (pss != null) {
pss.setValues(ps);
}
//這里執(zhí)行的SQL查詢
rs = ps.executeQuery();
ResultSet rsToUse = rs;
if (nativeJdbcExtractor != null) {
rsToUse = nativeJdbcExtractor.getNativeResultSet(rs);
}
//返回需要的記錄集合
return rse.extractData(rsToUse);
}
finally {
//最后關(guān)閉查詢的紀(jì)錄集,對(duì)數(shù)據(jù)庫(kù)連接的釋放在execute()中釋放,就像我們?cè)谏厦娣治龅目吹侥菢印?
JdbcUtils.closeResultSet(rs);
if (pss instanceof ParameterDisposer) {
((ParameterDisposer) pss).cleanupParameters();
}
}
}
});
}
輔助類DataSourceUtils來用來對(duì)數(shù)據(jù)庫(kù)連接進(jìn)行管理的主要工具,比如打開和關(guān)閉數(shù)據(jù)庫(kù)連接等基本操作:
- public static Connection doGetConnection(DataSource dataSource) throws SQLException {
-
- 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();
- }
-
- logger.debug("Fetching JDBC Connection from DataSource");
- Connection con = dataSource.getConnection();
-
- if (TransactionSynchronizationManager.isSynchronizationActive()) {
- logger.debug("Registering transaction synchronization for JDBC Connection");
-
-
- 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;
- }
public static Connection doGetConnection(DataSource dataSource) throws SQLException {
//把對(duì)數(shù)據(jù)庫(kù)連接放到事務(wù)管理里面進(jìn)行管理
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();
}
// 這里得到需要的數(shù)據(jù)庫(kù)連接,在配置文件中定義好的。
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;
}
那我們實(shí)際的DataSource對(duì)象是怎樣得到的?很清楚我們需要在上下文中進(jìn)行配置:它作為JdbcTemplate父類JdbcAccessor的屬性存在:
- public abstract class JdbcAccessor implements InitializingBean {
-
-
- private DataSource dataSource;
-
-
- private SQLExceptionTranslator exceptionTranslator;
-
- private boolean lazyInit = true;
-
- ........
- }
public abstract class JdbcAccessor implements InitializingBean {
/** 這里是我們依賴注入數(shù)據(jù)庫(kù)數(shù)據(jù)源的地方。 */
private DataSource dataSource;
/** Helper to translate SQL exceptions to DataAccessExceptions */
private SQLExceptionTranslator exceptionTranslator;
private boolean lazyInit = true;
........
}
而對(duì)于DataSource的緩沖池實(shí)現(xiàn),我們通過定義Apache Jakarta Commons DBCP或者C3P0提供的DataSource來完成,然后只要在上下文中配置好就可以使用了。從上面我們看到JdbcTemplate提供了許多簡(jiǎn)單查詢和更新功能,但是如果需要更高層次的抽象,以及更面向?qū)ο蟮姆椒▉碓L問數(shù)據(jù)庫(kù)。Spring為我們提供了org.springframework.jdbc.object包,這里面包含了SqlQuery,SqlMappingQuery, SqlUpdate和StoredProcedure等類,這些類都是Spring JDBC應(yīng)用程序可以使用的主要類,但我們要注意使用這些類的時(shí)候,用戶需要為他們配置好一個(gè)JdbcTemplate作為其基本的操作的實(shí)現(xiàn)。
比如說我們使用MappingSqlQuery來將表數(shù)據(jù)直接映射到一個(gè)對(duì)象集合 - 具體可以參考書中的例子
1.我們需要建立DataSource和sql語句并建立持有這些對(duì)象的MappingSqlQuery對(duì)象
2.然后我們需要定義傳遞的SqlParameter,具體的實(shí)現(xiàn)我們?cè)贛appingSqlQuery的父類RdbmsOperation中可以找到:
- public void declareParameter(SqlParameter param) throws InvalidDataAccessApiUsageException {
-
- if (isCompiled()) {
- throw new InvalidDataAccessApiUsageException("Cannot add parameters once query is compiled");
- }
-
- this.declaredParameters.add(param);
public void declareParameter(SqlParameter param) throws InvalidDataAccessApiUsageException {
//如果聲明已經(jīng)被編譯過,則該聲明無效
if (isCompiled()) {
throw new InvalidDataAccessApiUsageException("Cannot add parameters once query is compiled");
}
//這里對(duì)參數(shù)值進(jìn)行聲明定義
this.declaredParameters.add(param);
}
而這個(gè)declareParameters維護(hù)的是一個(gè)列表:
-
- private List declaredParameters = new LinkedList();
/** List of SqlParameter objects */
private List declaredParameters = new LinkedList();
這個(gè)列表在以后compile的過程中會(huì)被使用。
3.然后用戶程序需要實(shí)現(xiàn)MappingSqlQuery的mapRow接口,將具體的ResultSet數(shù)據(jù)生成我們需要的對(duì)象,這是我們迭代使用的方法。1,2,3步實(shí)際上為我們定義好了一個(gè)迭代的基本單元作為操作模板。
4.在應(yīng)用程序,我們直接調(diào)用execute()方法得到我們需要的對(duì)象列表,列表中的每一個(gè)對(duì)象的數(shù)據(jù)來自于執(zhí)行SQL語句得到記錄集的每一條記錄,事實(shí)上執(zhí)行的execute在父類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));
-
- return getJdbcTemplate().query(newPreparedStatementCreator(sqlToUse, parameters), rowMapper);
- }
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來完成對(duì)數(shù)據(jù)庫(kù)的查詢操作,所以我們說JdbcTemplate是基本的操作類。
return getJdbcTemplate().query(newPreparedStatementCreator(sqlToUse, parameters), rowMapper);
}
在這里我們可以看到template模式的精彩應(yīng)用和對(duì)JdbcTemplate的靈活使用。通過使用它,我們免去了手工迭代ResultSet并將其中的數(shù)據(jù)轉(zhuǎn)化為對(duì)象列表的重復(fù)過程。在這里我們只需要定義SQL語句和SqlParameter - 如果需要的話,往往SQL語句就常常能夠滿足我們的要求了。這是靈活使用JdbcTemplate的一個(gè)很好的例子。
Spring還為其他數(shù)據(jù)庫(kù)操作提供了許多服務(wù),比如使用SqlUpdate插入和更新數(shù)據(jù)庫(kù),使用UpdatableSqlQuery更新ResultSet,生成主鍵,調(diào)用存儲(chǔ)過程等。
書中還給出了對(duì)BLOB數(shù)據(jù)和CLOB數(shù)據(jù)進(jìn)行數(shù)據(jù)庫(kù)操作的例子:
對(duì)BLOB數(shù)據(jù)的操作通過LobHander來完成,通過調(diào)用JdbcTemplate和RDBMS都可以進(jìn)行操作:
在JdbcTemplate中,具體的調(diào)用可以參考書中的例子 - 是通過以下調(diào)用起作用的:
- public Object execute(String sql, PreparedStatementCallback action) throws DataAccessException {
- return execute(new SimplePreparedStatementCreator(sql), action);
- }
public Object execute(String sql, PreparedStatementCallback action) throws DataAccessException {
return execute(new SimplePreparedStatementCreator(sql), action);
}
然后通過對(duì)實(shí)現(xiàn)PreparedStatementCallback接口的AbstractLobCreatingPreparedStatementCallback的回調(diào)函數(shù)來完成:
- 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;
public final Object doInPreparedStatement(PreparedStatement ps) throws SQLException, DataAccessException {
LobCreator lobCreator = this.lobHandler.getLobCreator();
try {
//這是一個(gè)模板方法,具體需要由客戶程序?qū)崿F(xiàn)
setValues(ps, lobCreator);
return new Integer(ps.executeUpdate());
}
finally {
lobCreator.close();
}
}
//定義的需要客戶程序?qū)崿F(xiàn)的虛函數(shù)
protected abstract void setValues(PreparedStatement ps, LobCreator lobCreator)
throws SQLException, DataAccessException;
而我們注意到setValues()是一個(gè)需要實(shí)現(xiàn)的抽象方法,應(yīng)用程序通過實(shí)現(xiàn)setValues來定義自己的操作 - 在setValues中調(diào)用lobCreator.setBlobAsBinaryStrem()。讓我們看看具體的BLOB操作在LobCreator是怎樣完成的,我們一般使用DefaultLobCreator作為BLOB操作的驅(qū)動(dòng):
- public void setBlobAsBinaryStream(
- PreparedStatement ps, int paramIndex, InputStream binaryStream, int contentLength)
- throws SQLException {
-
- ps.setBinaryStream(paramIndex, binaryStream, contentLength);
- ........
- }
public void setBlobAsBinaryStream(
PreparedStatement ps, int paramIndex, InputStream binaryStream, int contentLength)
throws SQLException {
//通過JDBC來完成對(duì)BLOB數(shù)據(jù)的操作,對(duì)Oracle,Spring提供了OracleLobHandler來支持BLOB操作。
ps.setBinaryStream(paramIndex, binaryStream, contentLength);
........
}
上面提到的是零零碎碎的Spring JDBC使用的例子,可以看到使用Spring JDBC可以幫助我們完成許多數(shù)據(jù)庫(kù)的操作。Spring對(duì)數(shù)據(jù)庫(kù)操作最基本的服務(wù)是通過JdbcTeamplate和他常用的回調(diào)函數(shù)來實(shí)現(xiàn)的,在此之上,又提供了許多RMDB的操作來幫助我們更便利的對(duì)數(shù)據(jù)庫(kù)的數(shù)據(jù)進(jìn)行操作 - 注意這里沒有引入向Hibernate這樣的O/R方案。對(duì)這些O/R方案的支持,Spring由其他包來完成服務(wù)。
書中還提到關(guān)于execute和update方法之間的區(qū)別,update方法返回的是受影響的記錄數(shù)目的一個(gè)計(jì)數(shù),并且如果傳入?yún)?shù)的話,使用的是java.sql.PreparedStatement,而execute方法總是使用 java.sql.Statement,不接受參數(shù),而且他不返回受影響記錄的計(jì)數(shù),更適合于創(chuàng)建和丟棄表的語句,而update方法更適合于插入,更新和刪除操作,這也是我們?cè)谑褂脮r(shí)需要注意的。