Tricky Transactions on Force.com

A little exercise in knowing when transactions commit on Force.com! See if you correctly understand when data is, and is not, written to the database, and learn a little about save points while you're at it.

Okay, transactions are not too tricky, but can you work out what happens in this piece of code?

void manoman() {
String foo = Datetime.now().format ('hh-mm-s-S'); // a reasonably unique name
try {
Account a = new Account(name=foo);
insert a;
a = new Account();
insert a;
} catch (Exception e) {
System.debug('******** exception ' + e);
}
Account [] x = [select name from account where name=:foo];
System.debug('Out with it' + x);
}

Imagine that the code runs in a typical Force.com transaction.  For example, manoman() may run as a result of a Visualforce button executing some controller code, or within an email handler and so on.  Also imagine that no existing account records (I could have used any database object, I'm just using Account here because it exists).

What happens?  No cheating – don't go run this in System Log yet!

I'll give you a clue.  The first insert works just fine. 

I'll give you another clue.  The second insert fails (because the Account object is set up in such a way that "name" is a required field).  This isn't really part of the exercise though – I just needed some database operation here that failed.

That wasn't the hard part.  What happens next?  What will be output to the debug console?

The exception?  Yes.  I've just told you the second insert fails so sure, the exception is output.  

What else?  "Out with it", I hear some of you cry.  Yes, that is output as well. Bravo. Anything else?

Remember, that an exception was thrown in that code.  What happens to the transaction and the data inserted within that transaction, in this code?

Yes. The first inserted record will also be output.

Did you expect that the first insert commits - that the record is added to the database? 

I didn't expect that.  I thought that because the second first insert failed, the transaction would rollback, and so the first second insert would fail as well.

Why did it commit?

Force.com doesn't have explicit transaction demarcation.  Instead, if code "succeeds", it is deemed as having, well, succeeded.  In this case, the code succeeded.  It succeeded because we caught the exception!  The manoman() method returns just fine, because of our try/catch block. 

In other words, the platform always commits any work completed during the transaction unless exceptions bubble to the top.

If we didn't catch the exception, then the method would not succeed – it would thrown an uncaught exception.  As a result, the transaction would not commit, and in that case the first insert would not be committed either.

Savepoints

Say I wanted a different behaviour.  Well, I could remove the try/catch block depending on context.  That will prevent the first insert from committing.  

There is another way though, which is to use savepoints.  Here's a revision of the code:

void manomanoman() {
String foo = Datetime.now().format ('hh-mm-s-S'); // a reasonably unique name 
Savepoint sp = Database.setSavepoint();
try {
Account a = new Account(name=foo);
insert a;
a = new Account();
insert a;
} catch (Exception e) {
Database.rollback(sp);
System.debug('******** exception ' + e);
}
Account [] x = [select name from account where name=:foo];
System.debug('Out with it' + x);
}

Note that I set a save point before I do any database work, and then within the exception I rollback to that savepoint.  The database will then rollback any changes and ensure that the first commit does not get written to the database.  In this case, the output will not include any accounts.

(Thanks to Andrew Waite for correcting an earlier tutorial of mine that handled transactions incorrectly – I hope I got it all right here!)

Published
May 11, 2010
Topics:

Leave your comments...

Tricky Transactions on Force.com