I'm writing generic logger for SQLException and I'd like to get parameters that were passed into PreparedStatement, how to do it ? I was able to get the count of them.
ParameterMetaData metaData = query.getParameterMetaData();
parameterCount = metaData.getParameterCount();
Short answer: You can't.
Long answer: All JDBC drivers will keep the parameter values somewhere but there is no standard way to get them.
If you want to print them for debugging or similar purposes, you have several options:
Create a pass-through JDBC driver (use p6spy or log4jdbc as a basis) which keeps copies of the parameters and offers a public API to read them.
Use Java Reflection API (Field.setAccessible(true) is your friend) to read the private data structures of the JDBC drivers. That's my preferred approach. I have a factory which delegates to DB specific implementations that can decode the parameters and that allows me to read the parameters via getObject(int column).
File a bug report and ask that the exceptions are improved. Especially Oracle is really stingy when it comes to tell you what's wrong.
Solution 1: Subclass
Simply create a custom implementation of a PreparedStatement which delegates all calls to the original prepared statement, only adding callbacks in the setObject, etc. methods. Example:
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);
}
};
}
If you want to save parameters and get them later, there are many solutions, but I prefer creating a new class like ParameterAwarePreparedStatement which has the parameters in a map. The structure could be similar to this:
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);
}
}
Solution 2: Dynamic proxy
This second solution is shorter, but seems more hacky.
You can create a dynamic proxy by calling a factory method on java.lang.reflect.Proxy and delegate all calls on the original instance. Example:
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;
}
Then you intercept the setObject, etc. calls by looking at method names and looking to the second method arguments for your values.
This article, from Boulder, ahtoulgh DB 2 "specific", gives a complete example of ParameterMetadata usage.
Related
I'm using camel version 3.14.5 and I'm wondering if there's a way (I can't see one) where I can use a customBeanPropertyRowMapper when using a SqlEndpoint.
I'm using the sql endpoint like this in a route:
.to("mySqlComponent:classpath:my_sql.sql?outputType=StreamList&outputClass=com.my.project.MyCustomMappedPojo")
Looking at the code it does look like a BeanPropertyRowMapper is hardcoded in the class
DefaultSqlEndpoint
#SuppressWarnings("unchecked")
public ResultSetIterator queryForStreamList(Connection connection, Statement statement, ResultSet rs) throws SQLException {
if (outputClass == null) {
RowMapper rowMapper = new ColumnMapRowMapper();
return new ResultSetIterator(connection, statement, rs, rowMapper);
} else {
Class<?> outputClzz = getCamelContext().getClassResolver().resolveClass(outputClass);
RowMapper rowMapper = new BeanPropertyRowMapper(outputClzz);
return new ResultSetIterator(connection, statement, rs, rowMapper);
}
}
So all I'm after is a way to make use of a Custom RowMapper.
The most obvious way would be to pass it to the SqlEndpoint directly, but there's no such property.
Alternatively I thought about using a custom SqlEndpoint whilst wiring a SqlComponent in Spring, but I see that the SqlComponent uses a SqlEndpoint hardcoded (i.e.: doesn't allow me to inject a custom Endpoint) which in turns uses the hardcoded BeanPropertyRowMapper as per the code sample above.
I'm currently trying to store encrypted data in some of the columns of a Postgres DB. After receiving helpful feedback from this question: client-side-encryption-with-java-and-postgres-database I am using converters/bindings to implement transparent encryption in the JDBC layer.
Right now I'm trying to insert a BigDecimal[][][] into a Postgres DB column of type bytea.
The insertion works but the problem is that the encryption code I've added in the converters/binding doesn't seem to run. Unfortunately, when I check the database I'm seeing an unencrypted 3D matrix. (FYI my encryption utility code is tested and does work)
To test, I put my encryption code in the DAO layer and the BigDecimal[][][] matrix does get encrypted on DB inserts. Although I could do this it defeats the purpose of using converters/bindings for encryption.
So my question:
With the code I provided below am I doing anything wrong that is preventing the encryption code in my converter/binding to be run? I thought after a Prepared Statement is executed the converter is the next step but maybe not? I have a lack of knowledge on just when the converter/binding code gets called in the whole JOOQ flow so any insight is much appreciated! Thanks :D
First I'm using a PreparedStatment in a DAO to execute the insert query.
I can't show the full code but basically for the stmt I'm setting the BigDecimal[][][] as an object parameter:
private Result executeInsert(BigDecimal[][][] valueToEncrypt, String insertSql) {
try (Connection conn = config.connectionProvider().acquire();
PreparedStatement stmt = conn.prepareStatement(insertSql)) {
// Get a human readable version of the 3d matrix to insert into the db.
PostgresReadableArray humanReadableMatrix = getPostgresReadableArray(valueToEncrypt)
stmt.setObject(parameterIndex++, humanReadableMatrix, Types.OTHER);
ResultSet res = stmt.executeQuery();
}
...
}
I am currently attaching the binding to a codegen xml file here:
<forcedType>
<userType>
java.math.BigDecimal[][][]
</userType>
<binding>com.myapp.EncryptionBinding</binding>
<includeExpression>matrix_column</includeExpression>
<includeTypes>bytea</includeTypes>
</forcedType>
Here is my binding class EncryptionBinding:
public class EncryptionBinding implements Binding<byte[], BigDecimal[][][]> {
#Override
public Converter<byte[], BigDecimal[][][]> converter() {
return new MatrixConverter();
}
// Rending a bind variable for the binding context's value and casting it to the json type
#Override
public void sql(BindingSQLContext<BigDecimal[][][]> ctx) throws SQLException {
}
// Registering VARCHAR types for JDBC CallableStatement OUT parameters
#Override
public void register(BindingRegisterContext<BigDecimal[][][]> ctx) throws SQLException {
ctx.statement().registerOutParameter(ctx.index(), Types.VARCHAR);
}
// Converting the BigDecimal[][][] to a Encrypted value and setting that on a JDBC PreparedStatement
#Override
public void set(BindingSetStatementContext<BigDecimal[][][]> ctx) throws SQLException {
ctx.statement().setBytes(ctx.index(), ctx.convert(converter()).value());
}
...
Here is my converter class MatrixConverter used in the above EncryptionBinding class:
public class MatrixConverter extends AbstractConverter<byte[], BigDecimal[][][]> {
private static final Logger logger = LoggerFactory.getLogger(MatrixConverter.class);
public MatrixConverter() {
super(byte[].class, BigDecimal[][][].class);
}
#Override
public BigDecimal[][][] from(byte[] databaseObject) {
return EncryptionUtils.decrypt(databaseObject);
}
#Override
public byte[] to(BigDecimal[][][] userObject) {
return EncryptionUtils.encrypt(JsonUtils.toJson(userObject));
}
}
Looking over the DBUtils API docs, I cannot see if it's possible to query for a Set.
Which implementation of ResultSetHandler i should use for query Set of objects?
I don't think there is a default implementation for Set. You can create a generalized handler for Set as shown below.
public class SetHandler<T> implements ResultSetHandler<Set<T>> {
private final RowProcessor rp = new BasicRowProcessor();
private final Class<T> type;
public SetHandler(Class<T> type) {
this.type = type;
}
#Override
public Set<T> handle(ResultSet rs) throws SQLException {
Set<T> set = new HashSet<>();
while (rs.next()) {
set.add((T) this.rp.toBean(rs,type));
}
return set;
}
}
One down side is that toBean method tries to find ResulSet Column-Bean Property mapping for every row in the ResultSet where as toBeanListmethod(used by BeanListHandler) find this mapping only once per list.
There is a BeanMapHandler which returns HashMap and it internally uses toBean method and hence I think for Sets/Maps we have to rely on toBean method or write a custom RowProcessor.
I am new in spring boot.
I have created on method in spring boot, please see the below function:
public ArrayList<ShipmentDetailsVO> getShipmentStatus(
ShipmentDetailsVO shpmntpert) {
return jdbcTemplate.query("select * from SELECT_SEARCH_DETAILS(?,?,?,?,?,?,?,?)",new ResultSetExtractor<ArrayList<ShipmentDetailsVO>>(){
#Override
public ArrayList<ShipmentDetailsVO> extractData(ResultSet rs) throws SQLException,
DataAccessException {
shipmentDao = new ArrayList<ShipmentDetailsVO>();
while(rs.next()) {
shipmentDetDaoObj =new ShipmentDetailsVO();
shipmentDetDaoObj.setContractNumber(rs.getString(1));
System.out.println("hello" + rs.getString(1));
shipmentDetDaoObj.setOrderNumber(rs.getString(2));
System.out.println(rs.getString(2));
shipmentDetDaoObj.setShipmentNumber(rs.getString(3));
shipmentDetDaoObj.setShipmentControlNo(rs.getString(4));
shipmentDetDaoObj.setStatusCode(rs.getString(5));
shipmentDetDaoObj.setStatusDateStr(rs.getString(6));
shipmentDetDaoObj.setLastUpdatedtStr(rs.getString(7));
shipmentDetDaoObj.setResendFlag(false);
shipmentDetDaoObj.setSourceSystem(rs.getString(8));
shipmentDetDaoObj.setDestinationSystem(rs.getString(9));
shipmentDetDaoObj.setRfid(rs.getString(10));
shipmentDetDaoObj.setUid(rs.getString(11));
shipmentDetDaoObj.setShipmentSeqId(rs.getString(12));
shipmentDao.add(shipmentDetDaoObj);
}
return shipmentDao;
}
});
}
I don't have any idea how to assign all the '?' with object's(shpmntpert) data. Can any one kindly help on the same.
From JdbcTemplate documentation
You can find one of the query the signature
public <T> T query(String sql, Object[] args, ResultSetExtractor<T> rse)
throws DataAccessException
With an explanation of the interesting parameters
sql - SQL query to execute
args - arguments to bind to the query
So you can see that the array is used to bing the values. That means that for a query like
select * from table where name = ? and weight= ?
you need to generate an array with those values in the correct order
new Object[]{name, weight}
An other signature show you that they provide some setter for PreparedStatement named PreparedStatementSetter.
public <T> T query(String sql,
PreparedStatementSetter pss,
ResultSetExtractor<T> rse)
throws DataAccessException
Where the PreparedStatementSetter interface have only one methods to implements
void setValues(PreparedStatement ps)
That will let you set the values like you want fron this setter. You could pass the instance to an implementation of this interface or directly in the code like
final MyInstance m = new MyInstance();
jdbcTemplate.query("select * from table where name = ? and weight= ?",
new PreparedStatementSetter(){
#Override
public void setValues(PreparedStatement ps){
ps.setString(1, m.getName);
ps.setInt(2, m.getWeight);
}
}, myResultSetExtractor);
Note : This is a quick reading of the documentation knowing that I never used this API. But from what I have read, this should work just fine.
So when we use JDBI to query from database, it is getting it into a Map<String, Object> type.
I want to get it as my customized object (constructor) instead of Map<String, Object>.
DBI dbi = establishConnection(url, userName, passWord);
Handle handle = dbi.open();
List<Map<String, Object>> rs = handle.select("select * from sometable");
Instead I want to use:
List<customizedObject> rs = handle.select("select * from sometable");
Where customizedObject class is an object that contains all the column properties with it.
Is there any way to do this? I found some relative documentation, but I cannot really understand the implementation.
http://jdbi.org/sql_object_api_queries/
Please also see the previous page in the documentation that shows how to link your Handle or DBI with the mappers.
Essentially, you need a mapper to convert the ResultSet to the desired object and an interface to refer to the mapper.
Let's assume a minimal example. First the mapper needs to be provided:
public class CustomizedObjectMapper implements ResultSetMapper<customizedObject> {
#Override
public customizedObject map(int index, ResultSet r, StatementContext ctx)
throws SQLException {
return new customizedObject(r.getString("uuid"), r.getString("other_column"));
}
}
Then we need an interface to define which query provides the data that is passed to the mapper class. One result row leads to one invocation of CustomizedObjectMapper.map(...):
#RegisterMapper(CustomizeObjectMapper.class)
public interface CustomizeObjectQuery {
#SqlQuery("Select uuid, other_column from schema.relation")
List<customizedObject> get();
}
Finally, the objects can be retrieved: List<customizedObject> test = dbi.open(CustomizeObjectQuery.class).get().
Your can also put the components together on an individual basis like so and omit the interface:
dbi.open().createQuery("Select uuid, other_colum from schema.relation").map(new EventMapper()).list()