Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Migrated to Confluence 4.0
Wiki Markup
h1.


h1. Transactions

h3. Methods of Configuring Transactions

We have various ways of configuring transactions in our apps:


{panel}

h5. 1. TransactionProxyFactoryBean

For each bean to be considered for transactional behavior, an XML bean is declared with the TransactionProxyFactoryBean as its parent, and with the target bean as a property. The TransactionProxyFactoryBean itself is also configured via XML: it is made aware of the transaction manager, and method-level transaction behavior for all "children" is specified. When the Spring container initializes, the target bean is wrapped with a transactional proxy object; all the interfaces implemented by the target object are proxied.
{panel}

{panel}

h5. 2. AOP

Transactional behavior is defined via AOP pointcuts and a transaction advice specifier in the Spring XML configuration. By default, Spring creates a proxy class which intercepts calls to the target methods.
{panel}

{panel}

h5. 3. @Transactional annotation

Transactional behavior is defined using annotations in the transactional classes themselves. By default, Spring creates a proxy class which intercepts calls to the target methods.
{panel}
It appears that both the AOP and @Transactional configuration can be used together; the same proxy object will be used.


What all these schemes are doing is allowing us to configure all aspects of our database transactions, for example to specify where the transactional boundaries should be in our application.

So, given three methods of configuration, what should we use?

Of the three methods for configuring transactions, the  TransactionProxyFactoryBean is the oldest and should probably no longer  be used. The Spring docs go into detail about AOP and @Transactional,  only mentioning TransactionProxyFactoryBean in a sidebar. I wouldn't be  surprised if TransactionProxyFactoryBean were deprecated and dropped in  future versions of Spring.

The two remaining methods each have pros and cons.


@Transactional is close to the code it affects, thus making it easy to see and control where the behavior is applied. But moving exclusively to @Transactional would mean modifying a lot of existing Java code and converting existing project configurations.

AOP allows transactions to be configured without modifying Java code. So no changes to existing code are needed to add this behavior where we want. But AOP pointcuts can be tricky to get right: it's easy to specify a pointcut that doesn't hit the methods you want, and you will never see any error messages. Conversely it might be easy to apply behavior to the wrong methods because of an incorrectly specified pointcut.


Although AOP and @Transactional can be mixed, there could be issues  with unpredictable behavior where the two configurations collide. For  this reason, it is probably not a good practice to mix, or if we do, define  concrete standards - for example use @Transactional in the controller  and AOP in the service level.

Here's an example of AOP configuration:
{code}

Transactions

Methods of Configuring Transactions

We have various ways of configuring transactions in our apps:

Panel
1. TransactionProxyFactoryBean

For each bean to be considered for transactional behavior, an XML bean is declared with the TransactionProxyFactoryBean as its parent, and with the target bean as a property. The TransactionProxyFactoryBean itself is also configured via XML: it is made aware of the transaction manager, and method-level transaction behavior for all "children" is specified. When the Spring container initializes, the target bean is wrapped with a transactional proxy object; all the interfaces implemented by the target object are proxied.

Panel
2. AOP

Transactional behavior is defined via AOP pointcuts and a transaction advice specifier in the Spring XML configuration. By default, Spring creates a proxy class which intercepts calls to the target methods.

Panel
3. @Transactional annotation

Transactional behavior is defined using annotations in the transactional classes themselves. By default, Spring creates a proxy class which intercepts calls to the target methods.

It appears that both the AOP and @Transactional configuration can be used together; the same proxy object will be used.

What all these schemes are doing is allowing us to configure all aspects of our database transactions, for example to specify where the transactional boundaries should be in our application.

So, given three methods of configuration, what should we use?

Of the three methods for configuring transactions, the TransactionProxyFactoryBean is the oldest and should probably no longer be used. The Spring docs go into detail about AOP and @Transactional, only mentioning TransactionProxyFactoryBean in a sidebar. I wouldn't be surprised if TransactionProxyFactoryBean were deprecated and dropped in future versions of Spring.

The two remaining methods each have pros and cons.

@Transactional is close to the code it affects, thus making it easy to see and control where the behavior is applied. But moving exclusively to @Transactional would mean modifying a lot of existing Java code and converting existing project configurations.

AOP allows transactions to be configured without modifying Java code. So no changes to existing code are needed to add this behavior where we want. But AOP pointcuts can be tricky to get right: it's easy to specify a pointcut that doesn't hit the methods you want, and you will never see any error messages. Conversely it might be easy to apply behavior to the wrong methods because of an incorrectly specified pointcut.

Although AOP and @Transactional can be mixed, there could be issues with unpredictable behavior where the two configurations collide. For this reason, it is probably not a good practice to mix, or if we do, define concrete standards - for example use @Transactional in the controller and AOP in the service level.

Here's an example of AOP configuration:

Code Block
    <aop:config>
        <aop:advisor pointcut="execution(* *..StudentService.*(..))" advice-ref="txAdvice" />
        <aop:advisor pointcut="execution(* *..InstructorRoleAuthorizationService.*(..))" advice-ref="txAdvice" />
        <aop:advisor pointcut="execution(* *..StudentRoleAuthorizationService.*(..))" advice-ref="txAdvice" />
        <aop:advisor pointcut="execution(* *..StvRolesAuthorizationService.*(..))" advice-ref="txAdvice" />
        <aop:advisor pointcut="execution(* *..ApplicationVariableService.*(..))" advice-ref="txAdvice" />
    </aop:config>

    <tx:advice id="txAdvice" transaction-manager="transactionManager" >
        <tx:attributes>
            <tx:method name="save*" propagation="REQUIRED" rollback-for="*" />
            <tx:method name="delete*" propagation="REQUIRED" rollback-for="*" />
            <tx:method name="*" propagation="REQUIRED" read-only="true" />
        </tx:attributes>
    </tx:advice>
{code}
The first part - <aop-config> - defines which parts of the code (join points) should participate in the transactions, using AspectJ-style pointcut expressions. For example,

...

&nbsp; "*{_}execution(\* ..StudentService.(..))_*" means that for every execution of a method in the StudentService class, the behavior specified by "txAdvice" should come into play.

...



The second part,

...

&nbsp; <tx:advice... specifies the transaction "advice" or behavior that should be applied to the join points associated with the advice, and also associates the transaction manager (specified elsewhere) with the advice. The example allows a finer-grained definition of methods and their transaction characteristics (e.g. all methods with names starting with "save" and "delete" will require a transaction and will roll back if any exception is encountered). The third <tx:method> specifier in the example is the default - methods not matching the explicit method name patterns will get this default transaction behavior.

...

Transaction Propagation

...





h3. Transaction Propagation

The default propagation behavior is "REQUIRED", which means that if there is a transaction in progress, the code will participate in that transaction, but it there is no transaction in progress, a new transaction will be created. Only in unusual circumstances would we want to specify different propagation behavior from this default.

...



h3. Rollback Rules

...




By default, if an unchecked exception (e.g. NullPointerException) is thrown during execution of code participating in a transaction, any uncommitted database updates will be rolled back. If no exceptions are thrown, *{_}or a checked exception (e.g. Exception) is thrown{_}*, the transaction will end with a commit. In the transaction configuration, you can optionally specify exceptions that should cause a rollback, thus getting rollback behavior for checked exceptions.

...



It might make sense, then, to specify rollback for java.lang.Exception.

h3.

...

 Read-Only / Read-Write Modes

...



Transactions can also be configured to be read-write (the default) or read-only.

...



A read-write transaction will always flush immediately before a transaction commit, whatever the OSIV interceptor flush-mode setting.

...



A read only transaction *{_}will not prevent updates{_}*. This is a little surprising, but updates can take place and be committed in a read-only transaction. This is because Oracle does not support read-only transactions over JDBC. Where the read-only and read-write transactions do differ is in the flush behavior. A read-write transaction will set the flush mode to automatic; this is why there is always a flush before a commit in a read-write transaction. A read-only transaction however, leaves the flush mode alone; so if the OSIV interceptor's flush mode is set to the default (i.e. never), no flush will take place at all, and changes will not be written to the database or committed. If the OSIV interceptor flush mode is set to AUTO, this flush mode will stay in effect for the read-write transaction, and a flush *will* occur before a commit. And of course if an explicit flush is coded within a read-only transaction, the flush will write updates to the database and, at the end of the transaction, committed.

...




This table summarizes the behavior:

...


|| OSIV Interceptor

...

 \\
Flush

...

 Mode \\ || Read-only/

...

 \\
Read-write

...

Explicit flush
in transaction?

...

Changes committed?

...

Default (NEVER)

...

RW

...

N

...

Y

...

Default (NEVER)

...

RW

...

Y

...

Y

...

Default (NEVER)

...

RO

...

N

...

N

...

Default (NEVER)

...

RO

...

Y

...

Y

...

AUTO

...

RW

...

N

...

Y

...

AUTO

...

RW

...

Y

...

Y

...

AUTO

...

RO

...

N

...

Y

...

AUTO

...

RO

...

Y

...

Y

So the only differences in behavior between read-only and read-write transactions come when the flush mode is set to NEVER (OSIV Interceptor default) and no explicit flushes are done in the code - changes are not committed.

Put another way, the only differences in behavior between the flush modes NEVER and AUTO come in a read-only transaction with no explicit flushes in the code - changes are not committed.

Finally, a read-only transaction does not enforce a read-only policy. Given this behavior, it's not clear how useful a read-only transaction is.

Hibernate Flushing

Flushing is the process of synchronizing the state of the database with the state of persistent Java objects held in the session. What this means in practice is SQL insert, update, and delete statements being issued based on earlier Hibernate save, update, delete, and modifications to persistent objects. So the flush will write changes to the database but will not commit them.

We can control to some extent when Hibernate flushes by manipulating the session's Flush Mode. With automatic flushing: Hibernate will flush before some queries to avoid inconsistent data - for example if you read some data, modify the object, then issue another query that would read the (not-updated) data again from the database, apparently Hibernate is smart enough to do a flush before the query, so that the query is getting your updated version of the data. Setting flush mode to manual would lose this benefit.

In our more recent web apps, a default flush mode is set in the OpenSessionInView interceptor. The default flush mode setting for the OSIV interceptor is NEVER, although OGS and OREG use a default flush mode setting of AUTO. The recommendation in the OSIV interceptor Javadoc is for all Hibernate operations to take place in transactions, with the transaction manager to handle flushing - in this scheme, OSIV should not need to flush. We need to revisit OGS & OREG on this issue. Update: see the Read-Only / Read-Write Modes section above for a description of how these two flush modes affect transaction behavior.

JPA - currently only has two flush modes; COMMIT (required to flush only before commit - may flush at other times) and AUTO (required to flush before any query and before commit). For this reason, we should possibly not use NEVER or MANUAL, as they are Hibernate-specific and will not translate to JPA.

Bottom line here is that with an ORM, flushing outside of your control is probably a given. We need to be aware that modifications to persistent entities will be synchronized to the database on a flush - sometimes in the middle of a transaction and certainly at the end of a transaction. I think this means:

  • Don't update a persistent object until you know all the data to be modified is valid.
  • Always make persistence operations explicit (i.e. after you modify a persistent object, issue a Hibernate update or saveOrUpdate.
  • Make sure transactions encompass all the changes you want to make in a unit of work. This means either a transaction around the controller method or around a facade service method that in turn calls finer-grained service methods.
  • Might be best not to use persistent objects directly as "form backing objects".

Conclusions

There's a lot of information here, so here's an attempt to boil it down into some practical advice:

...

 TX \\ || Explicit flush \\
in transaction? \\ || Changes committed? \\ ||
| Default (NEVER) | RW \\ | N \\ | Y \\ |
| Default (NEVER) | RW \\ | Y \\ | Y \\ |
| Default (NEVER) | RO \\ | N \\ | N \\ |
| Default (NEVER) | RO \\ | Y \\ | Y \\ |
| AUTO \\ | RW \\ | N \\ | Y \\ |
| AUTO \\ | RW \\ | Y \\ | Y \\ |
| AUTO \\ | RO \\ | N \\ | Y \\ |
| AUTO \\ | RO \\ | Y \\ | Y \\ |
So the only differences in behavior between read-only and read-write transactions come when the flush mode is set to NEVER (OSIV Interceptor default) and no explicit flushes are done in the code - changes are not committed.


Put another way, the only differences in behavior between the flush modes NEVER and AUTO come in a read-only transaction with no explicit flushes in the code - changes are not committed.

Finally, a read-only transaction does not enforce a read-only policy. Given this behavior, it's not clear how useful a read-only transaction is.



h3. Hibernate Flushing


Flushing is the process of synchronizing the state of the database with the state of persistent Java objects held in the session. What this means in practice is SQL insert, update, and delete statements being issued based on earlier Hibernate save, update, delete, and modifications to persistent objects. So the flush will write changes to the database *{_}but will not commit them{_}*.


We can control to some extent when Hibernate flushes by manipulating the session's Flush Mode. With automatic flushing: Hibernate will flush before some queries to avoid  inconsistent data - for example if you read some data, modify the  object, then issue another query that would read the (not-updated) data  again from the database, apparently Hibernate is smart enough to do a  flush before the query, so that the query is getting your updated  version of the data. Setting flush mode to manual would lose this  benefit.

In our more recent web apps, a default flush mode is set in the OpenSessionInView interceptor. The default flush mode setting for the OSIV interceptor is NEVER, although OGS and OREG use a default flush mode setting of AUTO. The recommendation in the OSIV interceptor Javadoc is for all Hibernate operations to take place in transactions, with the transaction manager to handle flushing - in this scheme, OSIV should not need to flush. We need to revisit OGS & OREG on this issue. *Update*: see the _Read-Only / Read-Write Modes_ section above for a description of how these two flush modes affect transaction behavior.



JPA - currently only has two flush modes; COMMIT (required to flush  only before commit - may flush at other times) and AUTO (required to  flush before any query and before commit). For this reason, we should possibly not use NEVER or MANUAL, as they are Hibernate-specific and will not translate to JPA.


Bottom line here is that with an ORM, flushing outside of your control is probably a given. We need to be aware that modifications to persistent entities will be synchronized to the database on a flush - sometimes in the middle of a transaction and certainly at the end of a transaction. I think this means:

* Don't update a persistent object until you know all the data to be modified is valid.
* Always make persistence operations explicit (i.e. after you modify a persistent object, issue a Hibernate *update* or *saveOrUpdate*.
* Make sure transactions encompass all the changes you want to make in a unit of work. This means either a transaction around the controller method or around a facade service method that in turn calls finer-grained service methods.
* Might be best not to use persistent objects directly as "form backing objects".

h3. Conclusions

There's a lot of information here, so here's an attempt to boil it down into some practical advice:
# All hibernate operations must take place within a transaction.
# Care should be taken to identify "units of work" and make sure a single transaction is defined around each unit.
# This may mean declaring a transaction around each controller method that responds to an HTTP request.
# We should not use the TransactionProxyFactoryBean method of configuring transactions.
# We should use AOP for configuring transactions in the service layer and below.
# We can use AOP or @Transactional for configuring transactions in the controller layer. Any given app should choose one of these methods and use it consistently.
# We should think about what kinds of exceptions should trigger a transaction rollback, and configure the transactions appropriately. *By default, checked exceptions will NOT roll back a transaction*.
# We can control which exceptions force a rollback.
# We need to be aware that dirty persistent objects WILL trigger database  updates and commits unless a transaction is rolled back by the throwing  of an exception.
# We should use the default transaction propagation setting (REQUIRED) unless there is a clear reason for using a different setting.
# Flush modes still need to be discussed.