Is a generic sql query possible in java? - java

If I have a method to create SQL queries, as below :
public List selectTuple() {
boolean status = true;
String query = "SELECT ";
query += getFields() == null ? " * " : " " + getFields() + " ";
query += " FROM " + tables;
if ( getSearchClause() != null ) {
query += " " + getSearchClause();
}
query += ";";
Debug("SQL...........caleed selectTuple method, query is : "+ query);
setQuery(query);
if ( getPrepared() ) {//If this is a Prepared query,
status = setPreparedStatement();
} else {
status = setNonPreparedStatement();
}
if ( ! status ) {
Log("[CRITICAL] (.........)..........");
}
status = doExecuteQuery();
if ( ! status ) {
Log("[CRITICAL] (.........)..........");
}
return( getResults() );
}//method selectTuple
However, since this will be used for different tables, the fields will be of different data types (int, string, date etc). So how can I iterate through such a ResultSet ?
Also, how can I create such an Insert query ?
Thanks.

Yes, I think it could be done... You can use getMetaData() in the ResultSet to get the number and type of columns and iterate through the ResultSet consequently.
getMetaData():
ResultSetMetaData class
However, I don't know how to code such a generic insert query...

You will need to use getObject and pass a map of JDBC to Java object mappings for your own types if any.
So, if your table has column i numeric and column s varchar the next code
ResultSet rs = st.executeQuery("select i, s from test");
rs.next();
System.out.println(rs.getObject(1).getClass());
System.out.println(rs.getObject(2).getClass());
will result in
class java.lang.Integer
class java.lang.String
the only thing for you to do is to check the returned Object for its class, using instanceof, to do the actual casting.
You can refer to this article for more details.
For the insert part you could use setObject method and rely on JDBC conversion, which is probably not a very good idea but should work.

As #jahroy commented, avoid using JDBC for this type of generic things. I also just had the same problem and I came up with an easy and elegant way to do it with JPA
Here's a generic method I've created to handle any SELECT query, you can get the idea how JPA works. In this case I just wanted to make sure there were only SELECT queries but you can make it more generic, to accept INSERT, DELETE, UPDATE...
public List executeSelectQuery(String selectQuery, Class entityClass, String classPersistentUnit)
{
selectQuery = selectQuery.toUpperCase();
if(!selectQuery.startsWith("SELECT"))
{
throw new IllegalArgumentException("This method only accepts SELECT queries. You tried: " + selectQuery);
}
EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory(classPersistentUnit);
EntityManager entityManager = entityManagerFactory.createEntityManager();
Query query = entityManager.createNativeQuery(selectQuery, entityClass);
return query.getResultList();
}
The good thing is that Netbeans and Eclipse (and probably more IDEs too, but I just use these two) come up with an "auto-create" class Entity, on the fly! You don't need to code anything at all on these entity classes. An entity class, briefly, represents a table in your database. Therefore each table in your database will be represented by an entity class.
You can find an easy and small tutorial here

Related

class java.math.BigInteger cannot be cast to class java.lang.Boolean

I am trying to check whether the room name in that building exists or not.If exists, then don't save else save the data into the table.So I have written this query.
#Query(value = "select exists(select name,building from meetingroom " +
"where building=?1 and name=?2)",nativeQuery = true)
Boolean existsByRoom(String building,String name);
Now it is showing BigInteger cannot be cast to class java.lang.Boolean
Is there any way to solve this without effecting or changing the query.
Simplest solution is here is to use JPA derived query.
Assuming your entity is MeetingRoom
Boolean existsMeetingRoomByBuildingAndName(String building, String name);
One solution is to use java.util.Optional like below. And you need to use BigInteger instead of Boolean because query will return BigInteger value when record exist.
#Query(value = "select exists(select name,building from meetingroom " +
"where building=?1 and name=?2)",nativeQuery = true)
Optional<BigInteger> existsByRoom(String building,String name);
And in service layer, you can check if the value is present.
Optional<BigInteger> result =
meetingRoomRepository.existsByRoom(meetingRoomRequest.getBuilding(),meetingRoomRequest.getName());
if(result.isPresent()){
return true;
}
return false;

Java sql delete statement works with =, but doesn't work with in ()? [duplicate]

This question already has answers here:
PreparedStatement IN clause alternatives?
(33 answers)
Closed 5 years ago.
Say that I have a query of the form
SELECT * FROM MYTABLE WHERE MYCOL in (?)
And I want to parameterize the arguments to in.
Is there a straightforward way to do this in Java with JDBC, in a way that could work on multiple databases without modifying the SQL itself?
The closest question I've found had to do with C#, I'm wondering if there is something different for Java/JDBC.
There's indeed no straightforward way to do this in JDBC. Some JDBC drivers seem to support PreparedStatement#setArray() on the IN clause. I am only not sure which ones that are.
You could just use a helper method with String#join() and Collections#nCopies() to generate the placeholders for IN clause and another helper method to set all the values in a loop with PreparedStatement#setObject().
public static String preparePlaceHolders(int length) {
return String.join(",", Collections.nCopies(length, "?"));
}
public static void setValues(PreparedStatement preparedStatement, Object... values) throws SQLException {
for (int i = 0; i < values.length; i++) {
preparedStatement.setObject(i + 1, values[i]);
}
}
Here's how you could use it:
private static final String SQL_FIND = "SELECT id, name, value FROM entity WHERE id IN (%s)";
public List<Entity> find(Set<Long> ids) throws SQLException {
List<Entity> entities = new ArrayList<Entity>();
String sql = String.format(SQL_FIND, preparePlaceHolders(ids.size()));
try (
Connection connection = dataSource.getConnection();
PreparedStatement statement = connection.prepareStatement(sql);
) {
setValues(statement, ids.toArray());
try (ResultSet resultSet = statement.executeQuery()) {
while (resultSet.next()) {
entities.add(map(resultSet));
}
}
}
return entities;
}
private static Entity map(ResultSet resultSet) throws SQLException {
Enitity entity = new Entity();
entity.setId(resultSet.getLong("id"));
entity.setName(resultSet.getString("name"));
entity.setValue(resultSet.getInt("value"));
return entity;
}
Note that some databases have a limit of allowable amount of values in the IN clause. Oracle for example has this limit on 1000 items.
Since nobody answer the case for a large IN clause (more than 100) I'll throw my solution to this problem which works nicely for JDBC. In short I replace the IN with a INNER JOIN on a tmp table.
What I do is make what I call a batch ids table and depending on the RDBMS I may make that a tmp table or in memory table.
The table has two columns. One column with the id from the IN Clause and another column with a batch id that I generate on the fly.
SELECT * FROM MYTABLE M INNER JOIN IDTABLE T ON T.MYCOL = M.MYCOL WHERE T.BATCH = ?
Before you select you shove your ids into the table with a given batch id.
Then you just replace your original queries IN clause with a INNER JOIN matching on your ids table WHERE batch_id equals your current batch. After your done your delete the entries for you batch.
The standard way to do this is (if you are using Spring JDBC) is to use the org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate class.
Using this class, it is possible to define a List as your SQL parameter and use the NamedParameterJdbcTemplate to replace a named parameter. For example:
public List<MyObject> getDatabaseObjects(List<String> params) {
NamedParameterJdbcTemplate jdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
String sql = "select * from my_table where my_col in (:params)";
List<MyObject> result = jdbcTemplate.query(sql, Collections.singletonMap("params", params), myRowMapper);
return result;
}
I solved this by constructing the SQL string with as many ? as I have values to look for.
SELECT * FROM MYTABLE WHERE MYCOL in (?,?,?,?)
First I searched for an array type I can pass into the statement, but all JDBC array types are vendor specific. So I stayed with the multiple ?.
I got the answer from docs.spring(19.7.3)
The SQL standard allows for selecting rows based on an expression that includes a variable list of values. A typical example would be select * from T_ACTOR where id in (1, 2, 3). This variable list is not directly supported for prepared statements by the JDBC standard; you cannot declare a variable number of placeholders. You need a number of variations with the desired number of placeholders prepared, or you need to generate the SQL string dynamically once you know how many placeholders are required. The named parameter support provided in the NamedParameterJdbcTemplate and JdbcTemplate takes the latter approach. Pass in the values as a java.util.List of primitive objects. This list will be used to insert the required placeholders and pass in the values during the statement execution.
Hope this can help you.
AFAIK, there is no standard support in JDBC for handling Collections as parameters. It would be great if you could just pass in a List and that would be expanded.
Spring's JDBC access supports passing collections as parameters. You could look at how this is done for inspiration on coding this securely.
See Auto-expanding collections as JDBC parameters
(The article first discusses Hibernate, then goes on to discuss JDBC.)
See my trial and It success,It is said that the list size has potential limitation.
List l = Arrays.asList(new Integer[]{12496,12497,12498,12499});
Map param = Collections.singletonMap("goodsid",l);
NamedParameterJdbcTemplate namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(getJdbcTemplate().getDataSource());
String sql = "SELECT bg.goodsid FROM beiker_goods bg WHERE bg.goodsid in(:goodsid)";
List<Long> list = namedParameterJdbcTemplate.queryForList(sql, param2, Long.class);
There are different alternative approaches that we can use.
Execute Single Queries - slow and not recommended
Using Stored Procedure - database specific
Creating PreparedStatement Query dynamically - good performance but loose benefits of caching and needs recompilation
Using NULL in PreparedStatement Query - I think this is a good approach with optimal performance.
Check more details about these here.
sormula makes this simple (see Example 4):
ArrayList<Integer> partNumbers = new ArrayList<Integer>();
partNumbers.add(999);
partNumbers.add(777);
partNumbers.add(1234);
// set up
Database database = new Database(getConnection());
Table<Inventory> inventoryTable = database.getTable(Inventory.class);
// select operation for list "...WHERE PARTNUMBER IN (?, ?, ?)..."
for (Inventory inventory: inventoryTable.
selectAllWhere("partNumberIn", partNumbers))
{
System.out.println(inventory.getPartNumber());
}
One way i can think of is to use the java.sql.PreparedStatement and a bit of jury rigging
PreparedStatement preparedStmt = conn.prepareStatement("SELECT * FROM MYTABLE WHERE MYCOL in (?)");
... and then ...
preparedStmt.setString(1, [your stringged params]);
http://java.sun.com/docs/books/tutorial/jdbc/basics/prepared.html

Can I return custom object from UPDATE query instead of integer?

Executing UPDATE query it always returns INT number of rows affected.
Maybe there is some way to return for example list of all objects which where updated or first one updated?
I have #Entity SecurityPolicy and want this object back after update.
So here is my code:
#Modifying
#Query(value = "UPDATE security_policy s set max_fail_sign_in =:maxFailSignIn," +
" min_password_length =:minPasswordLength," +
" capital_in_password =:capitalInPassword," +
" digit_in_password =:digitInPassword," +
nativeQuery = true)
SecurityPolicy updateSecurityPolicy(#Param("maxFailSignIn") Integer maxFailSignIn,
#Param("minPasswordLength") Integer minPasswordLength,
#Param("capitalInPassword") Boolean capitalInPassword,
#Param("digitInPassword") Boolean digitInPassword);
When using #Modifying it tells me that result should be Int, and without it - could not extract ResultSet.
I know with spring data you can easily save or update some entity and get it as a result, so I want achieve something similar.
No you cannot as the integer response is not generated by framework but by RDBMS itself (SQL spec). That integer says how many rows were modified by query.
Since you don't want to use JPA to do this, you will have to SELECT that row after update, like you would do with plain SQL.

Bad grammar SQL Exception while reading the values using rowmapper

This is my Model class
//Model
public class CustomerData {
private String locomotive_id;
private String customer_name;
private String road_number;
private String locomotive_type_code;
private String in_service_date;
private String part_number;
private String emission_tier_type;
private String airbrake_type_code;
private String lms_fleet;
private String aar_road;
private String locomotive_status_code;
// Getters and Setters
Here is my RowMapper implementation
//RowMapper
public class CustomerDataResponseMapper implements RowMapper {
#Override
public Object mapRow(ResultSet rs, int count) throws SQLException {
CustomerData customerData = new CustomerData();
customerData.setLocomotive_id(rs.getString("locomotive_id"));
customerData.setCustomer_name(rs.getString("customer_name"));
customerData.setRoad_number(rs.getString("road_number"));
customerData.setLocomotive_type_code(rs.getString("locomotive_type_code"));
customerData.setIn_service_date(rs.getString("in_service_date"));
customerData.setPart_number(rs.getString("part_number"));
customerData.setEmission_tier_type(rs.getString("emission_tier_type"));
customerData.setAirbrake_type_code(rs.getString("airbrake_type_code"));
customerData.setLms_fleet(rs.getString("lms_fleet"));
customerData.setAar_road(rs.getString("aar_road"));
customerData.setLocomotive_status_code(rs.getString("locomotive_status_code"));
return customerData;
}
}
And finally, I got my DaoImpl class here
//DaoImpl
public String getCustomersData(String locoId, String custName, String roadNumber) {
CustomerData resultSet = null;
String str = "";
if (locoId != null && locoId.length() > 0 && !(locoId.equals("0"))) {
str = "select locomotive_id,customer_name,road_number,model_type as locomotive_type_code,to_char(in_service_date,'yyyy-mm-dd') as in_service_date,loco_part_number as part_number, emission_tier_type as emission_tier_type, "
+ "air_brake_type as airbrake_type_code,lms_fleet,aar_road,locomotive_status_code from get_rdf_explorer.get_rdf_locomotive_detail where locomotive_id = ?";
resultSet = (CustomerData) jdbcTemplate.queryForObject(str, new CustomerDataResponseMapper(), locoId);
} else if ((custName != null && custName.length() > 0)
&& (roadNumber != null && roadNumber.length() > 0 && roadNumber != "0")) {
str = "select locomotive_id,customer_name,road_number,model_type as locomotive_type_code,to_char(in_service_date,'yyyy-mm-dd') as in_service_date,loco_part_number as part_number, emission_tier_type as emission_tier_type, "
+ "air_brake_type as airbrake_type_code,lms_fleet,aar_road,locomotive_status_code from get_rdf_explorer.get_rdf_locomotive_detail where customer_name = ? and road_number= ?";
resultSet = (CustomerData) jdbcTemplate.queryForObject(str, new CustomerDataResponseMapper(), custName, roadNumber);
} else {
str = "select distinct customer_name from get_rdf_explorer.get_rdf_locomotive_detail order by customer_name asc";
resultSet = (CustomerData) jdbcTemplate.queryForObject(str, new CustomerDataResponseMapper());
}
return resultSet.toString();
}
How can I conditionally get the values from the resultSet based on whether a particular column is present in the resultSet or not. As I am not getting all the columns all the time through my queries.
I am getting SQL bad grammar exception when specific column is not present in resultSet. For example when the third query to get distinct customer names get executed, in the resultSet only customerName would be there, but not the other columns.
It would be really a great help. Thanks a lot in advance.
Since you already have 3 separate queries why not have 3 separate RowMappers, one for each query. Your queries "know" what columns they return, so you can easily create those classes for RowMapper.
If you really want High-Fidelity solution you could create abstract base RowMapper for common parts and 3 subclasses for parts specifig to the query.
You can use a generic method which investigates the ResultSet's columns
#SuppressWarnings("unchecked")
public <T> T getColIfPresent(ResultSet rs, String columnName) throws SQLException {
ResultSetMetaData metaData = rs.getMetaData();
for (int i = 1; i <= metaData.getColumnCount(); i++) {
if (columnName.equals(metaData.getColumnName(i))) {
return (T) rs.getObject(columnName);
}
}
return null;// not present
}
Then, in row mapper.
customerData.setLocomotive_id(getColIfPresent(rs, "locomotive_id"));
...
This has O(n*m) complexity where n - the number of columns checked, m - the number of columns returned in ResultSet.
If you choose to ignore the SQLException, at least log it in DEBUG or TRACE level in case a different subtype of SQLException occurs, so that it's not lost.
Rather than conditionnaly getting columns, you could modify your SQL to match your mapper, like setting other field to empty string or null (I don't remmember if getString() crashes on null or something).
For example your third query would look like:
select distinct customer_name, null as "locomotive_id",'' as "road_number", null as model_type, [etc.] from get_rdf_explorer.get_rdf_locomotive_detail order by customer_name asc
So each query would have the same columns and you don't have to adapt. This is the solution if you d'ont really want/can't change the rowMapper (or want to have only one for this object).
But honestly I would go with ikketu's solution. You should make a separate mapper for the thrid query (plus, it wouldn't be complicated). Not goign with an ORM is a choice but you'll have redundancy problem anyway. I would even add that you should separate some of the logic in your code, this methods seems to be doing different thing (business logic depending on input, and database access) it's not very clear (after the third if, create a method like "getdistinctName()" or something).
Santosh, a quick workaround could be passing a flag to your rowmapper while supplying it to jdbcTemplate. I've done so many times to avoid multiple rowmapper.
resultSet = (CustomerData) jdbcTemplate.queryForObject(str, new CustomerDataResponseMapper(1), custName, roadNumber);
For the above changes, you need to overload constructor with the default one. Then you need to use your flag i.e. instance variable in mapRow() method to handle each situation separately.
You can use BeanPropertyRowMapper which will directly map field names of target class. Here is the javadoc.
The names are matched either directly or by transforming a name separating the parts with underscores to the same name using "camel" case. So, you can use it any other classes whenever you want to map directly to a class. Just have to make sure selected fields are remain in target class. And a default or no-arg constructor.
Following example to get CustomerData using BeanPropertyRowMapper
RowMapper<CustomerData> mapper = new BeanPropertyRowMapper<>(CustomerData.class);
List<CustomerData> result = jdbc.query("your query string...", mapper, query_args...);
So, then you can return first object or whatsoever.
My advice is to split your getCustomersData into three different methods. If you definitely want to ignore this advice, the quick and dirty solution is to protect the rs.getString(...) calls inside your rowMapper. Something like this:
try {
customerData.setLocomotive_id(rs.getString("locomotive_id"));
} catch (SQLException e) {
// ignore this exception. DO NOT TRY THIS AT HOME!
}
If numbers of columns are not fix then you should go with ColumnMapRowMapper based on its implementation even you do not require to create separate concrete class of RowMapper (i.e. CustomerDataResponseMapper ) you just need to pass instance of ColumnMapRowMapper in query as given below:
ColumnMapRowMapper rowMapper = new ColumnMapRowMapper();
List<Map<String, Object>> customerDataList = jdbcTemplate.query(sql,rowMapper, args);
Now you should create one method to manipulate this map like
private CustomerData fillCustomerDataFromMap(List<Map<String, Object>> customerDataList){
CustomerData customerData = new CustomerData();
for(Map<String, Object> map: customerDataList ){
customerData.setColumn(map.get("columnName"));
customerData.setColumn(map.get("columnName"));
customerData.setColumn(map.get("columnName"));
customerData.setColumn(map.get("columnName"));
.........
.........
.........
}
return customerData;
}
This is more readable and remove the boilerplate codes and not throw any exception if column name is not present in map (it will simply returns null if column name is not present in map)
Reference of ColumnMapRowMapper :
https://github.com/spring-projects/spring-framework/blob/master/spring-jdbc/src/main/java/org/springframework/jdbc/core/ColumnMapRowMapper.java
Based on the logic of your queries i see that before executing sql query in get_rdf_explorer.get_rdf_locomotive_detail there are some records and 3 options of getting of necessary (unique) record are possible:
by locomotive_id
by customer_name and road_number
ANY record (all records must have same customer_name, else SQL distinct without any conditions return more than 1 row)
So, in the 3rd option you can get any 1 record with all attributes equal to NULL and NOT NULL customer_name value:
str = "select null as locomotive_id, customer_name, null as road_number,
<other attributes> from get_rdf_explorer.get_rdf_locomotive_detail where
rownum = 1";`

MySQL query called from Java - configure the "WHERE" parameter

I am connecting to a mysql db from standalone java application. My application uses several filters that determine, which data will be selected from db.
In some cases, I would like to construct the select command in a way, that its "WHERE" parameter is ignored and selects all values from db instead.
This is my example:
String query = "SELECT * from messages WHERE type='" + type + "' ORDER BY id DESC";
The variable type can contain some specific type that matches the Varchar of the items in my db. However, a user can set the type to "all values" (or something like that, I hope this is clear enough), in which case, the query would select ALL values from db (it will ignore the where parameter).
I know I could do this by simply putting some if statements in my code and call a different select command in every branch, but this would be highly ineffective in case that several specifications (attributes inside WHERE parameter) are used.
For example:
String query = "SELECT * from messages WHERE type='" + type + "' AND time='" + time + "' ORDER BY id DESC";
I am not sure whether this is even possible to do. If not, sorry about dumm question... Thanks in advance!
I think you will have to do it through code, nothing in SQL to do what you want to do. Typically, people use ORM like Hibernate and construct the query in more secure way (to avoid SQL injection) instead of using String concatenation.
This is how it is done in Hibernate: http://docs.jboss.org/hibernate/orm/3.3/reference/en/html/querycriteria.html#querycriteria-narrowing
You could create a Type class and a Time class and so on. This classes would contain a function called getSQL() which returns the type or in case of all types "".
The WHERE clause let you filter the dataset according to several predicates. Not specifying it means no filtering, hence all possible values for any predicates you would add.
If you consider the WHERE clause as a predicate, and predicates as either sets of predicates or atomic predicates, you can easily produce your clause by walking through the predicate nest and generate the needed string. If the string comes back empty, just elide the WHERE clause altogether.
interface Predicate {
public String toString();
}
// further derive this class for specific predicates
class AtomPredicate implements Predicate {
public AtomPredicate(){}
public toString() {}
}
// basic predicate set
class SetPredicate implements Predicate {
public SetPredicate(String connector){this.connector = connector;}
private ArrayList<Predicate> set;
private String connector;
public toString(){
String res, tmp;
int i;
if (set.size() == 0) return "";
while (res.size() == 0 && i < set.size()) {
res = set[i++].toString();
}
for (; i < set.size(); ++i) {
tmp = set[i].toString();
if (tmp.size() > 0)
res += connector + " " + tmp;
}
if (res.size() > 0)
return "(" + res + ")";
}
class WhereClause {
public WhereClause() {}
private Predicate predicate;
public toString(){
String res = predicate.toString();
if (res.size() > 0) return "WHERE " + res;
return "";
}
}
You can start from that basic outline, and expand as needed. You should however try to look for an existing solution first, like the jboss library linked in another answer, to avoid reinventing the wheel.

Categories

Resources