欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

利用JDBC的PrepareStatement打印真实SQL的方法详解

程序员文章站 2024-02-12 13:05:16
前言 本文主要给大家介绍了关于利用jdbc的preparestatement打印真实sql的相关内容,分享出来供大家参考学习,下面来一起看看详细的介绍: 我们知道,jd...

前言

本文主要给大家介绍了关于利用jdbc的preparestatement打印真实sql的相关内容,分享出来供大家参考学习,下面来一起看看详细的介绍:

我们知道,jdbc 的 preparestatement 优点多多,通常都是推荐使用 preparestatement 而不是其基类 statment。preparestatement 支持 ? 占位符,可以将参数按照类型转自动换为真实的值。既然这一过程是自动的,封装在 jdbc 内部的,那么我们外部就不得而知目标的 sql 最终生成怎么样——于是在调试过程中便有一个打印 sql 的问题。我们对 preparestatement 传入 sql 语句,如 select * from table where id = ?,然后我们传入对应的 id 参数,假设是 id = 10,那怎么把得到参数的 select * from table where id =  12 结果完整地得出来呢?——这便是本文所要探讨的问题。下面话不多说了,来一起看看详细的介绍:

方法如下:

首先,我们看看典型的一个 preparestatement 调用方法,如下一个函数,

/** 
 * 查询单个结果,保存为 map<string, object> 结构。如果查询不到任何数据返回 null。 
 * 
 * @param conn 
 *   数据库连接对象 
 * @param sql 
 *   sql 语句,可以带有 ? 的占位符 
 * @param params 
 *   插入到 sql 中的参数,可单个可多个可不填 
 * @return map<string, object> 结构的结果。如果查询不到任何数据返回 null。 
 */ 
public static map<string, object> query(connection conn, string sql, object... params) { 
 map<string, object> map = null; 
 printrealsql(sql, params); // 打印真实 sql 的函数 
  
 try (preparedstatement ps = conn.preparestatement(sql);) { 
  if(params != null) 
   for (int i = 0; i < params.length; i++) 
    ps.setobject(i + 1, params[i]); 
   
  try (resultset rs = ps.executequery();) { 
   if (rs.isbeforefirst()) { 
    map = getresultmap(rs); 
   } else { 
    logger.info("查询 sql:{0} 没有符合的记录!", sql); 
   } 
  } 
 } catch (sqlexception e) { 
  logger.warning(e); 
 } 
  
 return map; 
} 

值得注意该函数里面:

printrealsql(sql, params); // 打印真实 sql 的函数 

其参数一 sql 就是类似 select * from table where id = ? 的语句,参数二 params 为 object... params 的参数列表,可以是任意类似的合法 sql 值。最后,通过 printrealsql 函数最终得出形如 select * from table where id =  12 的结果。

printrealsql 函数源码如下:

/** 
 * 在开发过程,sql语句有可能写错,如果能把运行时出错的 sql 语句直接打印出来,那对排错非常方便,因为其可以直接拷贝到数据库客户端进行调试。 
 * 
 * @param sql 
 *   sql 语句,可以带有 ? 的占位符 
 * @param params 
 *   插入到 sql 中的参数,可单个可多个可不填 
 * @return 实际 sql 语句 
 */ 
public static string printrealsql(string sql, object[] params) { 
 if(params == null || params.length == 0) { 
  logger.info("the sql is------------>\n" + sql); 
  return sql; 
 } 
  
 if (!match(sql, params)) { 
  logger.info("sql 语句中的占位符与参数个数不匹配。sql:" + sql); 
  return null; 
 } 
 
 int cols = params.length; 
 object[] values = new object[cols]; 
 system.arraycopy(params, 0, values, 0, cols); 
 
 for (int i = 0; i < cols; i++) { 
  object value = values[i]; 
  if (value instanceof date) { 
   values[i] = "'" + value + "'"; 
  } else if (value instanceof string) { 
   values[i] = "'" + value + "'"; 
  } else if (value instanceof boolean) { 
   values[i] = (boolean) value ? 1 : 0; 
  } 
 } 
  
 string statement = string.format(sql.replaceall("\\?", "%s"), values); 
 
 logger.info("the sql is------------>\n" + statement); 
 
 connectionmgr.addsql(statement); // 用来保存日志 
  
 return statement; 
} 
 
/** 
 * ? 和参数的实际个数是否匹配 
 * 
 * @param sql 
 *   sql 语句,可以带有 ? 的占位符 
 * @param params 
 *   插入到 sql 中的参数,可单个可多个可不填 
 * @return true 表示为 ? 和参数的实际个数匹配 
 */ 
private static boolean match(string sql, object[] params) { 
 if(params == null || params.length == 0) return true; // 没有参数,完整输出 
  
 matcher m = pattern.compile("(\\?)").matcher(sql); 
 int count = 0; 
 while (m.find()) { 
  count++; 
 } 
  
 return count == params.length; 
} 

可见,上述思路是非常简单的,——有多少个 ? 占位符,就要求有多少个参数,然后一一对照填入(数组)。match 函数会检查第一个步骤,检查个数是否匹配,否则会返回“sql 语句中的占位符与参数个数不匹配”的提示;然后,参数的值会被转换为符合 sql 值所要求的类型;最后,就是将 sql 一一填入,——此处使用了一个字符串的技巧,先把 ? 字符通通转换为 %s,——那是 string.format 可识别的占位符,如此再传入 object[] 参数列表,即可得出我们期待的 sql 结果。

我们不能保证那 sql 可以直接放到数据库中被解析。因为我们的初衷只是把 sql 打印出来,务求更近一步让程序员在开发阶段看到 sql 是怎么样子的,而且不是一堆 ?、?……,这样会显得更符合真实情形一点。

preparestatement 内部源码肯定有这一步骤或者某个变量是表示那个真实 sql 的,——只是没有暴露出来。如果有,那么对程序员会更友好一些。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对的支持。