Using jdbcTemplate nested in another jdbcTemplate (Before start of result set) - java

I make a query:
jdbcTemplate.query(sqlQueryForGetNodes, new Object[]{treeId, versionId}, rs -> {
NodeType nodeType = NodeType.get(rs.getInt("NodeTypeId"));
int nodeId = rs.getInt("NodeId");
SmbpTreeSwitchCase switchCase = new SmbpTreeSwitchCase();
switchCase.setSwitchConditionType(getSwitchTypeByNodeId(nodeId));
smbpTreeNodes.add(switchCase);
});
private SwitchConditionType getSwitchTypeByNodeId(int nodeId) {
String sqlQueryForGetSwitchTypeByNodeId = "SELECT SwitchConditionTypeId FROM RE.SwitchCondition sc WHERE sc.NodeId = ?";
return jdbcTemplate.query(sqlQueryForGetSwitchTypeByNodeId, resultSet -> {
return SwitchConditionType.get(resultSet.getInt("SwitchConditionTypeId"));
}, nodeId);
}
And in this query, I need to subquery another table in method getSwitchTypeByNodeId to fill the object with data.
But I'm getting an error:
org.springframework.dao.TransientDataAccessResourceException: PreparedStatementCallback; SQL [SELECT SwitchConditionTypeId FROM RE.SwitchCondition sc WHERE sc.NodeId = ?]; Before start of result set; nested exception is java.sql.SQLException: Before start of result set
What am I doing wrong? Tell me how to correctly make a subquery in jdbcTemplate?

I think your two lambdas in your two calls to JdbcTemplate.query have different types and hence are being treated differently by Spring.
The first call, which appears to work, appears to use the overload of the JdbcTemplate.query method that takes a String, an Object[] and a RowCallbackHandler and returns void. In this case, your lambda is a RowCallbackHandler which is called once for each row in the result-set. Spring will be advancing the result-set from one row to the next; it's just up to you to do something with each row.
The second call, however, appears to use the overload of JdbcTemplate.query that takes a String, a ResultSetExtractor and a varargs array of arguments, and returns whatever the ResultSetExtractor returns. When using a ResultSetExtractor, Spring will call you only once and expect you to do all of the handling of the result-set. In particular, you will need to check whether the result-set contains any data at all.
Try modifying your second lambda to something like the following:
return jdbcTemplate.query(sqlQueryForGetSwitchTypeByNodeId, resultSet -> {
if (resultSet.next()) {
return SwitchConditionType.get(resultSet.getInt("SwitchConditionTypeId"));
} else {
// SQL query returned no rows. TODO handle this somehow...
}
});

Related

How to dynamically append results to a mocked method in Mockito

I'm originally mocking java.sql.ResultSet like this:
ResultSet rs = mock(ResultSet.class);
when(rs.next()).thenReturn(true, false);
when(rs.getString(1)).thenReturn("foo");
when(rs.getString(2)).thenReturn("bar");
when(rs.getInt(3)).thenReturn(55);
The above code is for mocking a simple one level data mocking, and it's working without any issues.
However, we have more complicated cases in our code where we use ResultSet in multiple method calling levels. Here's a sample code for a nested calling for ResultSet.
void level1(){
ResultSet rs = stmt.executeQuery();
while (rs.next()) {
String a = rs.getString(1);//Expected to return "foo"
String b = rs.getString(2);//Expected to return "bar"
int c = rs.getInt(3);//Expected to return 55
}
level2();
}
void level2(){
ResultSet rs = stmt.executeQuery();
while (rs.next()) {
String a = rs.getString(1);//Expected to return "lorem"
String b = rs.getString(2);//Expected to return "ipsum"
}
level3();
}
void level3(){
ResultSet rs = stmt.executeQuery();
while (rs.next()) {
String a = rs.getString(1);//Expected to return "alice"
int b = rs.getInt(2);//Expected to return 66
int c = rs.getInt(3);//Expected to return 77
}
level3();
}
To mock the method level1 in the above snippet, we have to write something similar to this
ResultSet rs = mock(ResultSet.class);
when(rs.next()).thenReturn(true, false, true, false, true, false);
when(rs.getString(1)).thenReturn("foo", "lorem", "alice");
when(rs.getString(2)).thenReturn("bar", "ipsum");
when(rs.getInt(3)).thenReturn(55, 77);
when(rs.getInt(2)).thenReturn(66);
As you can see from the above example, mocking the nested methods is not readable at all.
We are looking for a way to replace the unreadable mocking code with something more redable that will look like this:
ResultSet rs = mock(ResultSet.class);
when(rs.next()).thenReturnAndAppend(true, false);
when(rs.getString(1)).thenReturnAndAppend("foo");
when(rs.getString(2)).thenReturnAndAppend("bar");
when(rs.getInt(3)).thenReturnAndAppend(55);
when(rs.next()).thenReturnAndAppend(true, false);
when(rs.getString(1)).thenReturnAndAppend("lorem");
when(rs.getString(2)).thenReturnAndAppend("ipsum");
when(rs.next()).thenReturnAndAppend(true, false);
when(rs.getString(1)).thenReturnAndAppend("alice");
when(rs.getInt(2)).thenReturnAndAppend(66);
when(rs.getInt(3)).thenReturnAndAppend(77);
Is there a way in Mockito to achieve that?
I've already tried to use when().then() multiple times for the same method, but that overrides the previous mock for that method, and doesn't append values.
I've also tried to use OngoingStubbing to accomplish that but it throws an exception saying this is a bad behavior to use then on different lines.
I've also tried to create a custom mock method to store values in a map and load them from the map later
private Map<Object, Stream> mockingMap;
protected <T> void whenThen(T method, T value) {
if(!mockingMap.containsKey(method)) {
mockingMap.put(mock, (Stream<T>)Stream.of());
}
mockingMap.put(method, Stream.concat(mockingMap.get(method), Stream.of(value)))
when(method).thenAnswer(e -> {
mockingMap.get(method).next()
});
}
And will use this method like this:
whenThen(rs.getString(1), "foo");
whenThen(rs.getString(1), "lorem");
The issue with this implementation is that the value of method in whenThen method doesn't represent the mocked method rs.getString(1), which means that calling rs.getString(1) two times will result in two different values for the method parameter.
My first question is: Is there any built in method in Mockito that has a similar behavior to thenReturnAndAppend.
My second question is: If there's no similar method to thenReturnAndAppend, then how can I pass a unique key to the whenThen method that represents the method that we are trying to mock?
According to the documentation, you can do so using iterator-style stubbing:
https://javadoc.io/doc/org.mockito/mockito-core/2.24.5/org/mockito/Mockito.html#stubbing_consecutive_calls
Stubbing consecutive calls (iterator-style stubbing) Sometimes we need to stub with different return value/exception for the same method
call. Typical use case could be mocking iterators. Original version of
Mockito did not have this feature to promote simple mocking. For
example, instead of iterators one could use Iterable or simply
collections. Those offer natural ways of stubbing (e.g. using real
collections). In rare scenarios stubbing consecutive calls could be
useful, though:
when(mock.someMethod("some arg"))
.thenThrow(new RuntimeException())
.thenReturn("foo");
//First call: throws runtime exception:
mock.someMethod("some arg");
//Second call: prints "foo"
System.out.println(mock.someMethod("some arg"));
//Any consecutive call: prints "foo" as well (last stubbing wins).
System.out.println(mock.someMethod("some arg"));
Your example would then look similar to:
PreparedStatement stmt = null;
ResultSet mockResult1 = Mockito.mock(ResultSet.class);
Mockito.when(mockResult1 .next()).thenReturn(true);
Mockito.when(mockResult1 .getString(1)).thenReturn("foo");
Mockito.when(mockResult1 .getString(2)).thenReturn("bar");
Mockito.when(mockResult1 .getInt(3)).thenReturn(55);
ResultSet mockResult2 = Mockito.mock(ResultSet.class);
Mockito.when(mockResult2 .next()).thenReturn(true);
Mockito.when(mockResult2 .getString(1)).thenReturn("lorem");
Mockito.when(mockResult2 .getString(2)).thenReturn("ipsum");
Mockito.when(mockResult2 .getInt(3)).thenReturn(55);
Mockito.when(stmt.executeQuery()).thenReturn(mockResult1).thenReturn(mockResult2);

Query returning only empty value in java

I am using java to connect with oracle.This is the code which I have used
public List<FavouriteShop> getmyfavouriteshop(String username) {
List<FavouriteShop> res=null;
res = this.getJdbcTemplate().query("select * from(Select tbl_orderdetails.branch_name as myfavourite,tbl_orderdetails.branch_id as branch_id from tbl_orderdetails inner join tbl_ordermaster on tbl_orderdetails.order_master_id=tbl_ordermaster.ordermasterid where tbl_ordermaster.user_id='"+username+"' group by tbl_orderdetails.branch_name,tbl_orderdetails.branch_id order by count(tbl_orderdetails.branch_name) desc) where rownum<=3", new MyFavourite());
return res;
}
private class MyFavourite implements RowMapper<FavouriteShop> {
public FavouriteShop mapRow(ResultSet rs,int i) throws SQLException {
FavouriteShop g=new FavouriteShop();
g.setBranch_id(rs.getString("branch_id"));
g.setMyfavourite(rs.getString("myfavourite"));
return g;
}
}
I tried to execute same query in oracle I am getting output but not here and I am getting only empty result.
First, you have a possible SQL injection. You can avoid this by giving username as an argument to query
this.getJdbcTemplate().query("select * from (... where tbl_ordermaster.user_id=? ...) where rownum<=3",
new Object[]{ username }, new MyFavourite());
A possible reason for the empty result might be
... where tbl_ordermaster.user_id='"+username+"' ...
Usually, user_id is an integer value, but you compare it to a String and enclose it in quotes. Passing username as an argument to query as shown above, should already take care of this.
Usually it is not the same query or not the same database :)
Extract your query text to separate variable, print it to logs. Then copy-paste from logs to sql developer.
And check database and user name.
Also, it is possible that you inserted that entries but forgot to add COMMIT.

Java: MySQL Query and return

I'm trying to access my database, inject some SQL Code and return the value out of it.
First of all, I'm a new to this stuff but I've came up with the following code:
public static ResultSet checkCmdAmount() throws Exception {
try {
// This will load the MySQL driver, each DB has its own driver
Class.forName("com.mysql.jdbc.Driver");
// Setup the connection with the DB
connect = DriverManager.getConnection(""+MyBot.mysqlDbPath+"",""+MyBot.mysqlDbUsername+"",""+MyBot.mysqlDbPassword+"");
PreparedStatement zpst=null;
ResultSet zrs=null;
zpst=connect.prepareStatement("SELECT COUNT(1) FROM eigenebenutzerbefehle");
zrs=zpst.executeQuery();
return zrs;
}catch (Exception e) {
throw e;
} finally {
close();
}
}
In my return, I get the following:
ResultSet: com.mysql.jdbc.JDBC4ResultSet#196da649
But I want actually the Amount of rows in my table.
When I execute the sql code through phpmyadmin I get 3 which is correct.
What is wrong here?
You need to read and get the desired values from the ResultSet. Do it as below:
public static int checkCmdAmount() throws Exception {
// ...
int count = 0;
while (zrs.next()) {
// Get the values from the current row...
count = zrs.getInt(1);
}
return count;
}
A ResultSet object contains all rows returned by executing an SQL query using a PreparedStatment or Statement from a database.
So when you executed
ResultSet zrs=null;
zpst=connect.prepareStatement("SELECT COUNT(1) FROM eigenebenutzerbefehle");
zrs=zpst.executeQuery();
return zrs;
as you said your SQL query will return number of rows, and that information is stored in ResultSet object zrs, but a ResultSet object's job is to store all the rows containing values from all columns specified or all rows in case of using *.
And when you are returning zrs you are returning a ResultSet object, and when you try and print an object what you get is the default value for an object's toString() conversion, which is in most cases objects types fully qualified name + a few extra characters.
And your updated code executes
if(zrs.next()){
return zrs.getInt(1);
}
else{
return -1;
}
here zrs.next() call moves the zrs to valid next record(or row) from where values can be retrieved, it also returns true or false depending upon the presence of record. In your example if you add one more call to zrs.next() then it would be returning false.
zrs.getInt(1) will return the value in the row the zrs pointing to and the value of the first column, it has in that row, which is in your case only column.

Type mismatch: cannot convert from List to ILogin

private ILogin results;
public ILogin authenticate(Login login) {
System.out.println(login);
System.out.println(login.getEmail());
String query = "SELECT email, id FROM Login WHERE email='"
+ login.getEmail() + "' AND password='" + login.getPassword() + "'";
results = getHibernateTemplate().find(query);
System.out.println(results);
return results;
}
How do i change results = getHibernateTemplate().find(query); I get the error in this line. But i want that in ILogin type not of List type. How do i do an Type Conversion here.
The problem isn't the type conversion per se - it's that you've executed a query which may return multiple results, but you only want a single result.
You probably want to check that the resulting list has exactly one value (0 means login not found; more than 1 probably means there's a problem in your data somewhere) and then return that first value (return (ILogin) list.get(0);).
As a slightly separate matter, you shouldn't be including the data directly in your query like that, IMO. Use query parameters, which is pretty easy in Hibernate:
String query = "SELECT email, id FROM Login WHERE email=? AND password=?";
Object[] parameters = { login.getEmail(), login.getPassword() };
List results = getHibernateTemplate().find(query, parameters);
if (results.size() != 1) {
// Probably throw an exception
}
// I'm assuming your mapping has been set up appropriately such that
// the returned value will *be* an `ILogin`.
return (ILogin) results.get(0);
Finally, you almost certainly don't want results to be an instance variable - it should probably be a local variable, as per my example above.
The dummy way is to use getHibernateTemplate().find(query).get(0); but this will result in exception in case when no such login found.
As Jon said, check your query for emptiness.
I assume spring :).
List resultsList = getHibernateTemplate().find(query);
if ( resultsList.size() == 1 ) {
results = (ILogin)resultsList.get(0);
} else {
// error no entity or mutiple entities
}
return results.
This should work.

How to protect against SQL injection when the WHERE clause is built dynamically from search form?

I know that the only really correct way to protect SQL queries against SQL injection in Java is using PreparedStatements.
However, such a statement requires that the basic structure (selected attributes, joined tables, the structure of the WHERE condition) will not vary.
I have here a JSP application that contains a search form with about a dozen fields. But the user does not have to fill in all of them - just the one he needs. Thus my WHERE condition is different every time.
What should I do to still prevent SQL injection?
Escape the user-supplied values? Write a wrapper class that builds a PreparedStatement each time? Or something else?
The database is PostgreSQL 8.4, but I would prefer a general solution.
Thanks a lot in advance.
Have you seen the JDBC NamedParameterJDBCTemplate ?
The NamedParameterJdbcTemplate class
adds support for programming JDBC
statements using named parameters (as
opposed to programming JDBC statements
using only classic placeholder ('?')
arguments.
You can do stuff like:
String sql = "select count(0) from T_ACTOR where first_name = :first_name";
SqlParameterSource namedParameters = new MapSqlParameterSource("first_name", firstName);
return namedParameterJdbcTemplate.queryForInt(sql, namedParameters);
and build your query string dynamically, and then build your SqlParameterSource similarly.
I think that fundamentally, this question is the same as the other questions that I referred to in my comment above, but I do see why you disagree — you're changing what's in your where clause based on what the user supplied.
That still isn't the same as using user-supplied data in the SQL query, though, which you definitely want to use PreparedStatement for. It's actually very similar to the standard problem of needing to use an in statement with PreparedStatement (e.g., where fieldName in (?, ?, ?) but you don't know in advance how many ? you'll need). You just need to build the query dynamically, and add the parameters dynamically, based on information the user supplied (but not directly including that information in the query).
Here's an example of what I mean:
// You'd have just the one instance of this map somewhere:
Map<String,String> fieldNameToColumnName = new HashMap<String,String>();
// You'd actually load these from configuration somewhere rather than hard-coding them
fieldNameToColumnName.put("title", "TITLE");
fieldNameToColumnName.put("firstname", "FNAME");
fieldNameToColumnName.put("lastname", "LNAME");
// ...etc.
// Then in a class somewhere that's used by the JSP, have the code that
// processes requests from users:
public AppropriateResultBean[] doSearch(Map<String,String> parameters)
throws SQLException, IllegalArgumentException
{
StringBuilder sql;
String columnName;
List<String> paramValues;
AppropriateResultBean[] rv;
// Start the SQL statement; again you'd probably load the prefix SQL
// from configuration somewhere rather than hard-coding it here.
sql = new StringBuilder(2000);
sql.append("select appropriate,fields from mytable where ");
// Loop through the given parameters.
// This loop assumes you don't need to preserve some sort of order
// in the params, but is easily adjusted if you do.
paramValues = new ArrayList<String>(parameters.size());
for (Map.Entry<String,String> entry : parameters.entrySet())
{
// Only process fields that aren't blank.
if (entry.getValue().length() > 0)
{
// Get the DB column name that corresponds to this form
// field name.
columnName = fieldNameToColumnName.get(entry.getKey());
// ^-- You'll probably need to prefix this with something, it's not likely to be part of this instance
if (columnName == null)
{
// Somehow, the user got an unknown field into the request
// and that got past the code calling us (perhaps the code
// calling us just used `request.getParameterMap` directly).
// We don't allow unknown fields.
throw new IllegalArgumentException(/* ... */);
}
if (paramValues.size() > 0)
{
sql.append("and ");
}
sql.append(columnName);
sql.append(" = ? ");
paramValues.add(entry.getValue());
}
}
// I'll assume no parameters is an invalid case, but you can adjust the
// below if that's not correct.
if (paramValues.size() == 0)
{
// My read of the problem being solved suggests this is not an
// exceptional condition (users frequently forget to fill things
// in), and so I'd use a flag value (null) for this case. But you
// might go with an exception (you'd know best), either way.
rv = null;
}
else
{
// Do the DB work (below)
rv = this.buildBeansFor(sql.toString(), paramValues);
}
// Done
return rv;
}
private AppropriateResultBean[] buildBeansFor(
String sql,
List<String> paramValues
)
throws SQLException
{
PreparedStatement ps = null;
Connection con = null;
int index;
AppropriateResultBean[] rv;
assert sql != null && sql.length() > 0);
assert paramValues != null && paramValues.size() > 0;
try
{
// Get a connection
con = /* ...however you get connections, whether it's JNDI or some conn pool or ... */;
// Prepare the statement
ps = con.prepareStatement(sql);
// Fill in the values
index = 0;
for (String value : paramValues)
{
ps.setString(++index, value);
}
// Execute the query
rs = ps.executeQuery();
/* ...loop through results, creating AppropriateResultBean instances
* and filling in your array/list/whatever...
*/
rv = /* ...convert the result to what we'll return */;
// Close the DB resources (you probably have utility code for this)
rs.close();
rs = null;
ps.close();
ps = null;
con.close(); // ...assuming pool overrides `close` and expects it to mean "release back to pool", most good pools do
con = null;
// Done
return rv;
}
finally
{
/* If `rs`, `ps`, or `con` is !null, we're processing an exception.
* Clean up the DB resources *without* allowing any exception to be
* thrown, as we don't want to hide the original exception.
*/
}
}
Note how we use information the user supplied us (the fields they filled in), but we didn't ever put anything they actually supplied directly in the SQL we executed, we always ran it through PreparedStatement.
The best solution is to use a middle that does data validation and binding and acts as an intermediary between the JSP and the database.
There might be a list of column names, but it's finite and countable. Let the JSP worry about making the user's selection known to the middle tier; let the middle tier bind and validate before sending it on to the database.
Here is a useful technique for this particular case, where you have a number of clauses in your WHERE but you don't know in advance which ones you need to apply.
Will your user search by title?
select id, title, author from book where title = :title
Or by author?
select id, title, author from book where author = :author
Or both?
select id, title, author from book where title = :title and author = :author
Bad enough with only 2 fields. The number of combinations (and therefore of distinct PreparedStatements) goes up exponentially with the number of conditions. True, chances are you have enough room in your PreparedStatement pool for all those combinations, and to build the clauses programatically in Java, you just need one if branch per condition. Still, it's not that pretty.
You can fix this in a neat way by simply composing a SELECT that looks the same regardless of whether each individual condition is needed.
I hardly need mention that you use a PreparedStatement as suggested by the other answers, and a NamedParameterJdbcTemplate is nice if you're using Spring.
Here it is:
select id, title, author
from book
where coalesce(:title, title) = title
and coalesce(:author, author) = author
Then you supply NULL for each unused condition. coalesce() is a function that returns its first non-null argument. Thus if you pass NULL for :title, the first clause is where coalesce(NULL, title) = title which evaluates to where title = title which, being always true, has no effect on the results.
Depending on how the optimiser handles such queries, you may take a performance hit. But probably not in a modern database.
(Though similar, this problem is not the same as the IN (?, ?, ?) clause problem where you don't know the number of values in the list, since here you do have a fixed number of possible clauses and you just need to activate/disactivate them individually.)
I'm not confident if there is a quote() method, which was widely used in PHP's PDO. This would allow you a more flexible query building approach.
Also, one of the possible ideas could be creating special class, which would process filter criterias and would save into a stack all placeholders and their values.

Categories

Resources