I have methods annotated #Transactional and I would like to detect rollbacks and commits.
Does exist any way or any Spring object which can check if transaction was commited or rollbacked?
you can write a unit test cases which is using h2 in-memory database and it will acts like a real db. for example save(obj) and after get(obj).
Also you can write test case which is catch exception that is what you expect(probably when occur a rollback there is also a exception) with this format:
#Test(expected = Exception.class)
Testclass
just run your #Transactional method and then, in another transaction, check what's in db.
and remember to make your tests clean/prepare db before each test (after all the last one could have committed something)
Related
I have a API , That looks like below
#Transaction
void method (){
try{
service1.insertOne();
service2.insertTwo();
}
catch(Exception ex) {
// log exception
}
}
In Service classes, I am checking for certain validation and I am throwing an exception which is a subclass of RuntimeException. When i throw this exception, javax.persistence.RollbackException: Transaction marked as rollbackOnly . While it is preventing the data in the first service from not being inserted , since the second service validation has failed, I am not quite quite sure if this is the right way to handle this scenario.
In case if the Exception is not a sub-class of Exception, even when the validation for service2 fails , data in service1 gets inserted, but i do see the custom exception being thrown. So i am not sure where i am going wrong. Any help is appreciated.
the Spring Framework’s transaction infrastructure code only marks a transaction for rollback in the case of runtime, unchecked exceptions; that is, when the thrown exception is an instance or subclass of RuntimeException.
https://docs.spring.io/spring/docs/current/spring-framework-reference/data-access.html#transaction-declarative-rolling-back
So, it must stay as a type of RuntimeException.
Probably you have Transactional annotation on top of service1.insertOne and/or service2.insertTwo() methods and that's why you get javax.persistence.RollbackException: Transaction marked as rollbackOnly.
You don't need those additional annotations (if they're exist) since there is an already active transaction.
Check this answer out.
If your insertOne, and insertTwo methods are marked as #Transactional, default propagation is Required. So you should see all that executed in one transaction. Link to docs
In second case it works as it should as well. If you throw custom transaction, then Spring does not take case of it. You can update that default behavior, by providing rollbackFor. In that case you will expect almost the same situation as in first case.
So it is more question to you - do you want your validation to mark transaction as rollbackOnly, or not. I would say yes.
Perhaps question is too abstract. Let's say you are putting invoice, and positions - then I would expect this transaction to be rolled back. However, if you add group, and users... Group can go, since problem is with user.
Just use the noRollbackForoption of the #Transactional to specify for which exception should not trigger a rollback:
https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/transaction/annotation/Transactional.html#noRollbackFor--
#Transaction (noRollbackFor=YourRuntimeException.class)
void method (){
try{
service1.insertOne();
service2.insertTwo();
}
catch(Exception ex) {
// log exception
}
}
I am not quite quite sure if this is the right way to handle this scenario.
YES this way is fine, When you marked your method void method () as Transactional , you instructed whenever there is a runtime exception, please rollback . When a code block with in the #Transactional annotated method observes a runtimeexception during execution, the Spring framework will keep it is as flag ('rollbackOnly') under ThreadLocal variable. Please note that after completion of method() , spring will commit if it finds that the flag ('rollbackOnly') is false. Otherwise spring will instruct the transaction manager to rollback all of the inserts.
even when the validation for service2 fails , data in service1 gets inserted, but i do see the custom exception being thrown. So i am not sure where i am going wrong
As explained above.
I am using spring-boot(1.4.1) with hibernate(5.0.1.Final). I noticed that when I try to write to the db from within #TransactionalEventListener handler the call is simply ignored. A read call works just fine.
When I say ignore, I mean there is no write in the db and there are no logs. I even enabled log4jdbc and still no logs which mean no hibernate session was created. From this, I reckon, somewhere in spring-boot we identify that its a transaction event handler and ignore a write call.
Here is an example.
// This function is defined in a class marked with #Service
#TransactionalEventListener
open fun handleEnqueue(event: EnqueueEvent) {
// some code to obtain encodeJobId
this.uploadService.saveUploadEntity(uploadEntity, encodeJobId)
}
#Service
#Transactional
class UploadService {
//.....code
open fun saveUploadEntity(uploadEntity: UploadEntity, encodeJobId: String): UploadEntity {
// some code
return this.save(uploadEntity)
}
}
Now if I force a new Transaction by annotating
#Transactional(propagation = Propagation.REQUIRES_NEW)
saveUploadEntity
a new transaction with connection is made and everything works fine.
I dont like that there is complete silence in logs when this write is dropped (again reads succeed). Is there a known bug?
How to enable the handler to start a new transaction? If I do Propogation.Requires_new on my handleEnqueue event, it does not work.
Besides, enabling log4jdbc which successfully logs reads/writes I have following settings in spring.
Thanks
I ran into the same problem. This behavior is actually mentioned in the documentation of the TransactionSynchronization#afterCompletion(int) which is referred to by the TransactionPhase.AFTER_COMMIT (which is the default TransactionPhase attribute of the #TransactionalEventListener):
The transaction will have been committed or rolled back already, but the transactional resources might still be active and accessible. As a consequence, any data access code triggered at this point will still "participate" in the original transaction, allowing to perform some cleanup (with no commit following anymore!), unless it explicitly declares that it needs to run in a separate transaction. Hence: Use PROPAGATION_REQUIRES_NEW for any transactional operation that is called from here.
Unfortunately this seems to leave no other option than to enforce a new transaction via Propagation.REQUIRES_NEW. The problem is that the transactionalEventListeners are implemented as transaction synchronizations and hence bound to the transaction. When the transaction is closed and its resources cleaned up, so are the listeners. There might be a way to use a customized EntityManager which stores events and then publishes them after its close() was called.
Note that you can use TransactionPhase.BEFORE_COMMIT on your #TransactionalEventListener which will take place before the commit of the transaction. This will write your changes to the database but you won't know whether the transaction you're listening on was actually committed or is about to be rolled back.
I have a JavaEE application using Hibernate to connect to the database. In some part of my application I have calls to method which have a #Transactional annotation. In some of these cases, I want to rollback the whole transaction (the outer-service-method call, and the inner). And on some occasions I want to rollback only the inner service-method call (that is, rollback to savepoint defined at the start of the internal method).
The first part is already in place, but I have a problem with the second one. When I do the following, I get a "UnexpectedRollbackException" with the message "Transaction rolled back because it has been marked as rollback-only".
#Service
public class OuterService{
#AutoWired
private InnerServcie innerService;
#Transactional
public void outer(){
try{
innerService.inner();
}catch(RuntimeException e){
//if i dont throw this up, it will give me the "UnexpectedRollbackException"
System.out.println("I cought a RuntimeException");
}
}
}
#Service
public class InnerServcie{
#Transactional
public void inner(){
//here we insert some data into db using hibernate
//but something goes wrong and an exception is thrown
}
}
The feature you are looking for is called savepoints. They are not, strictly saying, nested transactions, but milestones in the consequent SQL instruction chain, to which you can rollback. Rolling back to savepoint means invalidating ALL instructions issued from the moment of creating the savepoint, so you can have multiple savepoints, but you can only rollback instructions between now and savepoint, not between 2 savepoints!
Spring supports savepoints, both when using JdbcTransactionObjectSupport manually, and using #Transactional annotation.
According to the document http://docs.spring.io/spring/docs/2.5.3/reference/transaction.html point 9.5.7.3 you should use Propagation.NESTED.
However, that options may not be available in your case. From Javadoc:
Note: Actual creation of a nested transaction will only work on
specific transaction managers. Out of the box, this only applies to
the JDBC DataSourceTransactionManager when working on a JDBC 3.0
driver. Some JTA providers might support nested transactions as well.
As last resort, you can issue SQL instructions starting/rollbacking to savepoint directly.
For PostgreSQL it would be:
SAVEPOINT foo;
ROLLBACK TO SAVEPOINT foo;
Source: http://www.postgresql.org/docs/8.2/static/sql-rollback-to.html
There is no support for nested transactions in Spring/Hibernate/Java EE. So, either the whole thing is rollbacked, or the inner transaction is actually a new, different transaction, that will be committed as soon as it succeeds, and even if the outer transaction rollbacks later.
If the latter is what you want, then simply annotate your inner method with
#Transactional(propagation = Propagation.REQUIRES_NEW)
Try setting the globalRollbackOnParticipationFailure attribute of your TransactionManager to false.
See http://docs.spring.io/spring/docs/3.1.4.RELEASE/javadoc-api/org/springframework/transaction/support/AbstractPlatformTransactionManager.html#setGlobalRollbackOnParticipationFailure(boolean) for more information.
My Class is defined as
#Transactional
#Service
public class InteractionHistoryServiceImpl implements InteractionHistoryService {
Within the class I have a single method that makes 4 separate calls to separate DB2 stored procedures as follows (pseudo):
#Override
public void createInteractionHistory(String userId, String userChannel,
CreateInteractionHistoryRequestPayload requestPayload) {
dao.createInteractionHistory(userId, userChannel, viewableBy,
interactCode, systemCreationDateTime, partyGrpId,
interactionHistoryController);
dao.createInteractionHistoryDetails(userId, interactonDescription, interactionHistoryController);
dao.createInteractionHistoryDetailsLink(userId, componentId, objectId, objectType, correspondanceType, direction, interactionHistoryController);
dao.createInteractionHistoryDetailsLink(userId, componentId, documentId, DOCUMENT, correspondenceType, direction, interactionHistoryController);
}
Now in the final call I force an exception in the database by passing a field width that's too big so it cannot call the proc at all. This is captured and converted into a system exception that extends RuntimeException.
I've debugged this code after the exception and it feeds into the spring framework and appears to be rolling things back.
When I check the underlying tables I can see that all the previous stored procs have succeeded and not rolled back but the final call did not succeed.
This is leaving me with inconsistent data and a headache as my understanding is that these should be handled all or nothing commit/rollback.
I've checked the stored procs and there are no commits going on in the database.
Any ideas here?
You need to consider your transaction boundary. Transaction boundary is typically where your BEGIN TRANSACTION and COMMIT / ROLLBACK statement is executed, and it's not entirely up to Spring, but could also be database implementation dependant.
Specifying #Transactional at the class level indicates every single public method of the class has a declarative transaction boundary (ie: a transaction will be started before the method executes, comitted when method finished / rolled back when exceptions occured)
In your case, it seems you did not enclose the 4 createInteractionHistory invocation in one single transaction -- and they are 4 separate transactions instead. Hence when the last one failed, the first 3 already succeed.
However even though you enclosed all those 4 invocation in a single transaction, it is not guaranteed that all your SQL will run in one transaction, since it depends on what code you have inside your stored proc and how DB2 draws the transaction boundary.
I suggest you spend some time on Spring transaction boundary and propagation topics -- and also DB2 transactions. Chapter 11 of the spring manual is a good reference.
I am looking to retrofit our existing transaction API to use Spring’s PlatformTransactionManager, such that Spring will manage our transactions. I chained my DataSources as follows:
DataSourceTransactionManager - > LazyConnectionDataSourceProxy - > dbcp.PoolingDataSource - > OracleDataSource
In experimenting with the DataSourceTransactionManager , I have found that where PROPAGATION_REQUIRES_NEW is used, it seems that Spring’s transaction management requires that the transactions be committed/rolled back in LIFO fashion, i.e. you must commit/rollback the most recently created transactions first.
Example:
#Test
public void testSpringTxns() {
// start a new txn
TransactionStatus txnAStatus = dataSourceTxnManager.getTransaction(propagationRequiresNewDefinition); // specifies PROPAGATION_REQUIRES_NEW
Connection connectionA = DataSourceUtils.getConnection(dataSourceTxnManager.getDataSource());
// start another new txn
TransactionStatus txnBStatus = dataSourceTxnManager.getTransaction(propagationRequiresNewDefinition);
Connection connectionB = DataSourceUtils.getConnection(dataSourceTxnManager.getDataSource());
assertNotSame(connectionA, connectionB);
try {
//... do stuff using connectionA
//... do other stuff using connectionB
} finally {
dataSourceTxnManager.commit(txnAStatus);
dataSourceTxnManager.commit(txnBStatus); // results in java.lang.IllegalStateException: Cannot deactivate transaction synchronization - not active
}
}
Sadly, this doesn’t fit at all well with our current transaction API which allows you to create transactions, represented by Java objects, and commit them in any order.
My question:
Am I right in thinking that this LIFO behaviour is fundamental to Spring’s transaction management (even for completely separate transactions)? Or is there a way to tweak its behaviour such that the above test will pass?
I know the proper way would be to use annotations, AOP, etc. but at present our code is not Spring-managed, so it is not really an option for us.
Thanks!
yes,I have met the same problems below when using spring:
java.lang.IllegalStateException: Cannot deactivate transaction synchronization - not active.
According above,Spring’s transaction management requires that the transactions be committed/rolled back in LIFO fashion(stack behavior).The problem disappear.
thanks.
Yes, I found this same behavior in my own application. Only one transaction is "active" at a time, and when you commit/rollback the current transaction, the next active transaction is the next most recently started transaction (LIFO/stack behavior). I wasn't able to find any way to control this, it seems to be built into the Spring Framework.