如何获取PreparedStatement的SQL?

192

我有一个通用的Java方法,具有以下方法签名:

private static ResultSet runSQLResultSet(String sql, Object... queryParams)
它打开一个连接,使用sql语句和queryParams变量长度数组中的参数构建一个PreparedStatement,运行它,将ResultSet(在CachedRowSetImpl中缓存)保存,关闭连接,并返回缓存的结果集。
我在这个方法中有异常处理,记录错误日志。由于SQL语句对于调试非常有用,因此我将SQL语句作为日志的一部分进行记录。我的问题是,记录String变量sql会记录带有?的模板语句而不是实际值。我想记录被执行(或尝试执行)的实际语句。
那么... 有没有办法获取将由PreparedStatement运行的实际SQL语句? (不必自己构建。如果我找不到访问PreparedStatement SQL的方法,我最终可能会在我的catch块中自己构建它。)

1
如果你正在编写直接的JDBC代码,我强烈建议你看看Apache commons-dbutils http://commons.apache.org/dbutils/。它可以大大简化JDBC代码。 - Ken Liu
4
好的,我会尽力为您进行翻译。以下是需要翻译的内容:Dupe: https://dev59.com/TnVC5IYBdhLWcg3wqzLV - Pascal Thivent
15个回答

0
如果你正在使用MySQL,你可以使用MySQL的查询日志来记录查询。我不知道其他供应商是否提供这个功能,但很有可能他们也会提供。

-1

我正在使用Oracle 11g,但无法从PreparedStatement中获取最终的SQL语句。在阅读了@Pascal MARTIN的答案后,我明白了原因。

我放弃了使用PreparedStatement的想法,改用了一个适合我的需求的简单文本格式化程序。以下是我的示例:

//I jump to the point after connexion has been made ...
java.sql.Statement stmt = cnx.createStatement();
String sqlTemplate = "SELECT * FROM Users WHERE Id IN ({0})";
String sqlInParam = "21,34,3434,32"; //some random ids
String sqlFinalSql = java.text.MesssageFormat(sqlTemplate,sqlInParam);
System.out.println("SQL : " + sqlFinalSql);
rsRes = stmt.executeQuery(sqlFinalSql);

你发现sqlInParam可以在一个(for,while)循环中动态构建,我只是简单地使用MessageFormat类来作为SQL查询的字符串模板格式化器。

5
这会彻底破坏使用预处理语句的原因,如避免 SQL 注入和提高性能。 - ticktock
2
我完全同意你的观点。我应该明确说明,我编写这段代码最多只能执行几次,用于大规模数据集成,并迫切需要一种快速的方式来输出一些日志,而不必涉及整个log4j框架,因为这对我所需的功能来说过于臃肿了。这段代码不应该用于生产环境 :-) - Diego Tercero
这对我使用Oracle驱动程序有效(但不包括参数): ((OraclePreparedStatementWrapper) myPreparedStatement).getOriginalSql() - latj
@latj它提供了没有绑定变量的原始SQL。我们需要带有绑定变量值的SQL。 - Anu

-1

你可以尝试使用 javaagent 来打印 SQL:


public class Main {

    private static final String mybatisPath = "org.apache.ibatis.executor.statement.PreparedStatementHandler";
    private static final String mybatisMethod = "parameterize";
    private static final String sqlPath = "java.sql.Statement";


    public static void premain(String arg, Instrumentation instrumentation) {

        instrumentation.addTransformer(new ClassFileTransformer() {
            @Override
            public byte[] transform(
                    ClassLoader loader,
                    String className,
                    Class<?> classBeingRedefined,
                    ProtectionDomain protectionDomain,
                    byte[] classfileBuffer) throws IllegalClassFormatException {

                if (!mybatisPath.replaceAll("\\.", "/").equals(className)) {
                    return null;
                }
                ClassPool pool = new ClassPool();
                pool.appendClassPath(new LoaderClassPath(loader));
                pool.appendSystemPath();

                try {
                    CtClass ctClass = pool.get(mybatisPath);
                    CtMethod method = ctClass.getDeclaredMethod(mybatisMethod, new CtClass[]{pool.get(sqlPath)});
                    method.insertAfter("cn.wjhub.Main#printSQL($1)");

                    return ctClass.toBytecode();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return null;
            }
        });
    }

    /**
     * printSQL
     *
     * @param statement statement
     */
    private void printSQL(Statement statement) {
        String sqlSource = statement.toString();
        System.out.println(sqlSource);
    }
}

这已经被其他答案提出过了,而且这并不适用于所有的JDBC驱动程序。 - Mark Rotteveel

-2

简单函数:

public static String getSQL (Statement stmt){
    String tempSQL = stmt.toString();

    //please cut everything before sql from statement
    //javadb...: 
    int i1 = tempSQL.indexOf(":")+2;
    tempSQL = tempSQL.substring(i1);

    return tempSQL;
}

对于preparedStatement也可以。


这只是一个带有几行额外代码来愚弄不熟练用户的.toString(),而且早已被解决了。 - Pere

-5
要做到这一点,您需要一个支持在低级别记录SQL的JDBC连接和/或驱动程序。
请查看log4jdbc

5
看一下log4jdbc,然后怎么办?你该如何使用它?你去那个网站看到一些与该项目有关的随意胡说八道的内容,没有清晰的实际使用技术的示例。 - Hooli

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接