我正在编写通用的SQLException记录器,我想获取传递到PreparedStatement中的参数,该如何操作?我已经能够获得它们的数量。
ParameterMetaData metaData = query.getParameterMetaData();
parameterCount = metaData.getParameterCount();
我正在编写通用的SQLException记录器,我想获取传递到PreparedStatement中的参数,该如何操作?我已经能够获得它们的数量。
ParameterMetaData metaData = query.getParameterMetaData();
parameterCount = metaData.getParameterCount();
简短回答:你无法获取 JDBC 驱动程序中的参数值。
详细回答:所有 JDBC 驱动程序都会保存参数值,但没有标准方法可以获取它们。
如果您想为调试或类似目的打印它们,有几个选项:
创建一个透传 JDBC 驱动程序(以 p6spy 或 log4jdbc 为基础),它会保留参数的副本并提供一个公共 API 来读取它们。
使用 Java 反射 API(Field.setAccessible(true)
是你的朋友)来读取 JDBC 驱动程序的私有数据结构。这是我首选的方法。我有一个工厂,它委托给特定于数据库的实现,可以解码参数,并允许我通过 getObject(int column)
读取参数。
提交错误报告并要求改进异常处理。特别是 Oracle 在告诉你出了什么问题方面非常吝啬。
只需要创建一个自定义的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);
}
}
第二个解决方案更加简短,但似乎有些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 等调用以获取您的值。