You should know by now - there are many things possible in ADF, but it doesn't mean every solution is right, even if it works. One example of such case - coding validation rules in beforeCommit method. This method is invoked after all changes are posted and ADF BC assumes data is valid, if we throw later validation error from beforeCommit - ADF BC state remains unchanged and changed data is not submitted again. There is a workaround to set jbo.txn.handleafterpostexc=true and to force in memory passivation snapshot with subsequent activation on validation error - however, this is a big performance hit. Every time, where there will be validation error - rollback will be executed and entire AM with all VO instances will be re-activated (SQL re-executed and data re-fetched). Today post is about bad practice, to demonstrate why you should not code validation in beforeCommit method.
Sample application - ADFHandleAfterPostApp.zip, implements validation rule in EO beforeCommit method. Validation rule calls PL/SQL function and throws exception if validation result is false. It validated salary value from the current row (PL/SQL function code is attached in sample application):
PL/SQL function checks if salary value is lower than 2000:
Sample application is configured with DB pooling. By default, without DB pooling set - DB lock will not be removed after exception from beforeCommit:
To test beforeCommit validation behaviour, open the form and set Salary value below 2000. Change also another field - FirstName, for example:
As expected, there will be validation error and exception thrown - error message displayed:
We can see the sequence of steps happening in the log. There is DB lock for the current row, data is posted successfully for both Salary and FirstName attributes, then exception happens:
Ok, we can fix Salary attribute value and set it to be higher than 2000. Keep the same value for FirstName attribute, it is not committed yet to DB and we expect it to be committed now:
What we can see in the log now - Salary attribute value was posted and committed, as validation passed successfully. However, second changed attribute - FirstName, value was not committed. This is because, ADF BC thinks FirstName was committed during previous commit, when it actually failed. As exception was thrown in beforeCommit, this is already after ADF BC marks data as valid and assumes to be committed to DB. This is the main issue with coding validation in beforeCommit - ADF BC transaction state remains invalid:
If you press Rollback - you will see, FirstName will be reset to the previous value:
There is a workaround for this case, to use jbo.txn.handleafterpostexc = true property in AM configuration:
However, this leads to runtime performance issues - as it fires passivation in memory on each commit and does rollback with re-activation on every validation error in beforeCommit. We can track all such activations, fired from beforeCommit, by overriding activateStateForUndo method in AM:
We can test the same scenario - change LastName and Salary attributes, set Salary to be less than 2000:
Now we can spot - there was activation event raised:
During activation, all VO's are re-executed and re-fetched. Here we can see SQL query and data fetch for Employees:
There is SQL query and data fetch for Departments, even we didn't touch it:
Sample application - ADFHandleAfterPostApp.zip, implements validation rule in EO beforeCommit method. Validation rule calls PL/SQL function and throws exception if validation result is false. It validated salary value from the current row (PL/SQL function code is attached in sample application):
PL/SQL function checks if salary value is lower than 2000:
Sample application is configured with DB pooling. By default, without DB pooling set - DB lock will not be removed after exception from beforeCommit:
To test beforeCommit validation behaviour, open the form and set Salary value below 2000. Change also another field - FirstName, for example:
As expected, there will be validation error and exception thrown - error message displayed:
We can see the sequence of steps happening in the log. There is DB lock for the current row, data is posted successfully for both Salary and FirstName attributes, then exception happens:
Ok, we can fix Salary attribute value and set it to be higher than 2000. Keep the same value for FirstName attribute, it is not committed yet to DB and we expect it to be committed now:
What we can see in the log now - Salary attribute value was posted and committed, as validation passed successfully. However, second changed attribute - FirstName, value was not committed. This is because, ADF BC thinks FirstName was committed during previous commit, when it actually failed. As exception was thrown in beforeCommit, this is already after ADF BC marks data as valid and assumes to be committed to DB. This is the main issue with coding validation in beforeCommit - ADF BC transaction state remains invalid:
If you press Rollback - you will see, FirstName will be reset to the previous value:
There is a workaround for this case, to use jbo.txn.handleafterpostexc = true property in AM configuration:
However, this leads to runtime performance issues - as it fires passivation in memory on each commit and does rollback with re-activation on every validation error in beforeCommit. We can track all such activations, fired from beforeCommit, by overriding activateStateForUndo method in AM:
We can test the same scenario - change LastName and Salary attributes, set Salary to be less than 2000:
Now we can spot - there was activation event raised:
During activation, all VO's are re-executed and re-fetched. Here we can see SQL query and data fetch for Employees:
There is SQL query and data fetch for Departments, even we didn't touch it:
Shouldn't we expect it to throw a lock error in this scenario instead of committing only the modified Salary?
ReplyDeleteHere is how I thought it would work, and this must be not the way ADF is actually handling this, so request you to correct it:
1. When the first commit fails, the view state is not rolling back to the original values, instead it's keeping the modified values FirstName="Steven123" and Salary="2000"
2. Now user modifies the salary to 2500. Upon commit, the first thing ADF would check is which attributes have changed; this it would probably do by comparing with the view's backup template that ADF keeps for resolving locking issues(?)
3. It finds "Steven123" and "2000" there for those 2 attributes, so only Salary has changed, and that's what it tries to commit
4. But as soon as it compares this view backup copy with the db state, it finds stale data in the view back, and ought to throw a lock error
If the view backup actually holds the original values, Steven and 2000 at step 2, then it should have found that both FirstName and Salary have been modified. Or has it something to do with the jsf lifecycle where since the model (bean or bindings) was updated at step 1, when the form is submitted at step 2, it's passing only the updated salary and not the FirstName.
Could you please clarify?
Thanks!
Where should we move the beforeCommit business rules validations? To postChanges for example? postChanges is on commit process too... it'll have the same effects I suppouse. And what happens if we have some business rules on DB on PL/SQL?
ReplyDeleteI will follow up on this, to clarify your doubts. Stay tuned.
ReplyDeleteRegards,
Andrejus
Hi,
ReplyDeleteThere is DB pooling enabled, so there will be no lock - lock is released at the end of request. Without DB pooling, lock would stay forever and no other user would be able to commit anything in the same record.
1. Yes, when commit fails - EO state is not reset back, it assumes all data is successfully posted. From ADF BC point of view, data is successfully stored in DB. Validation was thrown in beforeCommit, this is too late
2. ADF doesnt see FirstName as changed anymore during second commit, EO attribute is assumed to be unchanged - so, there is nothing posted to DB for this attribute on the second commit
3. No, it doesnt compare FirstName with the value in DB - it simply forgets about recently changed value after first validation error from beforeCommit and assumes FirstName is the same as in in DB - Steven
You should move validation rules from beforeCommit into EO validators, as standard recommendation from ADF developer guide. You can call PL/SQL validation rules from EO validators on attribute or EO level.
Regards,
Andrejus
Thanks for the clarification!
ReplyDeleteHowever, as far as I know, when ADF loads a view, it keeps a backup copy also to compare with the db state to take the lock before posting/committing the data. And if change indicator is not implemented then all view attributes would be compared to rule out stale data.
In this context, I am unable to reconcile steps 1 and 3 you mentioned above. At step 1, view has Steven123 (EO state is not reset) whereas view backup has Steven. Doesn't ADF compare the view with the view backup to find out which fields were modified in the transaction?
Thanks again!
The thing is - when you throw exception from beforeCommit, this "backup" view as you say - is removed. As it assumes all changes were committed to DB successfully. I hope this is clear enough from the blog post, try to follow steps visualised in screenshots.
ReplyDeleteRegards,
Andrejus