如何从准备语句中获取参数?解决方案 1:子类解决方案 2:动态代理
2022-09-02 03:43:37
我正在为SQLException编写通用记录器,我想获取传递到ReadyStatement中的参数,如何做到这一点?我能够得到他们的计数。
ParameterMetaData metaData = query.getParameterMetaData();
parameterCount = metaData.getParameterCount();
我正在为SQLException编写通用记录器,我想获取传递到ReadyStatement中的参数,如何做到这一点?我能够得到他们的计数。
ParameterMetaData metaData = query.getParameterMetaData();
parameterCount = metaData.getParameterCount();
简短的回答:你不能。
长答案:所有 JDBC 驱动程序都将参数值保留在某个位置,但没有标准的方法来获取它们。
如果要打印它们以进行调试或类似目的,则有几个选项:
创建一个直通 JDBC 驱动程序(使用 p6spy 或 log4jdbc 作为基础),该驱动程序保留参数的副本并提供公共 API 来读取它们。
使用 Java Reflection API(是你的朋友)读取 JDBC 驱动程序的私有数据结构。这是我的首选方法。我有一个工厂,它委托给数据库特定的实现,可以解码参数,并允许我通过读取参数。Field.setAccessible(true)
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);
}
};
}
如果你想保存参数并在以后获取它们,有很多解决方案,但我更喜欢创建一个新类,如ParadmarkAwarePreparedStatement,它在映射中具有参数。结构可能类似于:
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);
}
}
第二种解决方案更短,但似乎更笨拙。
您可以通过在 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 等调用。