Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Migration of unmigrated content due to installation of a new plugin

Transactions

Methods of Configuring Transactions

We have various ways of configuring transactions in our apps:

...

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 Moving exclusively to @Transactional means would mean modifying a lot of existing Java code and converting existing project configurations.

AOP seems allows transactions to be the cleanest method - transactions can 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.

This leaves us with - using AOP.

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>

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,  "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,  <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

The default propagation behavior is For the most part, the propagation behavior is specified as "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.

Rollback Rules

By default, if 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.

If an attempt to perform a Hibernate operation outside of a transaction, and with no Hibernate session bound to the thread, an exception will be thrown: "No Hibernate Session bound to thread, and configuration does not allow creation of non-transactional one here.". The solution to this is to include the code inside a transaction.

The use of the Spring OpenSessionInView interceptor adds a wrinkle here. It makes a session available to non-transactional code, and Hibernate operations within this code will work, but... it appears that each operation takes place with autocommit set to true, so each operation will be wrapped in its own transaction. Not the behavior we want. In addition, if OpenSessionInView is used with flush mode = auto, any modifications to hibernate entities will be flushed after the completion of the controller (but before the JSP render).

So it's important to make sure we don't have any hibernate operations taking place outside of transactions - and that all update operations (ideally all operations) with in a single HTTP request are within the same transaction. We should think about putting transactions around controller methods. Just putting transactions around service methods could be a problem if a controller calls multiple service methods that do updates.

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.

@Transactional means modifying a lot of existing code and converting existing project configurations.

AOP seems to be the cleanest method - transactions can be configured without modifying code.

Although AOP and @Transactional can be mixed, there could be issues with unpredictable behavior where the two configurations collide. For this reason, probably not a good practice to mix.

This leaves us with - using AOP.

Question - should CSF have any transaction definition or should this be the responsibility of the application?

. 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.

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 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.

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: Flush - automatic flushing is probably a good thing. 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.It's probably best not to modify any Hibernate entity objects until you know all the data is valid AND to always to an explicit save, update, or delete, and not rely on the automatic flush to detect updates

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:

  1. All hibernate operations must take place within a transaction.
  2. Care should be taken to identify "units of work" and make sure a single transaction is defined around each unit.
  3. This may mean declaring a transaction around each controller method that responds to an HTTP request.
  4. We should not use the TransactionProxyFactoryBean method of configuring transactions.
  5. We should use AOP for configuring transactions in the service layer and below.
  6. 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.
  7. 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.
  8. We can control which exceptions force a rollback.
  9. 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.
  10. We should use the default transaction propagation setting (REQUIRED) unless there is a clear reason for using a different setting.
  11. Flush modes still need to be discussed.