返回介绍

8.2.1 基础方法 execute

发布于 2025-04-22 22:09:15 字数 7511 浏览 0 评论 0 收藏

execute 作为数据库操作的核心入口,将大多数数据库操作相同的步骤统一封装,而将个性化的操作使用参数 PreparedStatementCallback 进行回调。

public <T> T execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action)

throws DataAccessException {

  Assert.notNull(psc, "PreparedStatementCreator must not be null");

  Assert.notNull(action, "Callback object must not be null");

  if (logger.isDebugEnabled()) {

   String sql = getSql(psc);

   logger.debug("Executing prepared SQL statement" + (sql != null ? " [" + sql + "]" :

  ""));

 }

 //获取数据库连接

  Connection con = DataSourceUtils.getConnection(getDataSource());

  PreparedStatement ps = null;

  try {

   Connection conToUse = con;

   if (this.nativeJdbcExtractor != null &&

  this.nativeJdbcExtractor.isNativeConnectionNecessaryForNative

   PreparedStatements()) {

    conToUse = this.nativeJdbcExtractor.getNativeConnection(con);

  }

   ps = psc.createPreparedStatement(conToUse);

  //应用用户设定的输入参数

  applyStatementSettings(ps);

   PreparedStatement psToUse = ps;

   if (this.nativeJdbcExtractor != null) {

    psToUse = this.nativeJdbcExtractor.getNativePreparedStatement(ps);

  }

  //调用回调函数

   T result = action.doInPreparedStatement(psToUse);

  handleWarnings(ps);

   return result;

 }

  catch (SQLException ex) {

  //释放数据库连接避免当 异常转换器没有被初始化的时候出现潜在的连接池死锁

   if (psc instanceof ParameterDisposer) {

    ((ParameterDisposer) psc).cleanupParameters();

  }

   String sql = getSql(psc);

   psc = null;

  JdbcUtils.closeStatement(ps);

   ps = null;

   DataSourceUtils.releaseConnection(con, getDataSource());

   con = null;

   throw getExceptionTranslator().translate("PreparedStatementCallback", sql, ex);

 }

  finally {

   if (psc instanceof ParameterDisposer) {

    ((ParameterDisposer) psc).cleanupParameters();

  }

  JdbcUtils.closeStatement(ps);

   DataSourceUtils.releaseConnection(con, getDataSource());

 }

}

以上方法对常用操作进行了封装,包括如下几项内容。

1.获取数据库连接

获取数据库连接也并非直接使用 dataSource.getConnection() 方法那么简单,同样也考虑了诸多情况。

public static Connection doGetConnection(DataSource dataSource) throws SQLException {

  Assert.notNull(dataSource, "No DataSource specified");

  ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronization

 Manager.getResource(dataSource);

  if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronized

  WithTransaction())) {

  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;

}

在数据库连接方面,Spring 主要考虑的是关于事务方面的处理。基于事务处理的特殊性, Spring 需要保证线程中的数据库操作都是使用同一个事务连接。

2.应用用户设定的输入参数

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());

}

setFetchSize 最主要是为了减少网络交互次数设计的。访问 ResultSet 时,如果它每次只从服务器上读取一行数据,则会产生大量的开销。setFetchSize 的意思是当调用 rs.next 时,ResultSet 会一次性从服务器上取得多少行数据回来,这样在下次 rs.next 时,它可以直接从内存中获取数据而不需要网络交互,提高了效率。 这个设置可能会被某些 JDBC 驱动忽略,而且设置过大也会造成内存的上升。

setMaxRows 将此 Statement 对象生成的所有 ResultSet 对象可以包含的最大行数限制设置为给定数。

3.调用回调函数

处理一些通用方法外的个性化处理,也就是 PreparedStatementCallback 类型的参数的 doInPreparedStatement 方法的回调。

4.警告处理

protected void handleWarnings(Statement stmt) throws SQLException {

 //当设置为忽略警告时只尝试打印日志

  if (isIgnoreWarnings()) {

   if (logger.isDebugEnabled()) {

   //如果日志开启的情况下打印日志

    SQLWarning warningToLog = stmt.getWarnings();

    while (warningToLog != null) {

     logger.debug("SQLWarning ignored: SQL state '" + warningToLog.

     getSQLState() + "', error code '" +

     warningToLog.getErrorCode() + "', message [" + warningToLog.

     getMessage() + "]");

     warningToLog = warningToLog.getNextWarning();

   }

  }

 }

  else {

  handleWarnings(stmt.getWarnings());

 }

}

这里用到了一个类 SQLWarning,SQLWarning 提供关于数据库访问警告信息的异常。这些警告直接链接到导致报告警告的方法所在的对象。警告可以从 Connection、Statement 和 ResultSet 对象中获得。试图在已经关闭的连接上获取警告将导致抛出异常。类似地,试图在已经关闭的语句上或已经关闭的结果集上获取警告也将导致抛出异常。注意,关闭语句时还会关闭它可能生成的结果集。

很多人不是很理解什么情况下会产生警告而不是异常,在这里给读者提示个最常见的警告 DataTruncation:DataTruncation 直接继承 SQLWarning,由于某种原因意外地截断数据值时会以 DataTruncation 警告形式报告异常。

对于警告的处理方式并不是直接抛出异常,出现警告很可能会出现数据错误,但是,并不一定会影响程序执行,所以用户可以自己设置处理警告的方式,如默认的是忽略警告,当出现警告时只打印警告日志,而另一种方式只直接抛出异常。

5.资源释放

数据库的连接释放并不是直接调用了 Connection 的 API 中的 close 方法。考虑到存在事务的情况,如果当前线程存在事务,那么说明在当前线程中存在共用数据库连接,这种情况下直接使用 ConnectionHolder 中的 released 方法进行连接数减一,而不是真正的释放连接。

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);

 }

}

  public static void doReleaseConnection(Connection con, DataSource dataSource) throws

  SQLException {

  if (con == null) {

  return;

 }

  if (dataSource != null) {

  //当前线程存在事务的情况下说明存在共用数据库连接直接使用 ConnectionHolder 中的

  released 方法进行连接数减一而不是真正的释放连接。

   ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronization

  Manager.getResource(dataSource);

   if (conHolder != null && connectionEquals(conHolder, con)) {

    // It's the transactional Connection: Don't close it.

   conHolder.released();

   return;

  }

 }

if (!(dataSource instanceof SmartDataSource) || ((SmartDataSource) dataSource). shouldClose(con)) {

   logger.debug("Returning JDBC Connection to DataSource");

  con.close();

 }

}

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。