Hi all,
Scenario:
Our app: A web application
Deployment env: 2-tier architecture where web-tier and business-tier run in the same JVM.
Transaction Management Strategy: JTA-enabled, session-per-request strategy. We use a Transaction Filter (something like hibernate filter and uses Spring's TransactionTemplate internally) that starts a new transaction and commits/rollback on the way out. Business Service layer (POJO, non-EJB) is decorated with Spring's AOP Transaction Interceptors with attribute Propagation_Required. So, our business services always join an existing transaction started by transaction filter.
Hibernate version : 3.2.1 sp4
Name and version of the database you are using: Oracle 10.2g
Hibernate Settings: Default settings (so session flushmode gets automatically set to AUTO and connection_release_mode to after_statement (since JTA) by hibernate)
Steps Performed
1. I submit a form from one of the web pages.
2. In the business layer of my application, some of the persistent domain objects are modified and after some processing but before issuing a hibernate save/update on those domain objects, some business rule is violated and an exception gets thrown.
3. On the way out of the business layer, Spring's Transaction Interceptor marks the JTA transaction for rollbackOnly (by calling setRollbackOnly on JTA TM).
4. Now in the action we have to redisplay the form along with the BusinessException Message, and since the form is complex, to show the original form, we have to perform a few db queries. Since we are using HQL, and session flush mode is auto, it flushes the session, which persists the domain object's dirty data to database. Note that transaction has not been committed yet (since we do that in Transaction Filter).
5. I opened a separate sql client window and did a query on the database. Found that values that were flushed in the above step are visible in the database! Since the transaction isolation level of our db is read-committed, we should not be seeing here the above flushed dirty data as transaction has not been committed yet.
6. After the necessary queries to display the form are performed, control returns to my Transaction Filter where we do a rollback. ( Since we are using Spring's Transaction Template, it does it in a little round about way; instead of doing an explicit rollback (since JTA Transaction was marked for rollbackOnly), it issues a commit anyways, which, fails and throws an exception. Shouldn't really be a problem though).
7. The original web page is rendered along with the exception message.
Problems/Questions1. As we saw in steps 4 and 5 above, flushing also committed the data. Is this expected?
2. I don't think hibernate should have done any flushing at all when I issued the HQL query in step 4. Internally, it should do a check if transaction is marked for rollback and not do any flushing if it is. Instead, it checks if the existing transaction is in progress and if it is, goes ahead and auto flushes the data. The logic used to determine if transaction is in progress also looks a little questionable to me. Following is the code snippet from JTAHelper:
Code:
public static boolean isInProgress(int status) {
return status==Status.STATUS_ACTIVE ||
status==Status.STATUS_MARKED_ROLLBACK;
}
Why is STATUS_MARKED_ROLLBACK a candidate for flushing? Is it deliberate? There is another method isRollback in JTAHelper with following code:
Code:
public static boolean isRollback(int status) {
return status==Status.STATUS_MARKED_ROLLBACK ||
status==Status.STATUS_ROLLING_BACK ||
status==Status.STATUS_ROLLEDBACK;
}
I believe hibernate should also call JTAHelper.isRollback() to determine if it transaction is marked for rollback and refrain from flushing accordingly. Is it a bug? Or am I missing something?
Possible solutions
1. I can write a new AOP interceptor that gets the current session bound to thread and calls session.clear() if current transaction is marked for rollback. I can plug it in just before the Spring's AOP transcaction interceptor (which is plugged in before any business service is invoked).
2. In the AbstractDAO (which provides helper methods to perform queries on db) of my application, I can add a method that does the same as above interceptor and hook it up with remaining methods in that class.
3. Other solutions? Please share your thoughts.
Thanks.