This should have at least 3 entries in the array when I view it at a later stage, but it only displays one. I believe this is thee problematic method, any advice?
String[] getKidsNamebyCid(int cid) {
String[] out = new String[20];
try {
String qry = "SELECT KIDSNAME FROM TBLKIDS WHERE CID = ?";//setting query command
ps = connect.prepareStatement(qry);//preparing statement
ps.setInt(1, cid);//setting CID
ps.executeQuery();//running command
int i = 0;
while (ps.getResultSet().next()) {
out[i] = ps.getResultSet().getString("KIDSNAME");
i++;
}
} catch (SQLException se) {
se.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return out;
}
The getResultSet() call isn't a getter. That method does things to the DB, and you can't just repeatedly call it; the first time you get a ResultSet object (which you need to close), the second time everything is reset. So don't; you need to call getResultSet() once.
How do I know? By reading. Straight from getResultSet() documentation:
This method should be called only once per result.
Also this code is littered with severe code issues more generally focussed around resources. Resources are objects which aren't -just- a bunch of bits in memory, they represent (and hold open) a 'resource'. In the case of DBs, it's connections to the DB engine. You can't just make resources, you have to always put them in 'guarding' blocks that guarantee the resources are cleaned up. As a consequence, you never want them to be a field unless there's no other way (and then the thing they are a field inside of becomes a resource).
So, the fact that your PreparedStatement is a field? No good. The fact that you call .getResource just like that, unguarded? No good.
Finally, your exception handling is silly. The default act when facing checked exceptions is to just add them to your throws clause. If you can't do that, the right move is throw new RuntimeException("uncaught", e);, not what you did.
executeQuery already returns a resultset. Generally, never call getResultSet*.
Finally, arrays are more or less obsolete; you want collections.
Putting it all together:
// delete your 'ps' field!
List<String> getKidsNamebyCid(int cid) throws SQLException {
var out = new ArrayList<String>();
String qry = "SELECT KIDSNAME FROM TBLKIDS WHERE CID = ?";
try (PreparedStatement ps = connect.prepareStatement(qry)) {
ps.setInt(1, cid);
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) out.add(rs.getString("KIDSNAME"));
}
}
return out;
}
*) The PreparedStatement API is extremely unfortunate. The way you interact with a PS is wildly different vs. a Statement (which you should rarely use; you can't add user input to a plain jane Statement), and yet because reasons and history PreparedStatement extends Statement. That means a whole bevy of methods are in PreparedStatements that you should never call. That's unfortunate. There are 2 things to learn here: [1] Java does not break backwards compatibility, even if that means some of the APIs are horrible, and [2] JDBC is not really meant for 'human consumption'. We don't program our CPUs in machine code either, yet machine code exists and will continue to. We use 'machine code' as glue; something library and language developers use as common tongue. So it is with JDBC: It's not meant for you. Use a library with a nice API, like JDBI. This probably goes beyond what your high school curriculum wants, but hey. There's not much to say except: It's on your curriculum and teacher that they're making you work with outmoded tools 'real'** developers don't use.
**) 'real' in the sense of: Is writing code that powers apps that get a lot of dollars and/or eyeballs.
You need to learn how PreparedStatement actually works. I highly recommend you follow a tutorial to learn how to use it, then follow the pattern for your own code.
But it's also all in the documentation, so let be quote the various relevant pieces.
The javadoc of executeQuery() says:
Executes the SQL query in this PreparedStatement object and returns the ResultSet object generated by the query.
The code in the question is already wrong at this point, since it **ignores the return value of the executeQuery() call.
In addition, the javadoc of getResultSet() says:
Retrieves the current result as a ResultSet object. This method should be called only once per result.
The code in the question is even more wrong at this point, since it calls getResultSet() repeatedly in a loop.
If you had read the javadoc of the methods you're using, it would have been obvious that something is wrong. As already stated, going through a tutorial would have shown how to do this right. Actually, any web search for examples of executing a query using JDBC would show that.
For extra background information for how it works, the javadoc of execute() says:
Executes the SQL statement in this PreparedStatement object, which may be any kind of SQL statement. Some prepared statements return multiple results; the execute method handles these complex statements as well as the simpler form of statements handled by the methods executeQuery and executeUpdate.
The execute method returns a boolean to indicate the form of the first result. You must call either the method getResultSet or getUpdateCount to retrieve the result; you must call getMoreResults to move to any subsequent result(s).
The javadoc of getMoreResults() says:
Moves to this Statement object's next result, returns true if it is a ResultSet object, and implicitly closes any current ResultSet object(s) obtained with the method getResultSet.
The "return multiple results" is not talking about multiple rows from a single query, but about multiple results from multiple queries. It generally requires the execution of a stored procedure, or a block of SQL code, for this to happen.
This is how to correctly get the multiple rows from the execution of a single SELECT statement:
String qry = "SELECT KIDSNAME FROM TBLKIDS WHERE CID = ?";
try (PreparedStatement ps = connect.prepareStatement(qry)) {
ps.setInt(1, cid);//setting CID
try (ResultSet rs = ps.executeQuery()) {
int i = 0;
while (rs.next()) {
out[i] = rs.getString("KIDSNAME");
i++;
}
}
}
If the SQL code in question had returned multiple result sets, you would do it this way:
try (PreparedStatement ps = connect.prepareStatement(qry)) {
// call ps.setXxx() methods here
boolean isResultSet = ps.execute();
while (isResultSet) {
try (ResultSet rs = ps.getResultSet()) {
int i = 0;
while (rs.next()) {
// call rs.getXxx() methods here
i++;
}
}
isResultSet = ps.getMoreResults();
}
}
That is better written using for loops, to keep the loop logic together:
try (PreparedStatement ps = connect.prepareStatement(qry)) {
// call ps.setXxx() methods here
for (boolean isResultSet = ps.execute(); isResultSet; isResultSet = ps.getMoreResults()) {
try (ResultSet rs = ps.getResultSet()) {
for (int i = 0; rs.next(); i++) {
// call rs.getXxx() methods here
}
}
}
}
Related
So, a solution I created threw this exception: jdbc.SQLServerException: The result set has no current row on the line marked in the below code.
public String get64BitEncodedImageBySiteID(int siteID){
try {
Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
Connection conn = DriverManager.getConnection(url, userName, password);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery( "SELECT SitePicture FROM SiteTable WHERE SiteID ="+siteID );
rs.next();
// The above line has since been moved to the if statement below where you can see it commented out,
// which prevents the exception from occuring but still doesn't fix the fact that the row is not being found.
if(/*rs.next() &&*/ rs.getBytes("SitePicture")!=null){ // EXCEPTION THROWN HERE!
byte ba[] = rs.getBytes("SitePicture");
return new sun.misc.BASE64Encoder().encodeBuffer(ba);
}
else {return null;}
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
The method above, in the instance the exception was thrown, is taking a genuine siteID (22379) from an Entity object pulled directly from the same table. When using System.out.println(siteID); during this method, it declared that number to still be correct, ie still 22379. I've checked directly with the SQL server by running an identical statement in SQL Server, so I know the row exists in the table, but for some reason it is not being found. Image below.
So the problem is, the ResultsSet rs is not finding the row even though I know that it's there. Does anyone have any helpful insights?
Clarification: Just to be clear, I know that the ResultsSet contains no rows and that is why I am getting the exception. I also know that putting the rs.next() into the if statement will prevent the exception (as already stated in the comments). What is puzzling me is that the fact the ResultsSet contains no rows even though a row with the ID being parsed to it verifiably does exists because I have checked it directly with the SQL server.
This turned out to be a local mistake, but I'll post the solution anyway because this situation has some educational value.
As I've learned from #Ralph's comment to this answer, eliminating "the impossible" is a good way for such problems.
After avoiding the risk of siteID being wrong (by hardcoding it), we have a following situation:
the same exact query worked in one environment, but not the other, for only one particular SiteID, 2184
it's impossible that ResultSet just doesn't work for this particular value (I claim it is, because I always assume errors are in my code, not in language libraries)
if so, the databases must differ
Adding result statements inside while loop helped in my case.
while(rs.next) {
rs.getString("your column name");
}
The most likely explanation is that your ResultSet contains no rows. Have you checked that?
If that's the case, rs.next() will return false, but you are not checking the return value any more. Put rs.next() back into the if block, it was OK in there.
You can make sure by:
if (rs.next()) {
if(rs.getBytes("SitePicture")!=null){
byte ba[] = rs.getBytes("SitePicture");
return new sun.misc.BASE64Encoder().encodeBuffer(ba);
}
} else {
System.out.println("No rows returned");
}
EDIT:
what column type is siteID?
Your method takes an int, but your SQL wraps it in quotes, as if it were a string.
EDIT 2:
Using a PreparedStatement might solve your problem.
PreparedStatement ps = conn.prepareStatement("SELECT SitePicture FROM SiteTable WHERE SiteID = ?");
ps.setInt(1, siteId);
ResultSet rs = ps.executeQuery();
You may not get the result without checking whether the result has entries. For that use
while(rs.next()){
rs.getString("column name");
}
and try it. It worked fine for me.
Thanks
Is it possible to create a PreparedStatement in java without setting the initial SQL query?
Example code:
#Override
public List<AccountBean> search(AccountConstraint... c) {
if (c.length == 0) {
throw new IllegalArgumentException("dao.AccountDAO.search: c.length == 0");
}
try {
List<AccountBean> beans = new ArrayList<>();
for (AccountConstraint ac : c) {
PreparedStatement ps = connection.prepareStatement(null);
QueryBuilder queryBuilder = new QueryBuilder(ps, "SELECT * FROM accounts");
queryBuilder.add(ac.getAccountIdConstraint());
queryBuilder.add(ac.getUsernameConstraint());
queryBuilder.add(ac.getPasswordConstraint());
queryBuilder.add(ac.getEmailConstraint());
//INSERT QUERY INTO PS
ResultSet rs = ps.executeQuery();
while (rs.next()) {
beans.add(new AccountBean(rs));
}
}
return beans;
} catch (SQLException ex) {
throw new RuntimeException(ex);
}
}
The trick is in QueryBuilder, this class is responsible for building parts of a query based on the initial SELECT part and then adds respective WHERE and AND clauses.
However to ensure that all data is safe, the actual arguments must also be put in the PreparedStatement, hence why it is being passed to the QueryBuilder.
Every QueryBuilder.add() adds some arguments into the PreparedStatement and appends a specific string to the end of the query.
I think some workarounds are possible, such as instead of giving a PreparedStatement to the QueryBuilder you would give a List<Object> and then you would write a custom function that puts them in the PreparedStatement later on.
But what are your thoughts, suggestions on this?
Regards.
Solution added
Few key changes first:
QueryBuilder now implements the Builder pattern properly.
QueryBuilder.add() accepts multiple Constraints at once.
AccountConstraint can give an array that gives all Constraints now.
#Override
public List<AccountBean> search(AccountConstraint... c) {
if (c.length == 0) {
throw new IllegalArgumentException("dao.AccountDAO.search: c.length == 0");
}
try {
List<AccountBean> beans = new ArrayList<>();
for (AccountConstraint ac : c) {
try (PreparedStatement ps = new QueryBuilder("SELECT * FROM accounts").add(ac.getConstraints()).build();ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
beans.add(new AccountBean(rs));
}
}
}
return beans;
} catch (SQLException ex) {
throw new RuntimeException(ex);
}
}
ps. I get two statements in one try{ } because of the try-with-resources.
Preparing a statement means compiling it so you can efficiently execute it many times with different arguments. So, no it does not make sense to compile a query before it is defined.
As I understand, you want to use the Java compiler to assist you in dynamically defining the query. Why don't you just create the prepared statement in a compile() method, thus, as the result of your builder. Also, your code becomes more readable and more resembles a declarative query if you use the builder pattern such that each call to add() returns this. Then you can write your query like this:
PreparedStatement ps = new QueryBuilder()
.select("*")
.from("accounts")
.where()
.add(yourConstraint())
...
.compile();
However, you must create the prepared statement before the loop. Otherwise, if you keep a reference to the builder and call compile() in your loop you will get a new prepared statement on every call. So you won't get the benefit of reusing a precompiled query. In the loop you only assign values to the variables in your prepared statement.
You can't modify the prepared statement via the API after you crate it. You can't create it without an SQL statement either.
Why not create the query separately and then bind the parameters? You can use a Map to hold the parameter placeholders and their values so they can be set to the prepared statement.
Although I'd just use the Spring's JDBC templates to get the same thing done more quickly.
How to improve your SQL query builder
If you look at how popular query builders like jOOQ and others do it, the idea is that you separate your concerns more thoroughly. You should have:
An expression tree representation of your SQL statement (and ideally that doesn't directly operate on strings)
A way to construct that expression tree conveniently, e.g. by using a DSL
Some sort of execution lifecycle management that generates the SQL string, prepares the statement, binds the variables, etc.
Or in code (jOOQ example, but this could also apply to your own query builder):
Result<?> result =
// This constructs the expression tree through the jOOQ DSL
ctx.selectFrom(ACCOUNTS)
.where(ac.getAccountIdConstraint())
.and(ac.getUsernameConstraint())
.and(ac.getPasswordConstraint())
.and(ac.getEmailConstraint())
// This internally creates a PreparedStatement, binds variables, executes it, and maps results
.fetch();
Of course, your AccountConstraint.getXYZConstraint() methods would not return SQL string snippets, but again expression tree elements. In the case of jOOQ, this would be a Condition
(Disclaimer: I work for the vendor of jOOQ)
How to improve your SQL performance
I've noticed that you run N queries for N AccountConstraint values, and you mix the results in a way that it doesn't matter which AccountConstraint value produced which AccountBean. I strongly suggest you move that loop into the generated SQL query, as you're going to get much faster results on pretty much every database. I've blogged about this here.
I am trying to write java code to access a table 'customer' with columns 'customer_id', 'email', 'deliverable', and 'create_date'
I have
Connection conn = DriverManager.getConnection(connectionUrl, connectionUser, connectionPassword);
Statement constat = conn.createStatement();
String query = "SELECT * FROM customer WHERE customer_id LIKE " + customerId;
ResultSet rtn = constat.executeQuery(query);
Customer cust = new Customer(rtn.getInt("customer_id"), rtn.getString("email"), rtn.getInt("deliverable"), rtn.getString("create_date"));
conn.close();
return cust;
I am receiving the error:
java.sql.SQLException: Before start of result set
As far as I can tell, my error is in the line where I am creating a new Customer object, but I cannot figure out what I am doing wrong. Can anyone offer me some help? Thanks!
You must always go to the next row by calling resultSet.next() (and checking it returns true), before accessing the data of the row:
Customer cust = null;
if (rtn.next()) {
cust = new Customer(rtn.getInt("customer_id"),
rtn.getString("email"),
rtn.getInt("deliverable"),
rtn.getString("create_date"));
}
Note that you should also
use prepared statements instead of String concatenation to avoid SQL injection attacks, and have more robust code
close the connections, statements and resultsets in a finally block, or use the try-with-resources construct if using Java 7
Read the JDBC tutorial
You should call ResultSet.first() to move the result to the first position. The result set is a programming convention not to retrieve the whole result of the query and keep in memory. As such, its interface is quite low level and you must explicit select the row via methods like first(), last() or next() (each returns true to check if the requested row index is in the set)
You need to add
rtn.next();
before you use the result set.
Usually this is done as
while (rtn.next()) {
<do something with the row>
}
The scenario is like this:
for loop // runs say 200000 times
{
// here, i do a select from a database, fetching few rows which are expected to increase with every new iteration of for loop
// currently i am doing this select using simple JDBC call (using JDBC only is NOT a requirement)
// then i do some string matching stuff and then i either insert or update a particular row (in 95% cases i will insert)
// this insert or update is being done using Hibernate (using Hibernate over here is a requirement)
}
So the problem is, in every loop, I have to consider the each and every previously inserted/updated row. Due to this requirement, I have to do a JDBC call in each and every loop. And this JDBC call is taking the maximum time, bringing down the performance.
I want to know, is there any method using which I do not have to make a JDBC call in each iteration, but still I will be able to consider all the records including the one in the just previous insert/update? Anything like caching or some in-memory data structure or something like that?
Here is the code:
for loop // runs say 2000 times
{
String query = pdi.selectAllPatients(patientInfo);
Statement st = conn.createStatement();
ResultSet patientRs = st.executeQuery(query);
while (patientRs.hasNext())
{
// some string ops
}
// Create session for DB No.2
Session sessionEmpi = sessionFactoryEmpi.getCurrentSession();
sessionEmpi.beginTransaction();
if(some condition)
patientDao.insertPatient(patientInfo, sessionEmpi);
else
patientDao.insertref(patientInfo.getref(), sessionEmpi);
conn.commit();
}
public int insertPatient(PatientInfo input, Session session) throws SQLException {
try {
session.save(input.getPatient());
session.flush();
session.save(input.getref());
session.getTransaction().commit();
return 1;
} catch (Exception ex) {
session.getTransaction().rollback();
ex.printStackTrace();
return 0;
}
}
Is the performance of the SELECT consistent? Unless your data is fairly small, you'll likely have trouble caching all your changes in memory. You may also be able to batch the SELECTs, effectively unrolling the loop.
You can use the PreparedStatement interface instead of Statement interface as it avoids the unnecessary calls for firing the query to the database you just have to bind the data in for loop this will help you to improve performance!!
example:
PreparedStatement s =con.prepareStatement("select * from student_master where stu_id = ?");
for()
{
s.setString(1,"s002");
ResultSet rs = s.executeQuery();
}
Does anyone know a better way of getting the number of rows in a Java resultset returned from a MySQL database? The resultset returned is not going to be the total number of rows read from the database so I don't think I can use SQL's COUNT aggregate function.
public static int getResultSetRowCount(ResultSet resultSet) {
int size = 0;
try {
resultSet.last();
size = resultSet.getRow();
resultSet.beforeFirst();
}
catch(Exception ex) {
return 0;
}
return size;
}
A better answer is to forget about the number of rows until you've successfully loaded the ResultSet into an object or collection. You can keep the count of the number of rows with either of those options.
It's important to close ResultSets (and all SQL resources like Connection and Statement) in the narrowest method scope possible. That means not passing ResultSet out of the persistence layer. Better to get the number of rows using a Collection size() call.
Stop thinking about databases and start thinking in terms of objects. Java's an object-oriented language.
You can execute
SELECT FOUND_ROWS()
immediately after executing your SELECT statement to find the row count.
If you are using Java 6 you can use the JDBC4ResultSet class which has the getUpdateCount method that returns the number of the lines affected by a SQL Statement even for a Select Statement.
See below the example code:
PreparedStatement ps = con.prepareStatement("select * from any_table ...");
JDBC4ResultSet rs = (JDBC4ResultSet)ps.executeQuery();
int rowNumber = rs.getUpdateCount();
I hope that this help!
You can always use SELECT COUNT() with the same exact conditions, before making the actual SELECT.
Here is my solution to this question (since I mostly want the number of records returned to build an array or something similar): Use a collection such as Vector<T> instead.
public Vector<T> getRecords(){
Vector<T> records = new Vector<T>();
init_conn_and_stmt_and_rs();
try {
rs = stmt.executeQuery("SELECT * FROM `table` WHERE `something` = 0");
while(rs.next()){
// Load the Vector here.
}
} catch (SQLException e) {
e.printStackTrace();
}finally{
close_rs_and_stmt_and_conn();
}
return records;
}
Clean and simple, no? Works for any size record set returned (no need to know the size before hand) and makes all the List methods available.
This has served me well for a time now, but if someone sees a flaw in this, please let me know. Always want to make my practices better, ya know.
You can also use the following way to get the total records in the resultSet:
statement = connect.createStatement();
resultSet = statement.executeQuery(sqlStatement);
resultSet.last();
int count = resultSet.getRow();
System.out.println(count);
count is the total returned rows for your result set. ;)