Как получить параметры из PreparedStatement?
Я пишу generic logger для SQLException, и я хотел бы получить параметры, которые были переданы в PreparedStatement, как это сделать? Я смог подсчитать их.
ParameterMetaData metaData = query.getParameterMetaData();
parameterCount = metaData.getParameterCount();
Ответы
Ответ 1
Короткий ответ: вы не можете.
Длинный ответ: все драйверы JDBC будут хранить значения параметров где-то, но нет стандартного способа их получения.
Если вы хотите распечатать их для отладки или аналогичных целей, у вас есть несколько вариантов:
-
Создайте сквозной драйвер JDBC (используйте p6spy или log4jdbc в качестве основы), который хранит копии параметров и предлагает публичный API для их чтения.
-
Используйте API Java Reflection (Field.setAccessible(true)
- ваш друг), чтобы прочитать частные структуры данных драйверов JDBC. Это мой предпочтительный подход. У меня есть factory, который делегирует DB конкретные реализации, которые могут декодировать параметры, и это позволяет мне читать параметры через getObject(int column)
.
-
Распечатайте отчет об ошибке и попросите улучшить исключения. Особенно, что Oracle действительно скуднее, когда речь заходит о том, что не так.
Ответ 2
Решение 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: Динамический прокси
Это второе решение короче, но кажется более взломанным.
Вы можете создать динамический прокси, вызвав метод factory на 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 и т.д., просматривая имена методов и просматривая аргументы второго метода для ваших значений.
Ответ 3
Эта статья, из Boulder, ahtoulgh DB 2 "specific", дает полный пример использования ParameterMetadata.