I'm working on a small program that lists local train stops in a numbered list then asks for the user to type the number of the station that they wish to see the next arrival time for.
The problem I have is I don't think the MySQL query is correct to retrieve the arrival time. The list returns empty. Using jdbc previously, this query worked fine:
"SELECT arrival_time FROM stop_times WHERE stop_id = '"
+ myStation.getID()
+ "' AND arrival_time > time('now', 'localtime') ORDER BY arrival_time asc;";
And the current hibernate query:
public List<String> getArrivals() {
sessionFactoryBean.getCurrentSession().beginTransaction();
String sql = "SELECT arrival_time FROM stop_times WHERE stop_id = '"
+ myStation.getID()
+ "' AND arrival_time > time('now', 'localtime') ORDER BY arrival_time asc;";
Query query = sessionFactoryBean.getCurrentSession()
.createSQLQuery(sql)
.addEntity(Station.class);
List<String> arrivals = query.list();
sessionFactoryBean.getCurrentSession().getTransaction().commit();
return arrivals;
}
Called from this method and where I get IndexOutOfBoundsException:
public String getNextArrival(int user_input) {
getStationName(user_input);
List<String> arrivals1 = arrival.getArrivals();
System.out.println(arrivals1);
System.out.println(arrivals1.size());
String arrivalTime = arrivals1.get(user_input);
return convertTime(arrivalTime);
}
Exception in thread "main" java.lang.IndexOutOfBoundsException: Index: 0,
Size: 0
at java.util.ArrayList.rangeCheck(ArrayList.java:653)
at java.util.ArrayList.get(ArrayList.java:429)
at com.moeller.code.Stops.getNextArrival(Stops.java:73)
Line 73 String arrivalTime = arrivals1.get(user_input);'
The DataBase is stored locally.
There are several problems with this.
First, the query does not use parameters, which means you will likely make this mistake elsewhere where it will be a danger. You have to pass on variables like this:
String sql = "SELECT arrival_time FROM stop_times WHERE stop_id = ?"
+ " AND arrival_time > time('now', 'localtime') ORDER BY arrival_time asc;";
Query query = sessionFactoryBean.getCurrentSession()
.createSQLQuery(sql)
.addEntity(Station.class);
query.setParameter(1, myStation.getID());
See the question mark? That is a positional parameter. You can also use named parameters.
String sql = "SELECT thing FROM table WHERE column1 LIKE :ptrn";
...
query.setParameter("ptrn", "%that%");
Notice how inside the query the parameter starts with :, but it does not when calling setParameter.
This way of safely inserting parameters is called using "Prepared Statements", or "Parameterized Queries". Find a quick tutorial on them, they are very important.
Secondly, in getNextArrival you forget to check if the list has that many elements.
if (arrivals1.size() <= user_input) {
return null;
}
Of course then you have to be careful when it returns a null to the function where it's used.
You are using a wrong method for the List.
when yo use List.get(param) param should be the position that you are looking for, no the userInput.
you need loop the list and compare each position of the list with the user input.
best Regards
Related
I'm very, very new to Hibernate and JPA. I want to be able to apply ORDER BY and LIMIT clauses to a Hibernate(?) query, but am coming up empty. NOTE: I inherited this code.
Here is the current code:
public SomeCoolResponse getSomeCoolResponse(String myId) {
String queryString = "select aThing from AWholeBunchOfThings aThing " +
"join aThing.thisOtherThing oThing join oThing.StillAnotherThing saThing " +
"where saThing.subthing.id = :id";
Query q = getEntityManager().createQuery(queryString);
q.setParameter("id", myId);
List<MyThings> list = q.getResultList();
if(list.size() > 0) {
return list.get(0);
}
return null;
}
Instead of getting an entire list and then just returning the first result (which is the only one we need), I'd like to be able to apply a LIMIT 0,1 clause so that the query will be faster. Also, the query needs to be sorted descending on aThing.created which is a UNIX timestamp integer.
I've tried altering queryString like this:
String queryString = "select aThing from AWholeBunchOfThings aThing " +
"join aThing.thisOtherThing oThing join oThing.StillAnotherThing saThing " +
"where saThing.subthing.id = :id ORDER BY aThing.created LIMIT 0,1";
But Hibernate still returns the entire set.
I've looked at using the JPA CriteriaBuilder API, but it hurt my brain.
I'm a total n00b when it comes to this, and any help is greatly appreciated!
I think you need
q.setMaxResults(1);
See also the accepted answer here.
How do you do a limit query in HQL?
As to the "order by" clause you may include it in the queryString.
The JPQL equivalent to LIMIT start,max is:
setFirstResult and setMaxResults:
q.setFirstResult(start);
q.setMaxResults(limit);
Edit: Cleaning up by removing details not relevant to the problem.
The problem. JPA query returns no results.
String qstr = "select o from MyStats o where o.queue_name = :queue";
String queue = "3";
em.createQuery(qstr).setParameter("queue", queue);
I thought the problem was either in an incorrect syntax of the JPA query or in incorrect annotation of EmbeddedID. Hence I posted definitions of classes involved but told nothing about database table apart from that it was Oracle.
My test code: Read from DB, take first value and re-use that value in subsequent select query meaning that record exists. Should be there, it was just read, right?
Test
String queue = "";
String qstr1 = "select o from MyStats o";
String qstr2 = "select o from MyStats o where o.queue_name = :queue";
logger.debug("SQL query: " + qstr1);
List<MyStats> list = em.createQuery(qstr1).getResultList();
logger.debug("111 Returning results: " + list.size());
for (MyStats s : list) {
queue = s.getQueue_name();
logger.debug("Picking queue name: " + queue);
break;
}
logger.debug("SQL query: " + qstr2);
list = em.createQuery(qstr2).setParameter("queue", queue).getResultList();
logger.debug("222 Returning results: " + list.size());
Output:
SQL query: select o from MyStats o
111 Returning results: 166
Picking queue name: 3
SQL query: select o from MyStats o where o.rec_id.queue_name = :queue
222 Returning results: 0
Class definition
#Entity
public class MyStats {
private String queue_name;
private long stats_id;
... //getters and setters
}
A query without WHERE clause works correctly so as a query with a member of MyStats class.
em.createQuery("select o from MyStats o where o.stats_id = :sid").setParameter("sid", 179046583493L);
I am using Oracle 10 database, Java EE 5 SDK, Glassfish 2.1.
The problem appeared to be with the mapping of Java String type to database column CHAR type.
Database table queue_name column is defined as CHAR(20), while Java type is String.
There are few options to fix it
Replace database column CHAR type with VARCHAR
Pad query parameter value with spaces for every request
Use LIKE condition instead of equals = and add % to the end of parameter value
Speculative: Use cast
(1) Acceptable if you have control over the database table
(2) Works for the given select statement, possibly breaks for JOINs
(3) May fail to do the trick. LIKE 'a%' returns not only 'a ' but 'aa ', 'abc ', and so on
(4) This is not completely clear to me. I am not sure if it is possible to adopt:
em.createNativeQuery("select cast(queue_name as CHAR(20)) from ...");
I am using postgres 9.1 and java code for jdbc.
I may use a order by clause in my sql query string
I just want to get the meta data information of the query to find whether the query has order by clause or not. If it has then how many fields has been specified in the order by clause.
Ex:
order by age
order by age, name
order by age asc, name desc
In these example I just want to retrieve the number of parameters that are specified in the order by clause and their column names.
If your are getting your query as string you could simply parse it.
i.e. To figure out that ORDER BY is there
"SELECT * FROM MyTable ORDER BY SomeColumn".toLowerCase().indexOf("order by") // if it's return -1 query does not contains order by section otherwise it returns start index for first occurence "ORDER BY" in given string
For more complex searching in string you may need to use RegExp
You can do it by breaking an SQL query into part and then reassigning.
Like
String sql="SELECT NAME,COMPANY,FNAME,AGE FROM COMP_DATA JOIN PERSONAL_DATA WHERE (1=1) AND FNAME='Vaibs' ORDER BY AGE";
While writing in JAVA do as below.
Break Whole query into String parts and recombine it like this.
String strSQL = "SELECT " + "NAME"+",COMPANY"+",FNAME"+",AGE" + "FROM "
+ getTableName1(); //getTableName1() return tablename
strSQL+="JOIN "+ getTable2()+"";//getTable2() return tablename as well
String strWhere = " WHERE (1=1) " + " and FNAME='" + fname+ "';
String orderBySQL = " Order by " + i_will_return_string_to_order_by();
//return AGE in our case
String FinalString= strSQL +strWhere +orderBySQL ;
SOP order by to get what you want.
Hope that helped.
I am trying to implement PreparedStatement, which won't work with sql DB.
Suppose I have the following sql query:
String selectSqlQuery = "SELECT * FROM customer WHERE f1 = ? AND f2 =? AND f3 > ?";
and the following code:
//----
prest = con.prepareStatement(selectSqlQuery );
prest.setString(1, "val1");
prest.setString(2, "val2");
prest.setInt(3, 108);
ResultSet rs = prest.executeQuery();
//---
My question is how to implement setString and setInt methods for injecting params?
For now I save parameters' indexes and values into HashMap, but after it I can't make injection into sql query string.
implementation of sql's java interfaces are part of vendor specific jdbc driver. You probably just need to get the proper jdbc jar file for you database. writing implementations of such stuff is usually just needed if you intend to write your own database driver...
Since you're writing your own driver, you can play with your class a little. Let's change the approach. If you have a query like this one:
"SELECT * FROM table WHERE id = ? AND name = ?"
Replace the ? to turn it into
"SELECT * FROM table WHERE id = {0} AND name = {1}"
About your set methods, those will have to save your new parameters in an Object array, again matching against the index.
Object parameterArray = new Object[1];
public boolean setString(int paramIndex, String param) {
if(paramIndex < 0 || paramIndex > parameterArray.length)
throw new IllegalArgumentException("Can't set parameter " + paramIndex + ", The query only has " + parameterArray.length + " parameters.");
parameterArray[paramIndex - 1] = param;
}
Before executing the query, take advantage of your formatted string and set the parameters:
MessageFormat messageFormat = new MessageFormat(query);
String newQuery = messageFormat.format(parameterArray);
The format method will replace the {number} substrings for the corresponding element in the index represented by the number between brackets.
I have the following problem:
I have two tables in one data base which consist of the same columns besides the name of the last column. I want to write data into them using Java.
I want to use the same preparedStatement for both tables, where I check with an if-command whether it is table1 or table2. table2 has amount10 as the name for the last column, table1 has amount20 for it. This number is stored in a variable within my code.
Below you can see a (simplified) example and how I tried to let the column name variable but it doesn't work. Is there any way to fix this without copying the whole statement and manually changing the number variable?
String insertData = "INSERT INTO `database`.`"+table+"`
(`person_id`,`Date`,`amount`+"number") VALUES "+
"(?,?,?) ON DUPLICATE KEY UPDATE " +
"`person_id` = ? , " +
"`Date` = ? , " +
"`amount`+"number" = ? ; ";
PreparedStatement insertDataStmt;
This will not work since variables number and table are not going to be magically injected into your insertData string while you are changing them.
I'd to a method prepareInsertstatement(String table, String number) that would return correct PreparedStatement:
public void prepareInsertStatement(Connection conn, Strint table, String number) {
String insertData = "INSERT INTO `database`.`"+table+"`
(`person_id`,`Date`,`amount+"number"') VALUES "+
"(?,?,?) ON DUPLICATE KEY UPDATE " +
"`person_id` = ? , " +
"`Date` = ? , " +
"`amount+"number"' = ? ; ";
PreparedStatement insertDataStmt = conn.prepareStatement(insertData);
return insertDataStmt;
}
Just remember to close the PreparesStatement when you don't need it any more.
I suppose that reason for that is invalid syntax. When you concatenate string for last column name you use code 'amount' + number. If your number value is 20, than concat result will be
'amount'20 that cause invalid syntax exception. Just move one extra ' after number.
"'amount" + number + "'"
Note: log, or just error that appears during this statement execution would be very useful to find right answer for your question.