We have an SQL statement which is executed by Jdbi (org.skife.jdbi.v2). For binding parameters we use Jdbi's bind method:
Handle handle = ...
Query<Map<String, Object>> sqlQuery = handle.createQuery(query);
sqlQuery.bind(...)
However we have a problem with in-lists and currently we are using String.format for this. So our query can look like this:
SELECT DISTINCT
tableOne.columnOne,
tableTwo.columnTwo,
tableTwo.columnThree
FROM tableOne
JOIN tableTwo
ON tableOne.columnOne = tableTwo.columnOne
WHERE tableTwo.columnTwo = :parameterOne
AND tableTwo.columnThree IN (%s)
%s is replaced by String.format so we have to generate a proper string in java code. Then after all %s are replaced we are using jdbi's bind method to replace all other parameters (:parameterOne or ?).
Is there a way to replace String.format with jdbi? There is a method bind(String, Object) but it doesn't handle lists/arrays by default. I have found this article which explains how to write our own factory for binding custom objects but it looks like a lot of effort, especially for something that should be already supported.
The article you linked also descibes the #BindIn annotation. This provides a general purpose implementiation for lists.
#UseStringTemplate3StatementLocator
public class MyQuery {
#SqlQuery("select id from foo where name in (<nameList>)")
List<Integer> getIds(#BindIn("nameList") List<String> nameList);
}
Please note that you'll have to escape all pointy brackets < like this \\<. There is a previous discusion on SO: How to do in-query in jDBI?
I just wanted to add an example since I recently spent considerable time getting a slightly more complex scenario to work :
Query :
select * from sometable where id <:id and keys in (<keys>)
What worked for me :
#UseStringTemplate3StatementLocator
public interface someDAO {
....
....
// This is the method that uses BindIn
#Mapper(someClassMapper.class)
#SqlQuery("select something from sometable where age \\< :age and name in (<names>)")
List<someclass> someMethod (#Bind("age") long age, #BindIn("names") List<string> names);
#Mapper(someClassMapper.class)
#SqlQuery("select something from sometable where id = :id")
List<someclass> someMethod1 (#Bind("id") long id);
...
...
}
Note: I did have to also add the below dependency since I am using
#UseStringTemplate3StatementLocator
<dependency>
<groupId>org.antlr</groupId>
<artifactId>stringtemplate</artifactId>
<version>3.2.1</version>
</dependency>
The main thing to observe in the above example : You only need to escape the less than operator (i.e. < ) and not the <> that surround the collection variable (names).
As you can see I did not use a sql.stg file to write my queries in. Initially I incorrectly assumed that when using #UseStringTemplate3StatementLocator , we have to write the queries in the sql.stg file. However, somehow I never got my sql.stg file to work and I eventually reverted back to writing the query within the DAO class using #SqlQuery.
For the most recent jdbi version things got easier:
public interface MyDao {
#SqlQuery("select id from foo where name in (<nameList>)")
List<Integer> getIds(#BindList("nameList") List<String> nameList);
}
#BindIn → #BindList, and no longer requires StringTemplate
Reference: https://jdbi.org/
Related
Let's say we have a table car and parts. To fetch all car with their parts we use the following query:
#Transactional
public List<ReadCarDto> getAllCars() {
return getDslContext().select(
CAR.ID,
CAR.NAME,
CAR.DESCRIPTION,
multiset(
selectDistinct(
PARTS.ID,
PARTS.NAME,
PARTS.TYPE,
PARTS.DESCRIPTION
).from(PARTS).where(PARTS.CAR_ID.eq(CAR.ID))
).convertFrom(record -> record.map(record1 -> new ReadPartDto(
record1.value1(),
record1.value2(),
record1.value3(),
record1.value4()
)))
).from(CAR).fetch(record -> new ReadCarDto(
record.value1(),
record.value2(),
record.value3(),
record.value4()
));
}
Question: I always want to fetch the full car and part rows. Is there a way to reuse my existing private RecordMapper<CarRecord, ReadCarDto> getCarMapper() method that already implements the DTO conversion (For parts too of course)? Otherwise I have to retype the conversion in my multiset queries.
It looks like the selectDistinct method only has support for 1 - 22 fields and select().from(CAR) doesn't provide a multiset method.
Sidenote: I don't want to use the reflection conversion.
Your question reminds me of this one. You probably want to use a nested row() expression to produce nested records. Something like this:
return getDslContext()
.select(
row(
CAR.ID,
CAR.NAME,
CAR.DESCRIPTION,
...
).mapping(carMapper),
multiset(...)
)
A future jOOQ version will let you use CAR directly as a nested row in your projections, see https://github.com/jOOQ/jOOQ/issues/4727. As of jOOQ 3.16, this isn't available yet.
I'm new to JPA and got stuck with a rather straight forward use case. All I want is to add some conditions to my criteria based on certain filter user passes to my application. User passes the information to application using key, value pairs and we'll decode it to see what type each parameter is.
Eg:
1) key=id and value=20. We'll interpret it as search by id=20, where id is integer
2) key=name and value='test'. We'll interpret it as search by name='test', where name is string
This is achieved by a method like this
public <T> T getSearchValue(Object inputValue) {
if (condition) {
return (T) inputValue.toString();
} else {
return (T) Integer.parseInt(inputValue.toString);
}
}
I was thinking that I could easily add the condition using criteria builder. Something like this
cb.equal(getSearchKey(), getSearchValue(inputValue));
cb.gt(getSearchKey(), getSearchValue(inputValue));
Here cb.equal works fine. But cb.gt or any other operation like lessThan, like etc. are all giving compilation error that there are 2 method matches and hence ambiguous
First method is gt(Expression<? extends Number>, Expression<? extends Number>)
and second one is gt(Expression<? extends Number>, Number)
So how do I resolve this and make sure it can resolve to Number as the second parameter for example? Problem here is I don't know the type in advance as it is added based on what key user want to search with. Is it possible to dynamically cast to a class somehow or is there any other better approach here?
Idea is to supply the restrictions to the where clause of the "SQL statement".
SELECT * FROM table_name WHERE key=value
Lots of people like to write JPQL queries similar to SQL because it is easier to understand when reading. Nevertheless, there are some advantages about using this Criteria API (dynamic, less error prone, less attention for security, easier to refactor).
The first step is getzing the CriteriaBuilder and then, creating a CriteriaQuery with it. Afterwards, you could go on to define from which class you want to build the Root and use this to get the columns. Joins could also follow.
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<YourClass> cq = cb.createQuery(YourClass.class);
Root<YourClass> root = cq.from(YourClass.class);
As a next step, you want to define the where clause with its restrictions (Predicates). As in a normal SQL statement, there could be only one where. If you have multiple restrictions, they have to be combined with criteriaBuilder.and().
There are several ways of applying the whole process. I have one way how I do this (and some others might do it like this as well). Usually, I create a List with all restrictions I want to use and create an array out of this which gets then combined with the cb.and().
List<Predicate> predicates = new ArrayList<>();
predicates.add(cb.equal(root.get("key1"), "value1"));
predicates.add(cb.gt(root.get("key2"), 100));
cq.select(root).where(cb.and(predicates.toArray(new Predicate[predicates.size()])));
Following, a whole example for such a DAO method.
public List<Foo>(String state, int size, String column, String value) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Foo> cq = cb.createQuery(Foo.class);
Root<Foo> root = cq.from(Foo.class);
List<Predicate> predicates = new ArrayList<>();
predicates.add(cb.equal(root.get(Foo_.bla), state));
predicates.add(cb.gt(root.get(Foo_.blub), size));
predicates.add(cb.equal(root.get(column), value));
cq.select(root).where(cb.and(predicates.toArray(new Predicate[predicates.size()])));
return entityManager.createQuery(cq).getResultList();
}
For getting the column names (or singular attributes), I used hibernate-jpamodelgen. Have a look for this plugin which generates classes with underscores automatically (e.g. Foo_) which makes it more typesafe when using column names.
Update: If you do not know the names of the columns for your restrictions in the where clause beforehand, it is simple to adjust it (see third restriction above).
Consider this trivial query:
SELECT 1 as first, 2 as second
When using Hibernate we can then do something like:
em.createNativeQuery(query).fetchResultList()
However, there seem to be no way of getting the aliases (or column names). This would be very helpful for creating List<Map<String, Object>> where each map would be a row with their aliases, for instance in this case: [{first: 1, second: 2}].
Is there a way to do something like that?
I would suggest a bit different approach which may meet your needs.
In JPA 2.1 there is a feature called "result set mapping".
Basically you have to define a POJO class which would hold the result values (all the values must be passed using the constructor):
public class ResultClass{
private String fieldOne;
private String fieldTwo;
public ResultClass(String fieldOne, String fieldTwo){
this.fieldOne = fieldOne;
this.fieldTwo = fieldTwo;
}
}
Then you have to declare the mapping on one of your entities (does not matter on which, it just has to be a declated #Entity):
#SqlResultSetMapping(name="ResultMapping", classes = {
#ConstructorResult(targetClass = ResultClass.class,
columns = {#ColumnResult(name="columnOne"), #ColumnResult(name="columnTwo")})
})
The columnOne and columnTwo are aliases as declared in the select clause of the native query.
And finally use in the query creation:
List<ResultClass> results = em.createNativeQuery(query, "ResultMapping").getResultList();
In my opinion this is more elegant and "a level above" solution as you are not working with a generic Map key/values pairs but with a concrete POJO class.
You can use ResultTransformer interface . Implement custom mapper for mapping values with aliases.
here is example https://vladmihalcea.com/why-you-should-use-the-hibernate-resulttransformer-to-customize-result-set-mappings/
with ResultTransformer you can easy customize result set type , especially if you need aliases
I need to set a table name dynamically so that I use query.setText(tname,abc)
e.g: select a.name from :tname where a.id = '2'
I used setText() because when I use setString() it says "tname is a invalid parameter" because I assume that Hibernate adds '' when setting string parameters.
But even setText() does not help and gives the same exception.
How can I set the table name dynamically?
Reply to PSR:
So you mean replace table name as a java string replacement. But then we can not take support of sql injections prevention etc from hibernate right? Also How we bind parameters in hibernate in a situation where like statement,
Eg: name like "%:name%"
This also gives me Illegal argument exception: Parameter does not exist as a named parameter when i try to bind it using query.setString(name,"def");
Hibernate will not do this for you, because it works with PreparedStatements, and you can't prepare a statement where the table being queried isn't known yet.
I don't see why you would be exposing table names to end users, so preventing SQL injection doing a regular string substitution should be easy. You use some sort of business logic to determine the correct table from a list that only you know. The table name isn't coming from user input at all.
Depending on your choice of RDBMS, you may find a discriminator column, or table inheritance with partitioning to be a better way of handling a situation where identical queries are made against different tables.
It is not possible to set table name dynamically.You can set dynamically column names.it is not possible to set table name
try like this
select a.name from '+table name+'where a.id = '2'
In my opinion, There are 2 ways to resolve this issue:
1- If you are using Spring and Hibernate together, you could use SpEL and it would be like #{#entityName} as it is described here
#Entity
public class User {
#Id
#GeneratedValue
Long id;
String lastname;
}
public interface UserRepository extends JpaRepository<User,Long> {
#Query("select u from #{#entityName} u where u.lastname = ?1")
List<User> findByLastname(String lastname);
}
2-You could use CriteriaBuilder like
CriteriaQuery<YourEntity> cr = cb.createQuery(YourEntity.class);
Root<YourEntity> root = cr.from(YourEntity.class);
cr.select(root);
I copied the source codes from the provided links and they are described there much better
I've a method 'getAllIDs()' which is used for getting the ids for specific table in database. It is used by many methods in my project.
public static int[] getAllIDs (String TableName, String WhereClause, String trxName)
{
ArrayList<Integer> list = new ArrayList<Integer>();
StringBuffer sql = new StringBuffer("SELECT ");
sql.append(TableName).append("_ID FROM ").append(TableName);
if (WhereClause != null && WhereClause.length() > 0)
sql.append(" WHERE ").append(WhereClause);
PreparedStatement pstmt = null;
ResultSet rs = null;
try
{
pstmt = DB.prepareStatement(sql.toString(), trxName);
rs = pstmt.executeQuery();
while (rs.next())
list.add(new Integer(rs.getInt(1)));
}
}
Whereclause is the conditional part of query. There is a chance for sql injection due to this whereclause.
So I need to modify this method to set the parameter using prepared statement parameter setting. Problem I faced because 'getAllIDs()' don't know how many parameters for each whereclause.
Parameters will be different for each whereclause, it can be any number. For some class, parameters will be 3 and for some it will be 2, etc having different data types. So how can i use setstring(), setint() etc. Explain me with the code which I posted.
Pass an additional PreparedStatementBinder argument to the method:
public interface PreparedStatementBinder {
/**
* Binds all the arguments to the given prepared statement
*/
public void bindArguments(PreparedStatement statement) throws SQLException;
}
The caller will have to pass a where clause (such as "foo = ? and bar = ?"), and an instance of this interface, such as
new PreparedStatementBinder() {
#Override
public void bindArguments(PreparedStatement statement) throws SQLException {
statement.setString(1, theFoo);
statement.setInt(2, theBar);
}
}
There are several possible ways to do this. A simple one could be to make the WhereClause behave like a map where you keep parameter names and values. Then you define the prepared statement template based on the keys and fill it with values. You may need a smarter data structure if you want to join the where clauses with AND/OR keywords or use different operators for each clause: = / < / > / NOT / IS NULL etc, all this dynamically.
If you can make use of more sophisticated libraries, the Criteria API in Hibernate or other ORM tools can be really suitable for this kind of usecase.
It sounds like you may be building an object-relational mapper (ORM), query generator, or similar.
If so, consider using an existing solution to the problem, as these are way harder to get right than they look.
Consider the JPA or Hibernate Criteria APIs; they're ugly, but comprehensive. There are many other programmatic query generators out there, not all of which are tied to ORMs.
Don't reinvent this wheel. It might look easy now, but you'll keep on hitting limitations until your design starts getting really complicated and unweildy.
Maybe i got you all wrong but I am proposing a solution based on what i deduced from you statements.
Make the Where-Clause parameter optional like
public static int[] getAllIDs (String tableName, String... whereClause, String trxName)
and if they appears set them like
for(String param:whereClause)
{
pst.setParameter("Param-name",param);
}
Most are suggesting a complete ORM solution like Hibernate or JPA. It will take some effort to integrate either of these if you need it for just this one query, but I'm going to guess that this is not the only query subject to SQL injection and the setup can be reused for some/most/all the queries in the app.
However, a quick-and-dirty solution is to create methods for each variation of the criteria if there are only a few of them. Each method would accept the exact type of parameter(s) required and know how to bind these arguments into a preparedStatement. There could be a factory method in the class (called by these methods) that would be passed table name, txn name, etc. and would return the SELECT and FROM clauses.
Regardless of the solution you choose, the callers of getAllIds() will need to change.