< Previous | Next >

Lesson 7: Exception handling and rollbacks

It's especially important to properly handle errors in applications that access data sources in order to avoid retrieving incorrect data, inserting incorrect data, or otherwise creating inconsistencies between the application and the data source. In this lesson, you will learn about a few tools that you can use to respond to errors when accessing relational databases.

Exception handling for SQL data access

You've probably noticed that the examples in this tutorial often put data access statements within a try block.
function execDropTable(status StatusRec inOut)
  try  
    execute #sql{
      DROP TABLE EGL.CUSTOMERTEMP
      } ;
  onException (exception SQLException)
    ConditionHandlingLib.HandleException(status, exception);
  end
end
When you put code in a try block, EGL attempts to run that code as usual. If EGL encounters an error, it stops running the code in the try block and moves directly to the onException block, skipping any statements that remain in the try block. You can put code in the onException block to recover from the error, correct the problem, or write error information to the log file.

When EGL encounters an error within a try block, it also creates an exception record to provide information about the error. It passes that record to the onException block. Within the onException block, you can use the information in that record to help determine the cause of the error and recover from it. In the above example, the onException block passes the exception record to a function that deals with the error.

Exception records can have one of several stereotypes, depending on the type of error. In the example above, the onException block expects an exception record with the SQLException stereotype, which is for SQL errors and other relational database errors. If EGL encounters a different type of error, such as reference to a null value, it creates a different type of exception record (in the case of a reference to a null value, an exception record with the NullValueException stereotype).

You can place multiple onException blocks after a single try block in order to process different types of errors differently, as in the following example:
function execDropTable(status StatusRec inOut)
  try  
    execute #sql{
      DROP TABLE EGL.CUSTOMERTEMP
      } ;
  onException (exception SQLException)
    ConditionHandlingLib.HandleException(status, exception);
  onException (exception AnyException)
		HandleOtherError(exception);
  end
end
In this example, the first onException block expects a SQLException record and the second block expects the AnyException stereotype. In this case, if EGL encounters an SQL error, the first onException block runs; if EGL encounters any other kind of error, the second block runs. The AnyException stereotype is a general-user exception record stereotype, used to respond to any error, regardless of stereotype.

All exception records have at least two fields: a messageID field with the error code for the error and a message field with a brief explanation of the problem. Depending on the stereotype, exception records can have other fields. For example, the SQLRecord stereotype has a sqlState field that holds a CHAR(5) value with the status of the statement, and a sqlCode field that holds an INT return code from the DBMS.

You can try out exception handling by passing invalid information, deleting or updating a record that doesn't exist, or trying to add a record with the same primary key. The following example attempts to add a new customer record with the same ID number as an existing row in the database, which will cause a conflict with primary keys and prompt EGL to throw an SQLException exception.

  1. Create a new program in the programs package with the name exceptionHandlingTest.
  2. In the main function of the new program, create a customer record variable and assign it information including an ID number already existing in the database:
    package programs;
    
    import eglderbyr7.data.Customer;
    
    program exceptionHandlingTest type BasicProgram {}
      
      function main()
        customer Customer;
        customer.FirstName = "John";
        customer.LastName = "Doe";
        customer.CustomerId = 1;
      end
    
    end
  3. Within a try block, add the record to the database:
    try
      add customer;
      SysLib.writeStdout("Customer added successfully.");
    end
    This try block isn't very useful because there is no matching onException block to respond to any errors.
  4. Before the end that closes the try block, add an onException block to handle the SQLException that will occur when EGL attempts to run the code in the try block:
    try
      add customer;
      SysLib.writeStdout("Customer added successfully.");
    onException(ex SQLException)
      SysLib.writeStdout("SQL exception "::ex.messageID);
      SysLib.writeStdout("message = "::ex.message);
      SysLib.writeStdout("SQLCode = "::ex.sqlCode);
      SysLib.writeStdout("SQLState = "::ex.sqlState);
    end
    The complete program looks like this:
    package programs;
    
    import eglderbyr7.data.Customer;
    
    program exceptionHandlingTest type BasicProgram {}
      
      function main()
        customer Customer;
        customer.FirstName = "John";
        customer.LastName = "Doe";
        customer.CustomerId = 1;
      
        try
          add customer;
          SysLib.writeStdout("Customer and order added successfully.");
        onException(ex SQLException)
          SysLib.writeStdout("SQL exception "::ex.messageID);
          SysLib.writeStdout("message = "::ex.message);
          SysLib.writeStdout("SQLCode = "::ex.sqlCode);
          SysLib.writeStdout("SQLState = "::ex.sqlState);
        end
        
            
      end
      
    end
  5. Save, generate, and run the program. The console shows the output from the onException block:
    SQL exception EGL0504E
    message = EGL0504E ADD: The statement was aborted 
      because it would have caused a duplicate key value 
      in a unique or primary key constraint or unique 
      index identified by 'SQL070307095259210' defined 
      on 'CUSTOMER'.[sqlstate:23505][sqlcode:20000]
    EGL0002I The error occurred in the 
      exceptionHandlingTest program processing the main function.
    SQLCode = 20000
    SQLState = 23505

Rolling back changes to the database

Most relational databases, including Derby, are recoverable resources. When you make a change to a recoverable resource, that change is not permanent until you issue a commit, which saves the changes. In this way, you can reverse changes to the database if you encounter an exception before you commit those changes.
You can set the sqlCommitControl build descriptor option to NOAUTOCOMMIT, which prevents EGL from committing each change automatically. With automatic commits turned off, you can manually commit the changes by invoking sysLib.commit() and you can reverse changes made since the last commit by invoking sysLib.rollback(). If you do not commit the changes with sysLib.commit(), EGL always commits changes at the end of the run unit, such as at the end of a main program.

For example, imagine that you are making several changes to the database, such as adding a new customer and entering order information for that customer's first order. If some of these operations succeed before another fails, you may be left with a customer without any orders, or an order without a customer who made the order. In this case, you can put a rollback in the onException block to reverse the changes and keep incomplete information out of the database:

  1. Set the sqlCommitControl build descriptor option to NOAUTOCOMMIT:
    1. In the Project Explorer view, double-click the EGLSQL project's build descriptor to open it in the EGL Build Parts Editor. This file is located at EGLSQL/EGLSource/EGLSQL.eglbld.
    2. In the Build Parts Editor, clear the Show only specified options check box.
    3. Under Option, find sqlCommitControl.
    4. Set the value for sqlCommitControl to NOAUTOCOMMIT.
      Note: To open the sqlCommitControl option for editing, click twice slowly in the Value column next to that option. Also, you can click three times quickly in the Value column.
    5. Save and close the build descriptor.
  2. Open the exceptionHandlingTest program that you created in the previous section.
  3. Add an order record to the customer record that is already in the program:
    program exceptionHandlingTest type BasicProgram {}
      
      function main()
        customer Customer;
        customer.FirstName = "John";
        customer.LastName = "Doe";
        customer.CustomerId = 1;
        
        customerOrder Orders;
        customerOrder.CustomerId = 1;
        customerOrder.OrderId = 50;
        customerOrder.OrderAmount = 0;
        customerOrder.OrderDetails = 
          "This is my first order, so get it right!";
        
        ...
        
      end
  4. Within a try block, add the order first and then the customer:
    try
      add customerOrder;
      SysLib.writeStdout("Order added successfully.");
      add customer;
      SysLib.writeStdout("Customer added successfully.");
    end
    In this case, the order will be added but the customer will not, because of the duplicate primary key. Therefore, you can reverse the change to the ORDERS table by invoking a rollback in the onException block.
  5. Add an onException block, including a rollback, after the try block:
    try
      add customerOrder;
      SysLib.writeStdout("Order added successfully.");
      add customer;
      SysLib.writeStdout("Customer added successfully.");
    onException(ex SQLException)
      SysLib.writeStdout("SQL exception "::ex.messageID);
      SysLib.writeStdout("message = "::ex.message);
      SysLib.writeStdout("Performing rollback.");
      SysLib.rollback();
    end
    Now, if either add statement fails, the code onException block runs and attempts to reverse the changes to the database.
  6. After the onException block, add code to test whether the rollback successfully removed the order that was added before the exception occurred. To see if the order record is still in the database, you can compare it to noRecordFound:
    tempOrder Orders;
    tempOrder.OrderId = 50;
    get tempOrder;
    
    if (tempOrder is noRecordFound)
      SysLib.writeStdout("The order was rolled back successfully.");
    else
      SysLib.writeStdout("The order is still in the database");
    end
    Another way to find out the status of a call to a database is to test the values of the sqlLib.sqlData system variable. This system variable is a record, and its fields store information about the success or failure of the last SQL database operation. Specifically, the field sqlLib.sqlData.sqlcode contains 0 if the operation completed successfully and returned results, and it contains 100 if the operation completed successfully but the SELECT statement returned no results. Therefore, you could also tell if the row was found or not with the following code:
    if (sqlLib.sqlData.sqlcode == 0)
    	SysLib.writeStdout("The order is still in the database");
    else
    	if (sqlLib.sqlData.sqlcode == 100)
    		SysLib.writeStdout("The order was rolled back successfully.");
    	end
    end
  7. Generate and run the program.
The output of the program shows that the order is added to the database, the exception occurs, and then the order is successfully removed as a result of the rollback:
Order added successfully.
SQL exception EGL0504E
message = EGL0504E ADD: The statement was aborted 
  because it would have caused a duplicate 
  key value in a unique or primary key constraint 
  or unique index identified by 'SQL070307095259210' 
  defined on 'CUSTOMER'.[sqlstate:23505][sqlcode:20000]
EGL0002I The error occurred in the exceptionHandlingTest 
  program processing the main function.
Performing rollback.
The order was rolled back successfully.
Here is the complete code of the exceptionHandlingTest.egl file. If you see any errors marked by red X symbols in the file, make sure your code matches the code in this file:Completed exceptionHandlingTest.egl file after lesson 7.

Lesson checkpoint

In this lesson, you learned the basics of handling errors in SQL operations. It's very important to anticipate problems and provide exception handling to allow the application to recover.
For more information on the runtime errors that can cause exceptions, see EGL Java™ runtime error codes. For a list of the exception records that EGL can create, see EGL core Exception records.
< Previous | Next >

Feedback