如何从PreparedStatement获取参数?

24

我正在编写通用的SQLException记录器,我想获取传递到PreparedStatement中的参数,该如何操作?我已经能够获得它们的数量。

ParameterMetaData metaData = query.getParameterMetaData();
parameterCount = metaData.getParameterCount();
3个回答

23

简短回答:你无法获取 JDBC 驱动程序中的参数值。

详细回答:所有 JDBC 驱动程序都会保存参数值,但没有标准方法可以获取它们。

如果您想为调试或类似目的打印它们,有几个选项:

  1. 创建一个透传 JDBC 驱动程序(以 p6spy 或 log4jdbc 为基础),它会保留参数的副本并提供一个公共 API 来读取它们。

  2. 使用 Java 反射 API(Field.setAccessible(true) 是你的朋友)来读取 JDBC 驱动程序的私有数据结构。这是我首选的方法。我有一个工厂,它委托给特定于数据库的实现,可以解码参数,并允许我通过 getObject(int column) 读取参数。

  3. 提交错误报告并要求改进异常处理。特别是 Oracle 在告诉你出了什么问题方面非常吝啬。


9

解决方案1:子类化

只需要创建一个自定义的PreparedStatement实现,将所有调用委托给原始预备语句,仅在setObject等方法中添加回调即可。例如:

public PreparedStatement prepareStatement(String sql) {
        final PreparedStatement delegate = conn.prepareStatement(sql);
        return new PreparedStatement() {
            // TODO: much more methods to delegate

            @Override
            public void setString(int parameterIndex, String x) throws SQLException {
                // TODO: remember value of X
                delegate.setString(parameterIndex, x);
            }
        };
    }

如果你想保存参数并在以后获取它们,有许多解决方案,但我更喜欢创建一个新类,比如ParameterAwarePreparedStatement,其中参数保存在一个映射中。结构可以类似于这样:

public class ParameterAwarePreparedStatement implements PreparedStatement {
    private final PreparedStatement delegate;
    private final Map<Integer,Object> parameters;

    public ParameterAwarePreparedStatement(PreparedStatement delegate) {
        this.delegate = delegate;
        this.parameters = new HashMap<>();
    }

    public Map<Integer,Object> getParameters() {
        return Collections.unmodifiableMap(parameters);
    }

    // TODO: many methods to delegate

    @Override
    public void setString(int parameterIndex, String x) throws SQLException {
        delegate.setString(parameterIndex, x);
        parameters.put(parameterIndex, x);
    }
}

解决方案 2:动态代理

第二个解决方案更加简短,但似乎有些hacky。

你可以通过调用java.lang.reflect.Proxy上的工厂方法创建一个动态代理,并将所有调用委派给原始实例。例如:

public PreparedStatement prepareStatement(String sql) {
    final PreparedStatement ps = conn.prepareStatement(sql);
    final PreparedStatement psProxy = (PreparedStatement) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class<?>[]{PreparedStatement.class}, new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if (method.getName().equals("setLong")) {
                // ... your code here ...
            }
            // this invokes the default call
            return method.invoke(ps, args);
        }
    });
    return psProxy;
}

然后通过查看方法名称和查找第二个方法参数来拦截 setObject 等调用以获取您的值。


1

本文来自Boulder,虽然涉及DB 2 "specific",但却提供了ParameterMetadata用法的完整示例。


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