Developping a RIA that involves long-lived entities introduces a big challenge regarding transaction handling. In traditional webapps, the transaction is mapped to the ORM session (e.g. JPA or Hibernate) wich, in turn, is mapped to simple and well-defined application lifecycle phases (be it the HTTP request itself with the "open session in view" pattern, or a more extended scope like JBoss Seam or CDI "conversation").
Jspresso offers a more flexible application worlkflow where the user is not driven by pre-defined pages chainings. Unlike in traditional webapps, the transient model state can evolve independently of the persistent state in an arbitrary way until the user decides to save (or rollback) the changes. Therefore, there is a subtle coordination achieved by the framework to that the model in-memory state remains coherent with the persistent one. Jspresso cannot solely rely on the ORM session to handle the session model state since it is bad practice to keep the ORM session open indefinitely and span multiple requests/transactions. Moreover, any runtime exception can make the ORM session unusable, thus invalidating all the transient state.
In order to achieve this, Jspresso implements automatic tracking of session model changes, and seamlessly deals with the ORM to merge those changes back to the persistent store. All this work nicely integrates with transaction management through the concept of "Unit of Work" (referred to as UOW). A Jspresso UOW can be seen as an in-memory draft zone where model parts can be duplicated and modified without impacting the actual model session state. A UOW can be started, committed, rolled-back, very much like transactions indeed. But you will rarely need to manage the UOW yourself since Jspresso automatically maps its lifecycle to the actual underlying transaction platform, e.g. the Hibernate session, the JTA implementor, ... This means that whenever a transaction is actually started, committed or rolled-back, thhe corresponding operations are performed on the UOW.
Jspresso offers built-in standard actions as well as a comprehensive API to handle domain model persistence along with transactions. Once the user as updated parts of the model using the application views and services here is a synopsis of the operations involved in persisting the changes :
There are 2 different ways to start a transaction in Jspresso. The first one is to annotate the backend action class with org.jspresso.framework.application.backend.action.Transactional (beware to use the Jspresso one, not the spring one). It forces the action to execute inside a transaction with a REQUIRED propagation scheme (use the existing transaction if any or start a new one).
@Transactional
public class MyCustomAction extends AbstractHibernateAction {
...
}
The previous method is just a shortcut to using the Spring transaction template that is configured in the application backend controller. This is the second, more versatile way of using transactions in Jspresso :
public class MyCustomAction extends AbstractHibernateAction {
@Override
public boolean execute(IActionHandler actionHandler, final Map<String, Object> context) {
getBackendController(context).getTransactionTemplate().execute(new TransactionCallback() {
public Object doInTransaction(TransactionStatus txStatus) {
...
}
});
}
}
The second form allows for more control over the transaction(s) demarcation, e.g. chain 2 or more different transactions in the same action.
Before working on an entity transactionally you have to make a carbon copy of it into the UOW and do all the work on this copy. The backend controller provides the following methods to perform the carbon copy :
IEntity cloneInUnitOfWork(IEntity entity);
List<IEntity> cloneInUnitOfWork(List<IEntity> entities);
Several characteristics of the cloning process :
Note :
You should always make sure that you work on UOW entity clones inside a transaction. This is the only way that Jspresso can garantee the complete isolation of the transaction space. Obscure Hibernate exceptions like "entity with the same identifier was already registered in the session" are generally the consequence of working on entities that have not been cloned into the UOW.
Now that you are in an isolated space, you can do whatever you need without having the risk to polute the application session in case of a failure (either due to a functional violation or a technical error). Note that all operations made on the UOW entities are local to the unit of work, e.g. :
It is time to commit or rollback the transaction. This is automatically done by the Spring transaction template or indirectly at the end of the "execute" method of a Transactional annotated backend action.
Whenever the transaction is rolled back, the UOW is simply discarded and the session state remains untouched.
Whenever the transaction is committed, we have to merge back the UOW state that needs to be propagated to the application session current state.
This last step is automatically performed for created/updated entities whose state has been synchronized (flushed) to the store on transaction commit. During this phase, the entity state is merged back to the correponding session entity (if any) and its dirty state is reset to unmodified.
This is all perfect for save operations; however, there are situations when you need to apply another type of merge from the UOW entities. These other merge modes cannot be automatically inferred by the framework, and thus, must be explicitely coded through the use of the following backend controller methods :
IEntity merge(IEntity entity, EMergeMode mergeMode);
List<IEntity> merge(List<IEntity> entities, EMergeMode mergeMode);
Several characteristics of the merging process :
Jspresso implements 4 different merge modes :