How to execute code on rollback of JPA transaction - jpa

I'm using EclipseLink (JPA). I've got a service that renames some files and calls a DAO which updates database tables accordingly. This happens in a transaction, and there might be a rollback. So that the DB stays in sync with the file system, I want to at least attempt to revert the file rename, and log an error when that can't be done. Is there some way to register a Runnable (or similar) with the transaction so that it executes on rollback?
EclipseLink has SessionEventListener which can respond to pre (preRollbackTransaction) and post rollback events, and I can get the transaction from the EntityManager. So I was thinking I could create some kind of global registry. However, I can't figure out how to match up the SessionEvent available in the listener with the EntityTransaction available from EntityManager.getTransaction(). Is there some way to do this, or is there a more straightforward way to provide a callback on rollback?
I could simply use a try-catch and execute the revert code in the catch, but it is possible the rollback will occur after the session method is returned (i.e. higher up the stack).

Related

What's the difference between DbUpdateConcurrencyException and 40001 postgres code?

I have two transactions.
In first I select an entity, do validations, upload provided by client file to S3 and then update this entity with info about S3 file.
Second transaction is simply deleting this entity.
Now, assume that someone called first transaction and immediately second. Second one will proceed faster and first one will throw DbUpdateConcurrencyException, as selected entity no longer exists on update query.
I get DbUpdateConcurrencyException, when my transaction has IsolationLevel.ReadCommited. But if I set IsolationLevel.Serializable it throws InvalidOperationException with 40001 postgres code. Could someone explain why do I get different errors, because it seems to me that outcome should be the same, as both errors invoked by updating non-existing entity?
The 40001 error corresponds to the SQLSTATE serialization_failure (see the table of error codes).
It's generated by the database engine in serializable isolation level when it detects that there are concurrent transactions and this transaction may have produced a result that could not have been obtained if the concurrent transactions had been run serially.
When using IsolationLevel.ReadCommited, it's impossible to obtain this error, because choosing this level of isolation precisely means that the client-side doesn't want to have these isolation checks being done by the database.
On the other hand, the DbUpdateConcurrencyException is probably not generated by the database engine. It's generated by the entity framework. The database itself is fine with an UPDATE updating zero row, it's not an error at the SQL level.
I think you get the serialization failure if the database errors out first, and the DbUpdateConcurrencyException error if the database doesn't error out, but the second layer in the order of layering (the EF) does.
The typical way to deal with serialization failures, at the serializable isolation level, is for the client-side to retry the transaction when it gets a 40001 error. The retried transaction will have a fresh view of the data and hopefully will pass (otherwise, loop on retrying).
The typical way to deal with concurrency at lesser isolation levels like Read Committed it to explicitly lock objets before accessing them to force the serialization of concurrent transactions.

Is #PostRemove out of transaction?

I found following information from the spec. But it's not clear enough for me who is not an english native.
The PostPersist and PostRemove callback methods are invoked for an entity after the entity has been made persistent or removed. These callbacks will also be invoked on all entities to which these operations are cascaded. The PostPersist and PostRemove methods will be invoked after the database insert and delete operations respectively. These database operations may occur directly after the persist, merge, or remove operations have been invoked or they may occur directly after a flush operation has occurred (which may be at the end of the transaction). Generated primary key values are available in the PostPersist method.
My question is any transaction related jobs can be rolled back after #PostRemove?
Let's say my entity deletes some offline files on #PostRemove
class MyEntity {
#PostRemove
private void onPostRemove() {
// delete offline files related to this entity
// not restorable!
}
}
Is it possible that those offline files deleted from the storage and the entity still left in the database? (by rollback?)
Yes, it is possible that your files are deleted and your entites are still left in db after a rollback. #PostRemove is in transaction.
If you want to be absolutely sure that your files are deleted if and only if the transaction is successfully completed then you should delete the files after the commit() succeeds not using the callback methods. But if you also need to be sure that the entity is removed if and only if the file is deleted, then you have a problem. You need a transactional way of accessing the file system.
For a simple solution move your files into a to_be_deleted-folder during the db-transaction. Therefore you can use the callback methods. The files are finally deleted when commit() succeeds and restored on failure.
If you want to elaborate it a bit more and your application is running in a java EE container, then you might want to look at CDI events or even at a jca adapter. If you are using Spring you can register a TransactionSynchronizationAdapter see this answer.
It depends.
If you're using multiple flushes (EntityManager#flush()), the transaction could still be rolled back. Otherwise, any callbacks prefixed with Post are executed after the database transaction is complete.

How to set isolation level in #Transactional "READ_UNCOMMITTED". I am using EclipseLink 2.5.1-RC1

I have a requirement to start new Transaction within an ongoing Transaction so that an exception in 2nd transaction will rollback only new transaction not the old one.
This I am doing by setting propagation attribute in 2nd transaction like this:
#Transactional(propagation = Propagation.REQUIRES_NEW)
This created a new Transaction, but the new Transaction needs to read some uncommitted data of the first transaction (dirty read), and also update that data. This I am trying to do by setting isolation attribute as :
#Transactional(propagation = Propagation.REQUIRES_NEW, isolation=Isolation.READ_UNCOMMITTED)
This throws an Exception - InvalidIsolationLevelException, saying "Standard JPA does not support custom isolation levels - use a special JpaDialect for your JPA implementation".
Can any help me to implement JpaDialect? I am using Eclipse Link 2.5.1 .
Or can I some how close the first transaction before starting a new transaction? Since First transaction is closed, the Second will have no problem reading the data committed by First Transaction.
In JPA you can try somehting like this, not much certain on how to achieve similar in EclipseLink/Spring.
But the overall concept might remain same, get the underlying database connection & set appropriate isolation level.
java.sql.Connection connection = entityManager.unwrap(java.sql.Connection.class);
connection.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);
//-- Or with some other constant something like Isolation.READ_UNCOMMITTED
Afterwards, you might also want to reset the isolation level back to the default.
If you don't want to make changes, then you might need to implement JpaDialect overriding methods to accommodate the change for isolation level in transaction.
You can refer here which describes implementation for Hibernate, can try similar for EclipseLink.

Play Framework - JPA - #Transactional error?

I'm experiencing a very strange behaviour with Transactions using play-2.2.2 with JPA and Eclipse-Link.
My Controller-Action is annotated with #Transactional like this:
#Transactional
public static Result submitOrder() {
// class does call private Methods which persist some entities (methods not annotated)
//...
Action is calling private methods to persist data (This should happen in the same transaction since no other Transaction is started).
During the Methods calls (at random locations) data gets written to db (inserts and updates). Debuging shows that the same (active) transaction is used before and after the write. EntityTransactionImpl:commit is never executed and transaction stays active until request is finished ( watched play.db.jpa.JPA.em().getTransaction() )
How is it possible that the data is written although transaction is still active?
It breakes the setRollbackOnly Mechanism since already written data isn't rolled back.
May there be any kind of Timeout that issue these writes.
Can you suggest any debug-entry-point to narrow down the problem (where can i start debuging the actual write-operations, if not in EntityTransactionImpl:commit)?
Dependencies in build.sbt
persistence.xml
The above described behaviour seemed very odd at first, but then i read about FlushMode and now it makes sense.
The FlushMode of eclipselink as well as hibernate is set to FlushModeType.AUTO
FlushModeType.AUTO automatically flushes Entities to the DB when it thinks it's neccessary. This can be because of an readable operation (Query) on a Persited (but not flushed) Entity but it also happened somehow randomly during my observations.
This breaks the rollback-on-failure mechanism, which I thought must be the standard behaviour of #Transactional.
To achive a propper rollback (on failure or if setRollbackOnly() is set), of all persisted but not flushed entities on transcaction commit, you have to explicitly set the FlushMode at the beginning of your Action.
JPA.em().setFlushMode(FlushModeType.COMMIT);
If you're using Eclipselink, you can also set the following property to make it default behaviour:
<property name="eclipselink.persistence-context.flush-mode" value="commit" />
Links which helped me understand:
Eclipselink Context Flushmode
what to use flush mode auto or commit
performance tuning hibernate

Behaviour of #Post callback methods i.e. #PostUpdate, #PostCreate etc. in JPA 2.0

Hi All,
I am not able to understand the exact behavior of #Post callback methods. As mention in ProJPA book "When the SQL for deletion of an entity finally does get sent to the database, the PostRemove event will get fired. As with the PostPersist lifecycle event, the PostRemove event does not guarantee success. The enclosing transaction may still be rolled back".
My concern here is, if SQL DELETE statement is already fire then how transaction will be rolled back? If SQL DELETE statement is not able to delete the object then exception will be thrown and #PostDelete will not be executed. So, under what scenario transaction can be rolled back??
Thanks all for your time!!
According JPA specifications: the #PostRemove callback is executed after the remove operation on the EntityManager.
The key point to understand is that the remove operation on the EntityManager won't throw an exception if the remove fail. The transaction may be marked as "Rollback-Only" (i.e. it means that the transaction will be roll-backed when the transaction ends... and the exception will be thrown at the end of the transaction).
The JPA specifications indicates that the #PostRemove callback will be executed (in the same transaction as the remove operation of course) regardless of the flag "Rollback-Only".
It means that you can perform additional database operation in the #PostRemove : those operations will be part of the same transaction (and so will also be rollbacked if something went wrong). And the corollary : if something flag the transaction as rollback-only during the #PostRemove : the remove operation will not be executed on your database.