When should you commit or rollback a transaction? As late as possible, I would have thought, based on most of the advice in the Oracle world. You certainly want this to be predictable and consistent, at least.
Unfortunately, if you use
APEX_UTIL.set_session_state in your PL/SQL process, the result is not so predictable.
Test case set up – create a table with a single row, and create a simple Apex application with one page, with one region, with an item (P1_N) and a Submit button.
CREATE TABLE test (N NUMBER); INSERT INTO test VALUES (1); COMMIT;
TEST CASE #1
Add an On Submit process to the page which fires when the Submit button is clicked, which executes the following:
BEGIN UPDATE test SET n = 3; COMMIT; APEX_UTIL.set_session_state('P1_N', 1); UPDATE test SET n = 2; APEX_UTIL.set_session_state('P1_N', 1); ROLLBACK; END;
What value would you expect to see in the database table now? I would have expected that the table would hold the value 3 – and indeed, it does.
TEST CASE #2
Modify the process slightly – after the second update, set the item to something different:
BEGIN UPDATE test SET n = 3; COMMIT; APEX_UTIL.set_session_state('P1_N', 1); UPDATE test SET n = 2; APEX_UTIL.set_session_state('P1_N', 4); --changed here ROLLBACK; END;
This time, the second update to the table has been committed before we issued our ROLLBACK. The new value 2 has been saved to the database. Why?
APEX_UTIL.set_session_state will issue a COMMIT – but only if the value of the item is changed. If you happen to call
set_session_state with the value that the item already has, it does nothing, and does not COMMIT. I understand why a COMMIT is ultimately necessary (Apex session state is stored in a table) – but I disagree that it’s necessary for it to commit my (potentially partial) transaction along with it.
This means that if an exception is raised somewhere in my process, the resulting rollback may or may not rollback the entire transaction, depending on whether any prior calls to
set_session_state happened to COMMIT or not. This is difficult to predict and therefore makes debugging harder. Not to mention the fact that it violates the general principle of “either the whole transaction succeeds and is COMMITted, or it fails and the whole transaction is rolled back”. I’m sorry, Apex, but you should not arbitrarily commit part of my transaction without at least telling me.
Mitigations for this? I’m not sure yet. One suggestion from this forum thread was to make the procedure use an autonomous transaction. This would align it more closely to what most developers would expect, I think. Unfortunately it appears the suggestion was rejected (or put on hold indefinitely).
I’m planning on refactoring my code to shift all calls to
set_session_state to as late in the process as possible; in addition, I’m thinking that I would put an explicit COMMIT prior to these calls so that my code would have more predictable behaviour. But the idea of wrapping
set_session_state in a wrapper procedure with an autonomous transaction seems good to try out as well.