A customer of mine is seeing partial execution of an Apex Function.
In the Function, they need to Insert/Update multiple objects.
Insert new Contact(...);
...
Insert new Cust_object__c(...);
In the second insert, they are encountering an error, but the Contact from the first insert is remaining, and I am trying to understand why.
With how I am reading the documentation around transaction Control, because the entire apex isn't finishing, the entire transaction shouldn't be committed.
Transaction Control Documentation
If the entire request completes successfully, all changes are
committed to the database. For example, suppose a Visualforce page
called an Apex controller, which in turn called an additional Apex
class. Only when all the Apex code has finished running and the
Visualforce page has finished running, are the changes committed to
the database.
I understand that I can create a save point, and roll back once the error is encountered, but I am trying to understand the Apex execution rules.
Shouldn't all transactions within the function be rolled back if there is an error encountered, even DML statements that completed successfully?
Update:
I have confirmed that there isn't any code that is captured in Try/Catch blocks. All DML operations are using DML "Insert" and not Database.insert(xxx,false). The underlying error stemmed from hard coded IDs that referenced objects that were deleted, but I still need to understand why only part of the transaction was rolled back.
The execution stack:
Lightning component Calls Apex Controller @AuraEnabled Function
Apex method Inserts Contact
Apex method Inserts Custom Object
Before Insert Trigger
After Insert Trigger
Apex Custom Trigger Helper Class
Insert Opportunity
Before Insert Trigger
After Insert Trigger
Apex Custom Opportunity Trigger
(Error Occurs)
The Insert for the Contact remains. The Insert of the Custom Object and the triggered Insert of the Opportunity are both rolled back.
Why wouldn't all DML actions in the Controller method be rolled back?
Best Answer
As long as the transaction completes successfully, any DML operations that completed successfully will remain so. Usually, this means that a developer did something like this:
Since they caught the exception, the transaction completed successfully; if firstList completed okay but secondList threw an exception, then this pattern would result in a partial transaction like you describe.
The general rule is that you should not do this; always roll back the transaction if you want to use try-catch atomically, or don't catch the exception.
The general pattern for rollback is like this:
This will prevent partial changes from being saved. Alternatively, don't use try-catch at all, but be aware that if you do this in a Visualforce page, it can result in a loss of "view state" when the page crashes, meaning the users won't have an opportunity to correct the error(s).