views:

287

answers:

3

Note: we reuse single connection.

************************************************
public Connection connection() {        
       try {
           if ((connection == null) || (connection.isClosed()))
           {
              if (connection!=null) log.severe("Connection was closed !");
               connection = DriverManager.getConnection(jdbcURL, username, password);
           }
       } catch (SQLException e) {
           log.severe("can't connect: " + e.getMessage());
       }
       return connection;        
   }
**************************************************

public IngisObject[] select(String query, String idColumnName, String[] columns) {
Connection con = connection();

Vector<IngisObject> objects = new Vector<IngisObject>();
try {
    Statement stmt = con.createStatement();

    String sql = query;
    ResultSet rs =stmt.executeQuery(sql);//oracle increases cursors count here
    while(rs.next()) {
       IngisObject o = new IngisObject("New Result");
       o.setIdColumnName(idColumnName);            
       o.setDatabase(this);
       for(String column: columns) o.attrs().put(column, rs.getObject(column));
       objects.add(o);
       }

    rs.close();// oracle don't decrease cursor count here, while it's expected
    stmt.close();
    } 
catch (SQLException ex) {
    System.out.println(query);
    ex.printStackTrace();
}
A: 

Normally you would put the close statements for your ResultSet and Statement into a finally block to ensure that they are called even if an exception occurs (could be the issue you are having here). In your current code, if a SQLException occurs then the two close( ) method calls will never occur and cursors would be left open.

Also what query are you using in Oracle to see the count of open cursors?

Edit:
That code should be closing the cursor. If it isn't then you should be able to see a 1 to 1 correlation of calling your method and the cursor count going up by 1. Be sure there isn't some unexpected process that is causing the cursor count to go up.

If you have the privileges, you can run this query against the database to see the open cursor count by sid to see if maybe it is some other process that is increasing the cursors and not yours specifically. It will pull back any with more than 10 cursors open, you can raise this to filter out the noise or narrow it specifically by username or osuser:

select oc.sid,
       count(*) numCur,
       s.username username,
       s.osuser osuser,
       oc.sql_text,
       s.program
  from v$open_cursor oc,
       v$session s
 where s.sid = oc.sid
group by oc.sid, 
         oc.sql_text, 
         s.username, 
         s.osuser, 
         s.program
having count(*) > 10
order by oc.sid;
Dougman
@Dougman I am using Oracle database manager console.
Vladimir
I know that it's better to close them in finally. But excpetion is not an issue. It doesn't occur in my testing.
Vladimir
@Vladimir: I added additional info to my answer. Your code should be working if no exception is being thrown. Look at the sid level and make sure it is your process alone that is affecting the cursor count.
Dougman
@Dougman thank you. I will try that.
Vladimir
+1  A: 

The correct way to do it is to close every resource in a finally block in its own try/catch block. I usually use a static utility class like this:

public class DatabaseUtils
{
    public static void close(Connection connection)
    {
        try
        {
            if (connection != null)
            {
                connection.close();
            }
        }
        catch (SQLException e)
        {
            // log exception here.
        }
    }

    // similar methods for ResultSet and Statement
}

So I'd write your code like this:

public IngisObject[] select(String query, String idColumnName, String[] columns) {

Vector<IngisObject> objects = new Vector<IngisObject>();

Connection con = null;
Statement stmt = null;
ResultSet rs = null;

try 
{
    connection = connection();
    stmt = con.createStatement();

    // This is a SQL injection attack waiting to happen; I'd recommend PreparedStatemen
    String sql = query;
    rs =stmt.executeQuery(sql);//oracle increases cursors count here
    while(rs.next()) 
    {
       IngisObject o = new IngisObject("New Result");
       o.setIdColumnName(idColumnName);            
       o.setDatabase(this);
       for(String column: columns) o.attrs().put(column, rs.getObject(column));
       objects.add(o);
    }

} 
catch (SQLException ex) 
{
    System.out.println(query);
    ex.printStackTrace();
}
finally
{
    DatabaseUtils.close(rs);
    DatabaseUtils.close(stmt);
    DatabaseUtils.close(con);
}
duffymo
+1  A: 

The init.ora parameter open_cursors defines the maximum of opened cursors a session can have at once. It has a default value of 50. If the application exceeds this number the error "ORA-01000: maximum open cursors exceeded" is raised.

Therefore it's mandatory to close the JDBC resources when they are not needed any longer, in particular java.sql.ResultSet and java.sql.Statement. If they are not closed, the application has a resource leak.

In case of reusing the Connection object, you must be aware of the fact that the opened oracle cursors are kept open and in use as long the connection exists and the transaction has not ended. When the application commits, the opened cursors are released.

Therefore as an application designer you need to know a rough estimation of the needed open cursors for your most complex transaction.

The difficulty lies in the inability of oracle's internal parameter views (v$open_cursor, v$sesstat, et. al.) to show the difference between opened cursors, which are reusable and opened cursors, which are still blocked (not reusable!) by an unclosed ResulSet or Statement. If you close all Statement and ResultSet objects in your finally block, your application is perfectly fine.

Adjusting the init.ora parameter works like this (our application needs 800 cursors at a maximum)

ALTER SYSTEM SET open_cursors = 800 SCOPE=BOTH;
Oliver Michels