Wednesday, 29 August 2012

Executing Data Operations as a Single Transaction - Apex

What Is an Apex Transaction?
An Apex transaction, also referred to as an Apex request, represents a set of operations that are executed as a single unit. All DML operations in a transaction either complete successfully, or if an error occurs in one operation, the entire transaction is rolled back and no data is committed to the database. The boundary of a transaction can be a trigger, a class method, an anonymous block of code, a Visualforce page, or a custom Web service method.
All operations that occur inside the transaction boundary represent a single unit of operations. This also applies for calls that are made from the transaction boundary to external code, such as classes or triggers that get fired as a result of the code running in the transaction boundary. For example, consider the following chain of operations: a custom Apex Web service method causes a trigger to fire, which in turn calls a method in a class. In this case, all changes are committed to the database only after all operations in the transaction finish executing and don’t cause any errors. If an error occurs in any of the intermediate steps, all database changes are rolled back and the transaction isn’t committed.

How Are Transactions Useful?

Transactions are useful when several operations are related, and either all or none of the operations should be committed. This keeps the database in a consistent state. There are many business scenarios that benefit from transaction processing. For example, transferring funds from one bank account to another is a common scenario. It involves debiting the first account and crediting the second account with the amount to transfer. These two operations need to be committed together to the database. But if the debit operation succeeds and the credit operation fails, the account balances will be inconsistent.

Try It Out

This example shows how all database insert operations are rolled back when the last operation causes a validation rule failure. In this example, the invoice method is the transaction boundary. All code that runs within this method either commits all changes to the database or rolls back all changes. In this case, we add a new invoice statement with a line item for the pencils merchandise. The Line Item is for a purchase of 5,000 pencils specified in the Units_Sold__c field, which is more than the entire pencils inventory of 1,000.
The sample Line Item object you created in Chapter 1 includes a validation rule. This validation rule checks that the total inventory of the merchandise item is enough to cover new purchases. Since this example attempts to purchase more pencils (5,000) than items in stock (1,000), the validation rule fails and throws a run-time exception. Code execution halts at this point and all DML operations processed before this exception are rolled back. In this case, the invoice statement and line item won’t be added to the database, and their insert DML operations are rolled back.
  1. Add the following class through the Developer Console.
  2. For the class name, type MerchandiseOperations and replace the auto-generated code with this example.
    public class MerchandiseOperations {
        public static Id invoice( String pName, Integer pSold, String pDesc) {
            // Retrieve the pencils sample merchandise 
        
            Merchandise__c m = [SELECT Price__c,Total_Inventory__c
                FROM Merchandise__c WHERE Name = :pName LIMIT 1];
            // break if no merchandise is found 
        
            System.assertNotEquals(null, m);
            // Add a new invoice 
        
            Invoice_Statement__c i = new Invoice_Statement__c(
                Description__c = pDesc);
            insert i;
    
            // Add a new line item to the invoice 
        
            Line_Item__c li = new Line_Item__c(
                Name = '1',
                Invoice_Statement__c = i.Id,
                Merchandise__c = m.Id,
                Unit_Price__c = m.Price__c,
                Units_Sold__c = pSold);
            insert li;
    
            // Update the inventory of the merchandise item  
        
            m.Total_Inventory__c -= pSold;
            update m;
            return i.Id;
        }
    }
    
  3. In the Developer Console, execute the static invoice method.
    Id invoice = MerchandiseOperations.invoice('Pencils', 5000, 'test 1');
    This snippet causes the validation rule on the line item to fail on the line item insertion and a DmlException is returned. All DML operations are rolled back—the invoice statement and line item aren’t committed to the database.
  4. Let’s find the validation rule error message and the exception in the execution log. Type VF_PAGE_MESSAGE next to Filter.
    The validation rule error message displays in the filtered view (You have ordered more items than we have in stock.)
  5. Next, type exception in the filter field and inspect the exception.
  6. Delete the previous snippet and execute this second chunk of code.
    Id invoice = MerchandiseOperations.invoice('Pencils', 5, 'test 2');
    This snippet inserts a new invoice statement with a line item and commits them to the database. The validation rule succeeds because the number of pencils purchased is within the total inventory count.

1 comment:

  1. Hi Rajesh,

    Thank you for the wonderful example, it really helps.

    As a newbie, I got a question here. As you know, the Database.xxx methods have an opt_allOrNone parameter - if false then the methods allow partial success.

    At the beginning, I thought this is in contradiction with the transaction control (as your wonderful blog demonstrated), but now I guess when the opt_allOrNone is false, Database.xxx just suppress exceptions if there are any failed records (but only indicate the failure in the returned result array) so that the whole transaction can still commit as long as no other exception occurs in subsequent operations.

    I will be much appreciated if you could confirm or correct my understanding. Thank you in advance.

    ReplyDelete