I am writing a Java class to insert data to a database with an iteration. Although I can now insert data, I am struggling in handling Oracle errors. In this case, I have deliberately created a Primary Key Constrain error by trying to insert duplicate primary keys (I have pre-loaded the database with the same entries as I am trying to insert with Java)
So as expected, I get the "ORA-00001: unique constraint". However, the problem I am having, is that after 300 iterations, I reach a new error:"ORA-01000: maximum open cursors exceeded"
I guess the issue is that every failed executeUpdate() keeps a cursor open.
I have temporarily solved the issue by including a close() statement on the catch of the error. However, I am wondering:
Should the failed executeUpdate() not be closing the cursor?
Is there a better way I can close cursors on Exception?
Why does it return a null Exception?
My Java Code:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class TestOracleError {
private static Connection conn;
public static void main(String[] args) {
//Connect
try {
conn = DriverManager.getConnection("jdbc:oracle:thin:"+
"#XXX.XXX.XXX.XXX:1521:XXXX", "XXXX", "XXXX");
}
catch(SQLException e) {
System.err.println(e.getMessage());
}
//Iterate insert
for(int i=1; i<5000; i++){
PreparedStatement pstmt=null;
try {
//Deliberate error on the SQL (Primary Key Constraint)
pstmt = conn.prepareStatement("INSERT INTO DUMMY (ID) VALUES "
+"('"+i+"')");
pstmt.executeUpdate();
pstmt.close();
}
catch(Exception e) {
System.err.println("DB Error on iteration "+i+": " +
e.getMessage());
//If I add the following line then I can close the error!
//try {pstmt.close();} catch (Exception e1) {}
}
}
}
}
If you need to insert many rows put the creation of prepared statement outside the loop and set only the values inside the loop.
// Moved outside
PreparedStatement pstmt=null;
// Using the question mark as a placeholder for a variable
pstmt = conn.prepareStatement("INSERT INTO DUMMY (ID) VALUES (?)");
for (int i = 1; i < 5000; i++) {
try {
//Deliberate error on the SQL (Primary Key Constraint)
// Only set the variable in the loop
pstmt.setInt(1, i);
pstmt.executeUpdate();
} catch (Exception e) {
System.err.println("DB Error on iteration "+i+": " +
e.getMessage());
//If I add the following line then I can close the error!
//try {pstmt.close();} catch (Exception e1) {}
}
}
pstmt.close(); // Moved out of loop
Note: Your code don't close the pstmt if an exception happens. So statements remains opened. This can potentially create the problem of too many open cursors.
Generally the best solution is to close the resources in a finally block or use a try with resource statement
Use finally or Try with resources.
try {
//Deliberate error on the SQL (Primary Key Constraint)
// Only set the variable in the loop
pstmt.setInt(1, i);
pstmt.executeUpdate();
} catch (Exception e) {
System.err.println("DB Error on iteration "+i+": " +
e.getMessage());
//If I add the following line then I can close the error!
//try {pstmt.close();} catch (Exception e1) {}
}
finally
{
pstmt.close();
}
or
try (Connection con = DriverManager.getConnection(myConnectionURL);
PreparedStatement ps = con.prepareStatement(sql);) {
......
}
} catch (SQLException e) {
e.printStackTrace();
}
I guess the issue is that every failed executeUpdate() keeps a cursor open.
No, the issue is that you aren't closing your PreparedStatements if an SQLException occurs.
Should the failed executeUpdate() not be closing the cursor?
No.
Is there a better way I can close cursors on Exception?
Close the PreparedStatement in a finally block, or with the try-with-resources syntax.
Why does it return a null Exception?
It doesn't. It throws an exception with a null message.
Instead of preparing 5000 PreparedStatements you should also investigate batches.
Related
I have a little concern with Objet PreparedStatement in java to insert in an Oracle database.
Infect I prepare well the model of my INSERT query in the PreparedStatement I add well all my parameters with an addBatch() for each record I want to insert.
I add several batches to insert a 500 record hits for example.
Until then all of them work well I can insert what I want
On the other hand, in case my PreparedStatement generates a BatchUpdateException error (for example violation of constraint) on the 500 line that I want to insert it inserts me nothing at all.
I want to the limit remove the record that raises concern (with violation constraint) and insert at least the 499 line that are OK
How can I do that ? if she gives me a track I'd be grateful.
Just for Info I want to insert several lines of a stroke from 500 lines, so the solution to insert line by line does not fit me too much performance level.
Cordially
maybe this is not what you want but oracle has some built in error logic
you have to create an error table
e.g. if the table is called emp, run this
exec dbms_errlog.create_error_log(dml_table_name=>'emp');
that will create a table err$_emp that will catch the errors
then you can do something like this below(note the log errors into clause)
the batch will succeed and you will have to check the error table for errors after you run it
import java.sql.*;
public class Class1 {
public static void main(String[] args) throws SQLException {
try {
Class.forName("oracle.jdbc.driver.OracleDriver");
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
PreparedStatement preparedStatement;
int records = 20;
try {
Connection connection = DriverManager.getConnection(
"jdbc:oracle:thin:#//host/db","scott","tiger");
String compiledQuery = "INSERT INTO EMP(EMPNO)" +
" VALUES" + "(?) LOG ERRORS INTO ERR$_EMP REJECT LIMIT UNLIMITED";
preparedStatement = connection.prepareStatement(compiledQuery);
for(int index = 1; index <= records; index++) {
preparedStatement.setInt(1, index);
preparedStatement.addBatch();
}
long start = System.currentTimeMillis();
int[] inserted;
try {
inserted = preparedStatement.executeBatch();
}
catch (SQLException e)
{
System.out.println("sql error");
}
long end = System.currentTimeMillis();
System.out.println("total time taken to insert the batch = " + (end - start) + " ms");
System.out.println("total time taken = " + (end - start)/records + " s");
preparedStatement.close();
connection.commit();
connection.close();
} catch (SQLException ex) {
System.err.println("SQLException information");
while (ex != null) {
System.err.println("Error msg: " + ex.getMessage());
ex = ex.getNextException();
}
throw new RuntimeException("Error");
}
}
}
private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
ConnectionDTB cdtb = new ConnectionDTB();
ResultSet rs = cdtb.retrieveData("select svcode from listSV");
if(!rs.isBeforeFirst()){
System.out.println("System is null");
//add
cdtb.close();
}
else{
while(rs.next()){
if(Integer.parseInt(jTextField1.getText()) == rs.getInt("svcode")){
System.out.println("ERROR");
cdtb.close();
break;
}
else{
if(rs.isLast()){
//add
cdtb.close();
}else{
cdtb.close();
}
}
My error is
Apr 25, 2015 2:21:29 AM GUI.Them1 jButton1ActionPerformed
SEVERE: null
java.sql.SQLException: Operation not allowed after ResultSet closed
What did I do wrong?
It looks like you are closing the database connection inside your loop, before you are done with the result set.
You can't close the database connection if you are still using statements or resultsets associated with the database connection. (Well, you can do it, but closing the connection will also close the resultset, and you get the kind of behavior you observe.)
The normative pattern is to use try/catch/finally blocks, and to close the resultset(s), statements(s) and connection in the finally block(s).
For example:
ConnectionDTB cdtb = null;
ResultSet rs = null;
try {
cdtb = new ConnectionDTB();
rs = cdtb.retrieveData("select svcode from listSV");
while(rs.next()){
// whatever processing you need to do on each row, but
// do NOT close the result set here
// do NOT close the database connection here!
}
} catch (SQLException ex) {
// anything you want to do if an exception is thrown
} finally {
if (rs != null) {
try { rs.close(); } catch (SQLException e) { /*ignore*/ }
}
if (cdtb != null) {
try { cdtb.close(); } catch (SQLException e) { /*ignore*/ }
}
}
That finally block could be simplified, removing the unnecessary try/catch. This would be fine too:
} finally {
if (rs != null) {
rs.close();
}
if (cdtb != null) {
cdtb.close();
}
}
The important thing here is to do the closing in the "finally" block, so that's going to be run even if an exception occurs. And with this pattern, there is one "close" of the database connection, it's not multiple calls scattered through your code.
Some other notes:
It looks as if your code means to "add" a row if one doesn't already exist. This is a lot of overhead, pulling back every value from the database and inspecting it.
It would be much more efficient to ask the database if such a row exists. To ask the database whether a row like that exists, use a statement like this:
SELECT svcode FROM listSV WHERE svcode = ?
Prepare the statement, bind a value to the placeholder (the value you are looking for), and execute the statement, and check if a row is returned or not.
If you are performing this check to see whether a row needs to be added to the listSV table, you could actually use a single INSERT ... SELECT statement to conditionally insert a row.
Although I don't think its actually shown from the information provided, I think that you are closing the result set (in this case 'rs') and then attempting to run an operation again.
If I had to guess as well, I'd say take out the rs.close because it is closing the result set prematurely. Maybe replace with a continue if you wanted to keep reading from the same set?
private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
ConnectionDTB cdtb = new ConnectionDTB();
ResultSet rs = cdtb.retrieveData("select svcode from listSV");
if(!rs.isBeforeFirst()){
System.out.println("System is null");
//add
cdtb.close();
rs.close();
}
else{
while(rs.next()){
if(Integer.parseInt(jTextField1.getText()) == rs.getInt("svcode")){
System.out.println("ERROR");
cdtb.close();
rs.close();
break;
}
else{
if(rs.isLast()){
//add
cdtb.close();
rs.close();
}
}
is this right?
by the way could u tell me AfterLast meaning?
public class StudentDataPersistence {
public void insertStudentInfo(Student student) {
String url = "jdbc:oracle:thin:#localhost:1521:XE";
String username = "system";
String password = "Data03#";
Connection connection = null;
//Statement statement = null;
try {
//Step 1 : Register JDBC driver
Class.forName("oracle.jdbc.driver.OracleDriver");
//Step 2 : Open a connection
System.out.println("Connecting to a selected database...");
connection = DriverManager.getConnection(url, username, password);
if (connection != null) {
System.out.println("Connected to oracle");
}
//Step 3 : Write code to map Java Object to the Student_Info table
System.out.println("Inserting records into the database");
statement = connection.createStatement();
String sql = "insert into Student_Info " +
"VALUES(student.getName(),student.getRoll_no(),student.getAddress(),student.getPhone_no())";
statement.executeUpdate(sql);
System.out.println("Inserted student information into the database");
} catch (SQLException se) {
//handle errors for JDBC
se.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
//Handle errors for Class.forName
} finally {
System.out.println("Inside the finally block");
//finally block used to close resources
try {
statement.close();
} catch (SQLException se) {
se.printStackTrace();
}
try {
connection.close();
} catch (SQLException se) {
se.printStackTrace();
}
}
System.out.println("!GoodBye");
}
public static void main(String[] args) {
Student student = new Student("Bavin", 1, "Umar Nagar", "89898989809");
StudentDataPersistence obj = new StudentDataPersistence();
obj.insertStudentInfo(student);
}
}
The error it shows it :
Connecting to a selected database...
Connected to oracle
Inserting records into the database
java.sql.SQLException: ORA-00904: "STUDENT"."GETPHONE_NO": invalid identifier
at oracle.jdbc.dbaccess.DBError.throwSqlException(DBError.java:189)
at oracle.jdbc.ttc7.TTIoer.processError(TTIoer.java:242)
at oracle.jdbc.ttc7.Oall7.receive(Oall7.java:554)
at oracle.jdbc.ttc7.TTC7Protocol.doOall7(TTC7Protocol.java:1478)
at oracle.jdbc.ttc7.TTC7Protocol.parseExecuteFetch(TTC7Protocol.java:888)
at oracle.jdbc.driver.OracleStatement.executeNonQuery(OracleStatement.java:2076)
at oracle.jdbc.driver.OracleStatement.doExecuteOther(OracleStatement.java:1986)
at oracle.jdbc.driver.OracleStatement.doExecuteWithTimeout(OracleStatement.java:2697)
at oracle.jdbc.driver.OracleStatement.executeUpdate(OracleStatement.java:1035)
at org.core.hibernate.reason.StudentDataPersistence.insertStudentInfo(StudentDataPersistence.java:52)
at org.core.hibernate.reason.StudentDataPersistence.main(StudentDataPersistence.java:80)
Inside the finally block
!GoodBye
All the answers (those of you who illustrate it with an oracle query) in reply were wrong.
Kindly do have a look at it before posting.
the correct one i got when i posted another thread regarding the same:
String query = "insert into Student_Info(name,roll_no,address,phone_no) VALUES('"+student.getName()+"',"+student.getRoll_no()+",'"+student.getAddress()+"','"+student.getPhone_no()+"')";
you have commented out your Statement object definition. So the statement object is unknown when you're using it.
uncomment this line:
//Statement statement;
And as earlier pointed out by #putaro, you need to quote certain parts of your SQL query.
String sql = "insert into Student_Info " +
"VALUES("+student.getName()+","+student.getRoll_no()+","+student.getAddress()+","+student.getPhone_no()+")";
This is to insert the actual object values into the query. Things within the quote would be inserted as it is.
Error ORA-00904 means Oracle does not know the identifier "STUDENT"."GETPHONE_NO" it looks like you are trying to insert some value to a column named "GetPhone_NO" to Table "Student" from your SQL. so you should check your SQL and table structure again
I see there are two problems in the code.
Currently your code is not using the student object while making the query. All student.getName() etc call taken as plain strings rather than method calls that returns the appropriate values.
Second it would be better to write the query in the following form. It will avoid silly errors because of the structure of the tables.
"INSERT INTO student_info(name,roll_no,address,phone) VALUES("+
student.getName()+"," +
student.getRoll_no()+","+student.getAddress()+","+student.getPhone_no()+")";
Even better is if you use prepared statement like
Try changing the query like
"INSERT INTO student_info(name,roll_no,address,phone) VALUES(?,?,?,?)"
and then set the parameter values.
I can't seem to figure out why I'm getting this exception: SQLException: Parameter index out of range (1 > number of parameters, which is 0). My mysql connector version is 5.1.29. Is this possible a problem with this version, or am I not properly setting up the query?
Scratch:
public void actionPerformed(ActionEvent e) {
String query = "INSERT INTO racebikes"
+ "(bikename, country_of_origin, cost) VALUES"
+ "(?,?,?)";
try {
statement.setString(1, (String)winners_combo_box.getSelectedItem());
statement.setString(2, winner_fields[0].getText());
statement.setFloat(3, Float.parseFloat(winner_fields[1].getSelectedText()));
statement.executeUpdate(query);
} catch(SQLException e1) {
e1.printStackTrace();
}
}
You're not assigning a value to statement in that method, and you're using the overload of executeUpdate from Statement... rather than the parameterless one in PreparedStatement.
I suspect you actually want to assign the statement using Connection.prepareStatement:
public void actionPerformed(ActionEvent e) {
String query = "INSERT INTO racebikes"
+ "(bikename, country_of_origin, cost) VALUES"
+ "(?,?,?)";
// TODO: Connection management?
PreparedStatement statement = conn.prepareStatement(query);
try {
statement.setString(1, (String)winners_combo_box.getSelectedItem());
statement.setString(2, winner_fields[0].getText());
statement.setFloat(3, Float.parseFloat(winner_fields[1].getSelectedText()));
statement.executeUpdate();
} catch(SQLException e1) {
e1.printStackTrace();
// TODO: Don't just pretend it worked...
} finally {
statement.close();
}
}
You shouldn't be trying to reuse a statement, basically. Personally I'd try not to reuse a connection, either, using connection pooling to handle reuse of the underlying connection, but that's a different matter.
I was working on a servlet that will generate a unique code and update that in a mySQL database.
Now, in that, I want to catch any exception thrown in case that unique code already exists in the mySQL table and generate a new code and try updating the database. The problem is I want to do this WITHIN the for loop itself. The code is as follows:
try
{
connection = datasource.getConnection();
SQLUpdate = "INSERT INTO Voucher_dump VALUES( '"+unique_code+"','08-10-2011 04:48:48','0')";
PreparedStatement ps1 = connection.prepareStatement(SQLUpdate);
ps1.executeUpdate();
ResultSet r = ps1.getResultSet(); // this is where I'm checking if it's a duplicate
if(r==null)
out.println("This is a duplicate");
else out.println("Updated");
trial12= "08-10-2011 04:48:480.03999855056924717a";
SQLUpdate = "INSERT INTO Voucher_dump VALUES( '"+trial12+"','08-10-2011 04:48:48','0')";
ps1 = connection.prepareStatement(SQLUpdate);
ps1.executeUpdate();
r = ps1.getResultSet();
if(r==null)
out.println("This is a duplicate");
else out.println("Updated");
}
catch (SQLException sqle)
{
sqle.printStackTrace();
}
I don't want to wait till the end of the entire loop to catch the SQLException (I have already defined this key in mySQL as primary). The moment, the result comes back as a duplicate entry, I want to re-generate this key and attempt the update again.My output for this particular code is coming blank on my output page (all other parameters are showing correctly). Neither is "This is a duplicate" displayed nor is "Updated". Maybe, ResultSet is not the best way to do it. Could you guys give me some advice on what would be the best way forward ?
Some advice in no particular order:
Close the connection in a finally block.
Close statements individually if you'll be creating many of them before closing the connection. ("Many" is defined by your DBAs.)
Format your code.
Don't use stdout and/or stderr from real code. Pick a logging framework.
Consider using some helper classes to simplify (and correct) your database access, like Spring's JdbcTemplate.
Make sure to include relevant context when you post example code.
Due to #6, I don't know what out is, but I suspect the reason you're not seeing anything is that you're inserting a duplicate value with the first statement, which will cause a SQLException from that line, not at getResultSet(), where you seem to expect it. Since the error is written to stdout, it'll show up in your server logs somewhere, but nothing will be written to out. I'm not sure why you think getResultSet() will return null or not null depending on whether there was a constraint violation. Take a look at the javadoc for that method.
Update: 7. As BalusC points out, never, ever concatenate a string directly into a JDBC Statment. Use PreparedStatment's placeholders and set* methods. For info on SQL injection, see Wikipedia and XKCD.
How about this code?
try {
Class.forName(driver).newInstance();
conn = DriverManager.getConnection(url + dbName);
System.out.println("Connected to the database");
int i = 1; //get the unique code
boolean isInserted = false;
while (!isInserted) {
try {
PreparedStatement preparedStatement = conn.prepareStatement("INSERT INTO test values (?)");
preparedStatement.setInt(1, i);
preparedStatement.executeUpdate();
isInserted = true;
} catch (com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException e) { //Catch the particular exception which throws error on unique constraint. This may depend on Java/MySQL your version
i++; //get the next unique code
}
}
System.out.println("Disconnected from database");
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
conn.close();
} catch (Exception e) {
}
}