spring jdbctemplate源码跟踪
2015-06-07 15:46
405 查看
闲着没事,看看源码也是一种乐趣!
java操作数据库的基本步骤都是类似的:
1. 建立数据库连接
2. 创建Connection
3. 创建statement或者preparedStateement
4. 执行sql,返回ResultSet
5. 关闭resultSet
5.关闭statement
6.关闭Connection
Spring对数据库的操作在jdbc上面做了深层次的封装,使用spring的注入功能,可以把DataSource注册到JdbcTemplate之中。
1. 构造函数,三种形式
一种思路:将datasource注入到JdbcTemplate。
2.获取Connection
Connection的获取方式:
2.1. 若是datasource直接使用jdbc而没有使用诸如c3p0,dbcp等第三方插件时则从下面的方法获取:
ConnectionHolder间接继承了ResourceHolder接口,ResourceHolder接口允许spring的事务基础可以在必要时检索和重置。通常我们只要继承ResourceHolderSupport即可。
2.2. 若使用了第三方插件时,则需要从插件中提取Connection。
第三方插件的关系如下:
其具体方法如下:
具体的实现在其子类里面,以c3p0为例:
调用C3P0 API获取Connection。
3. 创建Statement
3.1 不使用第三方插件
设置fetch size, max rows, and query timeout.其中过期时间在datasourceUtils中实现。
3.2 第三方插件
以c3p0为例
4. 执行statement并关闭之
4.1 不使用第三方插件
4.2 使用第三方插件
4.3 提取数据
或者
4.4 关闭ResultSet
5. 释放connection
6.事务处理
6.1 事务开始
DataSourceTransactionManager 的doBegin()方法
6.2 事务提交
6.3 事务回滚
事务过程:
Spring 对DataSource进行事务管理的关键在于ConnectionHolder和TransactionSynchronizationManager。
0.先从TransactionSynchronizationManager中尝试获取连接
1.如果前一步失败则在每个线程上,对每个DataSouce只创建一个Connection
2.这个Connection用ConnectionHolder包装起来,由TransactionSynchronizationManager管理
3.再次请求同一个连接的时候,从TransactionSynchronizationManager返回已经创建的ConnectionHolder,然后调用ConnectionHolder的request将引用计数+1
4.释放连接时要调用ConnectionHolder的released,将引用计数-1
5.当事物完成后,将ConnectionHolder从TransactionSynchronizationManager中解除。当谁都不用,这个连接被close
以上所有都是可以调用DataSourceUtils化简代码,而JdbcTemplate又是调用DataSourceUtils的。所以在 Spring文档中要求尽量首先使用JdbcTemplate,其次是用DataSourceUtils来获取和释放连接。至于 TransactionAwareDataSourceProxy,那是下策的下策。不过可以将Spring事务管理和遗留代码无缝集成。
所以如某位朋友说要使用Spring的事务管理,但是又不想用JdbcTemplate,那么可以考虑TransactionAwareDataSourceProxy。这个类是原来DataSource的代理。
其次,想使用Spring事物,又不想对Spring进行依赖是不可能的。与其试图自己模拟DataSourceUtils,不如直接使用现成的。
小结:
参考文献:
【1】http://blog.sina.com.cn/s/blog_53dd74430100haaj.html
java操作数据库的基本步骤都是类似的:
1. 建立数据库连接
2. 创建Connection
3. 创建statement或者preparedStateement
4. 执行sql,返回ResultSet
5. 关闭resultSet
5.关闭statement
6.关闭Connection
Spring对数据库的操作在jdbc上面做了深层次的封装,使用spring的注入功能,可以把DataSource注册到JdbcTemplate之中。
1. 构造函数,三种形式
/** * Construct a new JdbcTemplate for bean usage. * <p>Note: The DataSource has to be set before using the instance. * @see #setDataSource */ public JdbcTemplate() { } /** * Construct a new JdbcTemplate, given a DataSource to obtain connections from. * <p>Note: This will not trigger initialization of the exception translator. * @param dataSource the JDBC DataSource to obtain connections from */ public JdbcTemplate(DataSource dataSource) { setDataSource(dataSource); afterPropertiesSet(); } /** * Construct a new JdbcTemplate, given a DataSource to obtain connections from. * <p>Note: Depending on the "lazyInit" flag, initialization of the exception translator * will be triggered. * @param dataSource the JDBC DataSource to obtain connections from * @param lazyInit whether to lazily initialize the SQLExceptionTranslator */ public JdbcTemplate(DataSource dataSource, boolean lazyInit) { setDataSource(dataSource); setLazyInit(lazyInit); afterPropertiesSet(); }
一种思路:将datasource注入到JdbcTemplate。
2.获取Connection
//------------------------------------------------------------------------- // Methods dealing with static SQL (java.sql.Statement) //------------------------------------------------------------------------- @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); } 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. 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()); } }
Connection的获取方式:
2.1. 若是datasource直接使用jdbc而没有使用诸如c3p0,dbcp等第三方插件时则从下面的方法获取:
Connection con = DataSourceUtils.getConnection(getDataSource());
/** * Actually obtain a JDBC Connection from the given DataSource. * Same as {@link #getConnection}, but throwing the original SQLException. * <p>Is aware of a corresponding Connection bound to the current thread, for example * when using {@link DataSourceTransactionManager}. Will bind a Connection to the thread * if transaction synchronization is active (e.g. if in a JTA transaction). * <p>Directly accessed by {@link TransactionAwareDataSourceProxy}. * @param dataSource the DataSource to obtain Connections from * @return a JDBC Connection from the given DataSource * @throws SQLException if thrown by JDBC methods * @see #doReleaseConnection */ 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; }
ConnectionHolder间接继承了ResourceHolder接口,ResourceHolder接口允许spring的事务基础可以在必要时检索和重置。通常我们只要继承ResourceHolderSupport即可。
2.2. 若使用了第三方插件时,则需要从插件中提取Connection。
conToUse = this.nativeJdbcExtractor.getNativeConnection(con);
第三方插件的关系如下:
其具体方法如下:
/** * Check for a ConnectionProxy chain, then delegate to doGetNativeConnection. * <p>ConnectionProxy is used by Spring's TransactionAwareDataSourceProxy * and LazyConnectionDataSourceProxy. The target connection behind it is * typically one from a local connection pool, to be unwrapped by the * doGetNativeConnection implementation of a concrete subclass. * @see #doGetNativeConnection * @see org.springframework.jdbc.datasource.ConnectionProxy * @see org.springframework.jdbc.datasource.DataSourceUtils#getTargetConnection * @see org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy * @see org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy */ @Override public Connection getNativeConnection(Connection con) throws SQLException { if (con == null) { return null; } Connection targetCon = DataSourceUtils.getTargetConnection(con); Connection nativeCon = doGetNativeConnection(targetCon); if (nativeCon == targetCon) { // We haven't received a different Connection, so we'll assume that there's // some additional proxying going on. Let's check whether we get something // different back from the DatabaseMetaData.getConnection() call. DatabaseMetaData metaData = targetCon.getMetaData(); // The following check is only really there for mock Connections // which might not carry a DatabaseMetaData instance. if (metaData != null) { Connection metaCon = metaData.getConnection(); if (metaCon != null && metaCon != targetCon) { // We've received a different Connection there: // Let's retry the native extraction process with it. nativeCon = doGetNativeConnection(metaCon); } } } return nativeCon; }
具体的实现在其子类里面,以c3p0为例:
/** * Retrieve the Connection via C3P0's {@code rawConnectionOperation} API, * using the {@code getRawConnection} as callback to get access to the * raw Connection (which is otherwise not directly supported by C3P0). * @see #getRawConnection */ @Override protected Connection doGetNativeConnection(Connection con) throws SQLException { if (con instanceof C3P0ProxyConnection) { C3P0ProxyConnection cpCon = (C3P0ProxyConnection) con; try { return (Connection) cpCon.rawConnectionOperation( this.getRawConnectionMethod, null, new Object[] {C3P0ProxyConnection.RAW_CONNECTION}); } catch (SQLException ex) { throw ex; } catch (Exception ex) { ReflectionUtils.handleReflectionException(ex); } } return con; }
调用C3P0 API获取Connection。
3. 创建Statement
stmt = conToUse.createStatement(); applyStatementSettings(stmt); Statement stmtToUse = stmt; if (this.nativeJdbcExtractor != null) { stmtToUse = this.nativeJdbcExtractor.getNativeStatement(stmt); }
3.1 不使用第三方插件
/** * Prepare the given JDBC Statement (or PreparedStatement or CallableStatement), * applying statement settings such as fetch size, max rows, and query timeout. * @param stmt the JDBC Statement to prepare * @throws SQLException if thrown by JDBC API * @see #setFetchSize * @see #setMaxRows * @see #setQueryTimeout * @see org.springframework.jdbc.datasource.DataSourceUtils#applyTransactionTimeout */ protected void applyStatementSettings(Statement stmt) throws SQLException { int fetchSize = getFetchSize(); if (fetchSize > 0) { stmt.setFetchSize(fetchSize); } int maxRows = getMaxRows(); if (maxRows > 0) { stmt.setMaxRows(maxRows); } DataSourceUtils.applyTimeout(stmt, getDataSource(), getQueryTimeout()); }
设置fetch size, max rows, and query timeout.其中过期时间在datasourceUtils中实现。
/** * Apply the specified timeout - overridden by the current transaction timeout, * if any - to the given JDBC Statement object. * @param stmt the JDBC Statement object * @param dataSource the DataSource that the Connection was obtained from * @param timeout the timeout to apply (or 0 for no timeout outside of a transaction) * @throws SQLException if thrown by JDBC methods * @see java.sql.Statement#setQueryTimeout */ public static void applyTimeout(Statement stmt, DataSource dataSource, int timeout) throws SQLException { Assert.notNull(stmt, "No Statement specified"); Assert.notNull(dataSource, "No DataSource specified"); ConnectionHolder holder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource); if (holder != null && holder.hasTimeout()) { // Remaining transaction timeout overrides specified value. stmt.setQueryTimeout(holder.getTimeToLiveInSeconds()); } else if (timeout > 0) { // No current transaction timeout -> apply specified value. stmt.setQueryTimeout(timeout); } }
3.2 第三方插件
以c3p0为例
/** * Extracts the innermost delegate from the given Commons DBCP object. * Falls back to the given object if no underlying object found. * @param obj the Commons DBCP Connection/Statement/ResultSet * @return the underlying native Connection/Statement/ResultSet */ private static Object getInnermostDelegate(Object obj) throws SQLException { if (obj == null) { return null; } try { Class<?> classToAnalyze = obj.getClass(); while (!Modifier.isPublic(classToAnalyze.getModifiers())) { classToAnalyze = classToAnalyze.getSuperclass(); if (classToAnalyze == null) { // No public provider class found -> fall back to given object. return obj; } } Method getInnermostDelegate = classToAnalyze.getMethod(GET_INNERMOST_DELEGATE_METHOD_NAME, (Class[]) null); Object delegate = ReflectionUtils.invokeJdbcMethod(getInnermostDelegate, obj); return (delegate != null ? delegate : obj); } catch (NoSuchMethodException ex) { return obj; } catch (SecurityException ex) { throw new IllegalStateException("Commons DBCP getInnermostDelegate method is not accessible: " + ex); } }
4. 执行statement并关闭之
T result = action.doInStatement(stmtToUse);
class QueryStatementCallback implements StatementCallback<T>, SqlProvider { @Override public T doInStatement(Statement stmt) throws SQLException { ResultSet rs = null; try { rs = stmt.executeQuery(sql); ResultSet rsToUse = rs; if (nativeJdbcExtractor != null) { rsToUse = nativeJdbcExtractor.getNativeResultSet(rs); } return rse.extractData(rsToUse); } finally { JdbcUtils.closeResultSet(rs); } } @Override public String getSql() { return sql; } }
4.1 不使用第三方插件
rs = stmt.executeQuery(sql);
4.2 使用第三方插件
private static final String GET_INNERMOST_DELEGATE_METHOD_NAME = "getInnermostDelegate"; /** * Extracts the innermost delegate from the given Commons DBCP object. * Falls back to the given object if no underlying object found. * @param obj the Commons DBCP Connection/Statement/ResultSet * @return the underlying native Connection/Statement/ResultSet */ private static Object getInnermostDelegate(Object obj) throws SQLException { if (obj == null) { return null; } try { Class<?> classToAnalyze = obj.getClass(); while (!Modifier.isPublic(classToAnalyze.getModifiers())) { classToAnalyze = classToAnalyze.getSuperclass(); if (classToAnalyze == null) { // No public provider class found -> fall back to given object. return obj; } } Method getInnermostDelegate = classToAnalyze.getMethod(GET_INNERMOST_DELEGATE_METHOD_NAME, (Class[]) null); Object delegate = ReflectionUtils.invokeJdbcMethod(getInnermostDelegate, obj); return (delegate != null ? delegate : obj); } catch (NoSuchMethodException ex) { return obj; } catch (SecurityException ex) { throw new IllegalStateException("Commons DBCP getInnermostDelegate method is not accessible: " + ex); } }
4.3 提取数据
@Override public List<T> extractData(ResultSet rs) throws SQLException { List<T> results = (this.rowsExpected > 0 ? new ArrayList<T>(this.rowsExpected) : new ArrayList<T>()); int rowNum = 0; while (rs.next()) { results.add(this.rowMapper.mapRow(rs, rowNum++)); } return results; }
或者
@Override public SqlRowSet extractData(ResultSet rs) throws SQLException { return createSqlRowSet(rs); } /** * Create a SqlRowSet that wraps the given ResultSet, * representing its data in a disconnected fashion. * <p>This implementation creates a Spring ResultSetWrappingSqlRowSet * instance that wraps a standard JDBC CachedRowSet instance. * Can be overridden to use a different implementation. * @param rs the original ResultSet (connected) * @return the disconnected SqlRowSet * @throws SQLException if thrown by JDBC methods * @see #newCachedRowSet * @see org.springframework.jdbc.support.rowset.ResultSetWrappingSqlRowSet */ protected SqlRowSet createSqlRowSet(ResultSet rs) throws SQLException { CachedRowSet rowSet = newCachedRowSet(); rowSet.populate(rs); return new ResultSetWrappingSqlRowSet(rowSet); }
4.4 关闭ResultSet
/** * Close the given JDBC ResultSet and ignore any thrown exception. * This is useful for typical finally blocks in manual JDBC code. * @param rs the JDBC ResultSet to close (may be {@code null}) */ public static void closeResultSet(ResultSet rs) { if (rs != null) { try { rs.close(); } catch (SQLException ex) { logger.trace("Could not close JDBC ResultSet", ex); } catch (Throwable ex) { // We don't trust the JDBC driver: It might throw RuntimeException or Error. logger.trace("Unexpected exception on closing JDBC ResultSet", ex); } } }
5. 释放connection
/** * Close the given Connection, obtained from the given DataSource, * if it is not managed externally (that is, not bound to the thread). * @param con the Connection to close if necessary * (if this is {@code null}, the call will be ignored) * @param dataSource the DataSource that the Connection was obtained from * (may be {@code null}) * @see #getConnection */ public static void releaseConnection(Connection con, DataSource dataSource) { try { doReleaseConnection(con, dataSource); } catch (SQLException ex) { logger.debug("Could not close JDBC Connection", ex); } catch (Throwable ex) { logger.debug("Unexpected exception on closing JDBC Connection", ex); } } /** * Actually close the given Connection, obtained from the given DataSource. * Same as {@link #releaseConnection}, but throwing the original SQLException. * <p>Directly accessed by {@link TransactionAwareDataSourceProxy}. * @param con the Connection to close if necessary * (if this is {@code null}, the call will be ignored) * @param dataSource the DataSource that the Connection was obtained from * (may be {@code null}) * @throws SQLException if thrown by JDBC methods * @see #doGetConnection */ public static void doReleaseConnection(Connection con, DataSource dataSource) throws SQLException { if (con == null) { return; } if (dataSource != null) { ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource); if (conHolder != null && connectionEquals(conHolder, con)) { // It's the transactional Connection: Don't close it. conHolder.released(); return; } } logger.debug("Returning JDBC Connection to DataSource"); doCloseConnection(con, dataSource); }
6.事务处理
6.1 事务开始
DataSourceTransactionManager 的doBegin()方法
/** * This implementation sets the isolation level but ignores the timeout. */ @Override protected void doBegin(Object transaction, TransactionDefinition definition) { DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction; Connection con = null; try { if (txObject.getConnectionHolder() == null || txObject.getConnectionHolder().isSynchronizedWithTransaction()) { Connection newCon = this.dataSource.getConnection(); if (logger.isDebugEnabled()) { logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction"); } txObject.setConnectionHolder(new ConnectionHolder(newCon), true); } txObject.getConnectionHolder().setSynchronizedWithTransaction(true); con = txObject.getConnectionHolder().getConnection(); Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition); txObject.setPreviousIsolationLevel(previousIsolationLevel); // Switch to manual commit if necessary. This is very expensive in some JDBC drivers, // so we don't want to do it unnecessarily (for example if we've explicitly // configured the connection pool to set it already). if (con.getAutoCommit()) { txObject.setMustRestoreAutoCommit(true); if (logger.isDebugEnabled()) { logger.debug("Switching JDBC Connection [" + con + "] to manual commit"); } con.setAutoCommit(false); } txObject.getConnectionHolder().setTransactionActive(true); int timeout = determineTimeout(definition); if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) { txObject.getConnectionHolder().setTimeoutInSeconds(timeout); } // Bind the session holder to the thread. if (txObject.isNewConnectionHolder()) { TransactionSynchronizationManager.bindResource(getDataSource(), txObject.getConnectionHolder()); } } catch (Throwable ex) { if (txObject.isNewConnectionHolder()) { DataSourceUtils.releaseConnection(con, this.dataSource); txObject.setConnectionHolder(null, false); } throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex); } }
6.2 事务提交
@Override protected void doCommit(DefaultTransactionStatus status) { DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction(); Connection con = txObject.getConnectionHolder().getConnection(); if (status.isDebug()) { logger.debug("Committing JDBC transaction on Connection [" + con + "]"); } try { con.commit(); } catch (SQLException ex) { throw new TransactionSystemException("Could not commit JDBC transaction", ex); } }
6.3 事务回滚
@Override protected void doRollback(DefaultTransactionStatus status) { DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction(); Connection con = txObject.getConnectionHolder().getConnection(); if (status.isDebug()) { logger.debug("Rolling back JDBC transaction on Connection [" + con + "]"); } try { con.rollback(); } catch (SQLException ex) { throw new TransactionSystemException("Could not roll back JDBC transaction", ex); } }
事务过程:
Spring 对DataSource进行事务管理的关键在于ConnectionHolder和TransactionSynchronizationManager。
0.先从TransactionSynchronizationManager中尝试获取连接
1.如果前一步失败则在每个线程上,对每个DataSouce只创建一个Connection
2.这个Connection用ConnectionHolder包装起来,由TransactionSynchronizationManager管理
3.再次请求同一个连接的时候,从TransactionSynchronizationManager返回已经创建的ConnectionHolder,然后调用ConnectionHolder的request将引用计数+1
4.释放连接时要调用ConnectionHolder的released,将引用计数-1
5.当事物完成后,将ConnectionHolder从TransactionSynchronizationManager中解除。当谁都不用,这个连接被close
以上所有都是可以调用DataSourceUtils化简代码,而JdbcTemplate又是调用DataSourceUtils的。所以在 Spring文档中要求尽量首先使用JdbcTemplate,其次是用DataSourceUtils来获取和释放连接。至于 TransactionAwareDataSourceProxy,那是下策的下策。不过可以将Spring事务管理和遗留代码无缝集成。
所以如某位朋友说要使用Spring的事务管理,但是又不想用JdbcTemplate,那么可以考虑TransactionAwareDataSourceProxy。这个类是原来DataSource的代理。
其次,想使用Spring事物,又不想对Spring进行依赖是不可能的。与其试图自己模拟DataSourceUtils,不如直接使用现成的。
小结:
JdbcTemplate将我们使用的JDBC的流程封装起来,包括了异常的捕捉、SQL的执行、查询结果的转换等等。 spring大量使用Template Method模式来封装固定流程的动作,XXXTemplate等类别都是基于这种方式的实现。 除了大量使用Template Method来封装一些底层的操作细节,spring也大量使用callback方式类回调相关类别的方法以提供JDBC相关类别的功能,使传统的JDBC的使用者也能清楚了解spring所提供的相关封装类别方法的使用。
参考文献:
【1】http://blog.sina.com.cn/s/blog_53dd74430100haaj.html
相关文章推荐
- JavaBean
- struts2 的 result type详解
- java spring aop 的代理的简单实现
- Eclipse mybatis中XML的自动提示
- java
- java反射机制学习笔记及例子代码
- Java初级笔记_2
- Java基础之包装类的自动装箱和拆箱详解
- java编程——吸血鬼数字(四位)
- Java命令行
- JDK自带工具jps,jstat,jmap,jconsole使用
- Java 爬虫
- java输入输出高速
- Java枚举类型
- Java Map 迭代
- eclipse Maven 配置
- Struts2中的ModelDriven机制及其运用
- ubuntu下安装配置JDK
- Windows在结构Eclipse+Android4.0开发环境
- springMVC学习笔记汇总(持续更新)