A Transaction forms a unit of work. The Transaction manages what happens within that unit of work,
and when an error occurs the Transaction can roll back any changes performed.
There are the following types of transaction.
-
Transactions can lock all records in a datastore and keep them locked until they are ready
to commit their changes. These are known as
Pessimistic (or datastore) Transactions.
-
Transactions can simply assume that things in the datastore will not change until they are
ready to commit, not lock any records and then just before committing make a check for changes.
These are known as Optimistic Transactions.
-
Non-transactional where you perform operations effectively
in "auto-commit" mode
Pessimistic
transactions are the default in JDO. They are
suitable for short lived operations where no user interaction is taking place and so it is
possible to block access to datastore entities for the duration of the transaction.
By default DataNucleus does not currently lock the objects fetched in a pessimistic transaction,
but you can configure this behaviour for RDBMS datastores by setting the persistence property
datanucleus.SerializeRead
to
true
.
This will result in all "SELECT ... FROM ..." statements being changed to be
"SELECT ... FROM ... FOR UPDATE". This will be applied only where the underlying RDBMS
supports the "FOR UPDATE" syntax. This can be done on a transaction-by-transaction basis
by doing
org.datanucleus.jdo.JDOTransaction tx =
(org.datanucleus.jdo.JDOTransaction)pm.currentTransaction();
tx.setSerializeRead(true);
Alternatively, on a per query basis, you would do
org.datanucleus.jdo.JDOQuery jdoQuery =
(org.datanucleus.jdo.JDOQuery)pm.newQuery(...);
jdoQuery.setSerializeRead(true);
With a pessimistic transaction DataNucleus will grab a datastore connection at the first
operation, and maintain it for the duration of the transaction. A single connection is used for
the transaction (with the exception of any
Identity Generation operations which need datastore
access, so these can use their own connection).
In terms of the process of a pessimistic (datastore) transaction, we demonstrate this below.
|
Operation
|
DataNucleus process
|
Datastore process
|
|
Start transaction
|
|
|
|
Persist object
|
Prepare object (1) for persistence
|
Open connection.
Insert the object (1) into the datastore
|
|
Update object
|
Prepare object (2) for update
|
Update the object (2) into the datastore
|
|
Persist object
|
Prepare object (3) for persistence
|
Insert the object (3) into the datastore
|
|
Update object
|
Prepare object (4) for update
|
Update the object (4) into the datastore
|
|
Flush
|
No outstanding changes so do nothing
|
|
|
Perform query
|
Generate query in datastore language
|
Query the datastore and return selected objects
|
|
Persist object
|
Prepare object (5) for persistence
|
Insert the object (5) into the datastore
|
|
Update object
|
Prepare object (6) for update
|
Update the object (6) into the datastore
|
|
Commit transaction
|
|
Commit connection
|
So here whenever an operation is performed, DataNucleus pushes it straight to the datastore.
Consequently any queries will always reflect the current state of all objects in use.
However this mode of operation has no version checking of objects and so if they were updated
by external processes in the meantime then they will overwrite those changes.
It should be noted that DataNucleus provides two persistence properties that allow an amount of
control over when flushing happens with datastore transactions.
-
datanucleus.datastoreTransactionDelayOperations
when set to true will try to
delay all datastore operations until commit/flush.
-
datanucleus.datastoreTransactionFlushLimit
represents the number of dirty objects
before a flush is performed. This defaults to 1.
Optimistic
transactions are the other option in JDO.
They are suitable for longer lived operations maybe where user interaction is taking place and
where it would be undesirable to block access to datastore entities for the duration of the
transaction. The assumption is that data altered in this transaction will not be updated by
other transactions during the duration of this transaction, so the changes are not propagated
to the datastore until commit()/flush(). The data is checked just before commit to ensure the
integrity in this respect. The most convenient way of checking data for updates is to maintain
a column on each table that handles optimistic transaction data. The user will decide this
when generating their MetaData.
Rather than placing version/timestamp columns on all user datastore tables, JDO2 allows
the user to notate particular classes as requiring
optimistic
treatment. This is
performed by specifying in MetaData or annotations the details of the field/column to use for
storing the version - see versioning for JDO.
With JDO the version is added in a surrogate column, whereas a vendor extension allows
you to have a field in your class ready to store the version.
In JDO2 the version is stored in a surrogate column in the datastore so it also provides a
method for accessing the version of an object. You can call
JDOHelper.getVersion(object)
and this returns the version as an Object (typically Long or Timestamp). This will return null
for a transient object, and will return the version for a persistent object. If the object is
not
PersistenceCapable
then it will also return null.
In terms of the process of an optimistic transaction, we demonstrate this below.
|
Operation
|
DataNucleus process
|
Datastore process
|
|
Start transaction
|
|
|
|
Persist object
|
Prepare object (1) for persistence
|
|
|
Update object
|
Prepare object (2) for update
|
|
|
Persist object
|
Prepare object (3) for persistence
|
|
|
Update object
|
Prepare object (4) for update
|
|
|
Flush
|
Flush all outstanding changes to the datastore
|
Open connection.
Version check of object (1)
Insert the object (1) in the datastore.
Version check of object (2)
Update the object (2) in the datastore.
Version check of object (3)
Insert the object (3) in the datastore.
Version check of object (4)
Update the object (4) in the datastore.
|
|
Perform query
|
Generate query in datastore language
|
Query the datastore and return selected objects
|
|
Persist object
|
Prepare object (5) for persistence
|
|
|
Update object
|
Prepare object (6) for update
|
|
|
Commit transaction
|
Flush all outstanding changes to the datastore
|
Version check of object (5)
Insert the object (5) in the datastore
Version check of object (6)
Update the object (6) in the datastore.
Commit connection.
|
Here no changes make it to the datastore until the user either commits the transaction, or
they invoke flush(). The impact of this is that when performing a query, by default, the results
may not contain the modified objects unless they are flushed to the datastore before invoking the
query. Depending on whether you need the modified objects to be reflected in the results of the
query governs what you do about that. If you invoke flush() just before running the query
the query results will include the changes. The obvious benefit of optimistic transaction is that
all changes are made in a block and version checking of objects is performed before application
of changes, hence this mode copes better with external processes updating the objects.
See also :-
DataNucleus allows the ability to operate without transactions. With JDO this can be enabled
by setting the 2 properties
datanucleus.NontransactionalRead
,
datanucleus.NontransactionalWrite
to
true
(they default to true).
DataNucleus supports these allowing the ability to read objects and make updates outside of
transactions.
Persist and Delete non-transactional updates are committed to the datastore at the end of
the operation, so are in the datastore when control returns to your application.
Any other non-transactional updates are committed to the datastore
by the subsequent
transaction
, with the updates being queued until that point.
When a transaction is committed JDO will, by default, run its reachability algorithm
to check if any reachable objects have been persisted and are no longer reachable.
If an object is found to be no longer reachable and was only persisted by being reachable
(not by an explicit persist operation) then it will be removed from the datastore.
You can turn off this reachability check for JDO by setting the persistence property
datanucleus.persistenceByReachabilityAtCommit
to false.