I want to rollback my transaction on an exception which works absolutely fine. But now I do not want to rollback all actions I made.
For example, a request to my application gets processed and several database actions on multiple databases are done. If the exception is thrown I want to rollback all actions on one of my databases and only 2 actions on the second database. How can I do that? I always end up rolling bback the whole transaction...
what I tried so far:
#TransactionAttribute(TransactionAttributeType.REQUIRED)
public <Param> GenericResponseMsg executeRequest (Param myParam) {
entityManager1.persist(someEntity); // rollback
...
}
#TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void addFailedRequestToDatabase() {
entityManager2.persist(otherEntity); // do not rollback
...
}
I also tried to annotate the class with
#TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
but this results in the following exception:
Internal Exception: java.sql.SQLException: Connection can not be used while enlisted in another transaction
Error Code: 0
Any ideas? I am somehow stuck and don't know what to do anymore...
EDIT:
Here is the workflow you asked for:
#Stateless
#Path("my/path")
#TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public class MyRessource {
#EJB
private MyEJB myEjb;
#POST
#Path("method/Path")
#Consumes({MediaType.APPLICATION_JSON})
#Produces({MediaType.APPLICATION_JSON})
public GenericResponseMsg doSomeStuff(Param param) throws Exception {
try {
return myEjb.executeRequest(param);
} catch(Throwable throwable) {
myEjb.addFailedRequestToDatabase();
throw throwable;
}
}
}
#TransactionAttribute is support for EJB as per the specification. #Transactional support is coming in tomee 7 (currently under vote #asf).
Side note: tomee and openejb standalone code is 100% the same for JTA support.
Related
Let's say I have a #KafkaListener class with a #KafkaHandler method inside that processes any received messages and does some DB operations.
I want to have fine-grained control over how and when to commit (or rollback) the database changes (i.e. manually manage the DB transaction) in this class. The consumed message offset can be committed regardless of the DB transaction result.
Here is a simplified version of what I have:
#Service
#RequiredArgsConstructor
#KafkaListener(
topics = "${kafka.topic.foo}",
groupId = "${spring.kafka.consumer.group-id-foo}",
containerFactory = "kafkaListenerContainerFactoryFoo")
public class FooMessageConsumer {
// ...
private final EntityManager entityManager;
#KafkaHandler
public void handleMessage(FooMessage msg) {
// ...
handleDBOperations(msg);
// ...
}
void handleDBOperations(msg) {
try {
entityManager.getTransaction().begin();
// ...
entityManager.getTransaction().commit();
} catch (Exception e) {
log.error(e.getLocalizedMessage(), e);
entityManager.getTransaction().rollback();
}
}
}
When a message is received and entityManager.getTransaction().begin(); is invoked, this results in an exception:
java.lang.IllegalStateException: Not allowed to create transaction on shared EntityManager - use Spring transactions or EJB CMT instead
Why am I not allowed to create a transaction here?
And if I remove EntityManager and add #Transactional annotations to methods with DB operations (though this is not exactly what I want), then it results in another exception:
TransactionRequiredException Executing an update/delete query
It seems like it completely ignores the annotation. Is this somehow related to Kafka consumer having its own transaction management?
In short, what am I doing wrong here and how can I manage DB transactions in a #KafkaHandler method?
Any help is appreciated.
Thanks in advance.
Try using Springs TransactionTemplate:
https://docs.spring.io/spring-framework/docs/3.0.0.M4/reference/html/ch10s06.html
If your use case is (that) simple, Springs declarative transaction management should also let you achieve the behavior you ask for:
https://docs.spring.io/spring-framework/docs/3.0.0.M3/reference/html/ch11s05.html
It's not exactly as the title says, but close to. Consider these Spring beans:
#Bean
class BeanA {
#Transactional(propagation = Propagation.REQUIRED, rollbackFor = EvilException.class)
public void methodA() {
/* ... some actions */
if (condition) {
throw new EvilException();
}
}
}
#Bean
class BeanB {
#Autowired private BeanA beanA;
final int MAX_TRIES = 3;
#Transactional(propagation = Propagation.NESTED)
public void methodB() {
// prepare to call Bean A
try {
beanA.methodA();
/* maybe do some more things */
}
catch (EvilException e) {
/* recover from evil */
}
}
}
#Bean
class MainWorkerBean {
#Autowired private BeanB beanB;
#Autowired private OtherBean otherBean;
#Transactional(propagation = Propagation.REQUIRED)
public void doSomeWork() {
beanB.methodB();
otherBean.doSomeWork();
}
}
Important note: I'm using JDBC transaction manager that supports savepoints.
What I'm expecting this to do is, when EvilException is thrown, the transaction of the BeanA is rolled back, which with this setup happens to be the savepoint created by starting methodB. However, this appears to not be the case.
When going over with debugging tools, what I'm seeing is this:
When doSomeWork of MainWorkerBean starts, new transaction is created
When methodB starts, transaction manager properly initializes a savepoint and hands it to TransactionInterceptor
When methodA starts, transaction manager sees Propagation.REQUIRED again, and hands out a clean reference to the actual JDBC transaction again, that has no knowledge of the savepoint
This means that when exception is thrown, TransactionStatus::hasSavepoint return false, which leads to roll back of the whole global transaction, so recovery and further steps are as good as lost, but my actual code has no knowledge of the rollback (since I've written recovery for it).
For now, I can't consider changing BeanA's transaction to Propagation.NESTED. Admittedly, looks like it's going to allow me to have the more local rollback, but it's going to be too local, because as I understand it, Spring then will have two savepoints, and only roll back the BeanA savepoint, not BeanB one, as I'd like.
Is there anything else I'm missing, such as a configuration option, that would make internal transaction with Propagation.REQUIRED consider that it is running inside a savepoint, and roll back to savepoint, not the whole thing?
Right now we're using Spring 4.3.24, but I already crawled through their code and can't spot any relevant changes, so I don't think upgrading will help me.
As described in this bug ticket: https://github.com/spring-projects/spring-framework/issues/11234
For spring versions < 5.0, in the situation described, the global transaction is set to 'rollback-only'.
In this transaction I am processing several tasks. If an error should occur during a single task, I do not want the whole transaction to be rolled back, therefore I wrap the task processing in another transaction boundary with a propagation of PROPAGATION_NESTED.
The problem comes when, during task processing, calls are made to existing service methods defined with a transaction boundary of PROPAGATION_REQUIRED. Any runtime exceptions thrown from these methods cause the underlying connection to be marked as rollback-only, rather than respecting the current parent transaction nested propagation.
[...]
As of Spring Framework 5.0, nested transactions resolve their rollback-only status on a rollback to a savepoint, not applying it to the global transaction anymore.
On older versions, the recommended workaround is to switch globalRollbackOnParticipationFailure to false in such scenarios.
However, even for Spring5, I noticed when reproducing the problem, that the nested transaction may be rolled back, including all things done in the catch block of methodB(). So your recover code might not work inside methodB(), depending on what your recovery looks like. If methodA() was not transactional, that would not happen. Just something to watch out for.
Some more details to be found here: https://github.com/spring-projects/spring-framework/issues/8135
In Spring TransactionSynchronization interface it has methods (in order of execution):
- beforeCommit
- beforeCompletion
- afterCommit: Can perform further operations right after the main transaction has successfully committed.
- afterCompletion
Why Spring doesn't have rollback methods, such as beforeRollback or afterRollback but it has for commit only (beforeCommit and afterCommit)? Will this is necessary? Can anyone give me some advices or explains about this?
If I want to continue further operations that are supposed to follow on a successful rollback of the main transaction, like notification messages or emails what should I do in this case?
#Override
public void afterCompletion(int status) {
if (status == TransactionSynchronization.STATUS_ROLLED_BACK) {
logger.trace("Rolled back...");
}
}
Can I ask why you are using this interface?
This is used for callbacks by the PlatformTransactionManager which is in charge of managing the transaction lifecycle.
The "normal" use of Spring Transactions is to utilise #Transactional annotations, aop declarations in xml or TransactionTemplate / PlatformTransactionManager programatically to set the transaction scope, behaviour and visibility, as described in the docs - here.
To manage a transaction behaviour for rollbacks etc you just tell spring what to do in the event of an Exception
public class Foo {
#Autowired public Service someService;
#Transactional(propagation = Propagation.REQUIRES_NEW,
noRollbackFor = {IOException.class})
public boolean bar(SomeObject someObject) throws IOException {
someService.doComplicatedThing(someObject.getValue());
}
}
This tells the PlatformTransactionManager to start a new TX when hitting the foo() method, commit on successful return or an IOException, and rollback if there is some other type of exception. This is the genius of it - you dont need to worry about littering your code with getTransaction().isActive() and complicated checks for managing the isolation.
I am building a restful webservice using Jax-RS and EJB3 running on wildfly 8. The system is splited into two parts. A rest module and a persistence module. The rest module consists of a rest servlet with a method, reacheable under "mycontext/rest/do". This method calls the method "do()" of a stateless bean which represents the interface of the persistence module. "do()" calls "persistEntity(Entity e)" of another stateless bean which represents a facade (kind of an interface pattern) of a entity class. The entity I want to persist has a few constraints. If they get violated an exception like "ConstraintViolationException" gets thrown (by the EJB container I think). I try to catch the exception in one of the two stateless beans in the persistence module but this is not working. That means that the code in the catch block is not executed. But catching the exception in the servlet works.
Rest servlet (catching exception works here):
#Path("/rest")
public class AccountRestService {
#Inject
private Service service;
#POST
public void do(Entity entity) {
service.do(entity);
}
}
Bean representing interface of the persistence module (catching exception is ignored):
#Stateless
#LocalBean
public class Service {
#Inject
private EntityFacade facade;
public void do(Entity entity) {
facade.persist(entity);
}
}
Entity Facade (catching exception is ignored):
#Stateless
public class EntityFacade{
#PersistenceContext(unitName = "primary")
private EntityManager em;
public void create(Entity entity){
em.persist(entity); // this line throws exception like ConstraintViolationException
}
}
That's expected. The exception is thrown at flush time. The flush happens at transaction-commit time. And the commit happens after the EJB method has returned successfully.
So, contrary to what you say, it's not the persist() method which throws the exception (the stack trace would confirm that), but the method of the transactional proxy which wraps your EJB and commits the transaction.
There is really nothing you can do with such an exception except
show an error message to the user. In that case, catching the exception in the rest service, or letting a generic exception handler handle it is the right thing to do
retry the transaction. In that case, you should have an intermediate layer (or method) between the rest service method and the EJB, which calls the transactional EJB, catches the exception and retries.
I have a Spring and hibernate application (both latest version) and I have 2 beans as mentioned below
#Component
public class Bean1{
#Autowired
Bean2 bean2;
#Transactional(propagation = Propagation.REQUIRED)
public void foo()
{
bean2.bar();
}
#Component
public class Bean2{
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void bar()
{
try{
// Do something which throws exception
}
catch (Exception e) {
log & eat the exception here only.
But inspite of this the outer transaciton gets rolled back
}
}
The issue is that when bean2.bar causes any exception (e.g. foreign Key ConstraintViolationException) then it rolls back the outer transaction as well saying " Transaction rolled back because it has been marked as rollback-only","moreInfo":""}"
On seeing hibernate logs I found only one line for "new transaction"
D| o.s.o.h.HibernateTransactionManager- Creating new transaction with name ... PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''
which means no new transaction is getting created for the inner bean2.bar();
I am not able to find out what's wrong here? Any help is greatly appreciated.
REQUIRES_NEW applies to JTA transaction Managers only.
Refer Spring Doc here
REQUIRES_NEW
public static final Propagation REQUIRES_NEW
Execute non-transactionally, suspend the current transaction if one
exists. Analogous to EJB transaction attribute of the same name. Note:
Actual transaction suspension will not work on out-of-the-box on all
transaction managers. This in particular applies to
JtaTransactionManager, which requires the
javax.transaction.TransactionManager to be made available it to it
(which is server-specific in standard J2EE).