Spring save record and send message rollbacks - java

I have a class:
#Service
#Transactional
class MyService{
#Autowired MyTableRepository repository;
#Autowired FacebookMessageSender sender;
public void updateTableAndSendMessage(MyTable m){
m.setProcessed(1);
repository.save(m);
sender.sendMessageToFacebook(m);
}
}
Somewhere in the code:
List<MyTable> list=repository.findByProcessed(0);
for(MyTable m:list){
myService.process(m);
}
So, I have the following:
In the quartz job I retrieve all records with processed flag set to 0. Then I pass it to service, then processed flag becomes 1 and system sends message to Facebook messenger (wherever). But, if there's a sql exception occurs, before or after 'process' method execution, transaction will be rolled back and flag will be still 0. So Facebook message will be sent on next job launch, and again and again. I tried to break it down to 2 methods, one saves flag other sends message. But then what if message was not sent and I will have to rollback the transaction? So it's like deadlock. I need prevent FB message from being send on other exception and rollback DB changes on FB sending failure. How to do that in spring-data, afaik transaction will be committed after method ends. Thanks

The following code structure should work:
Service class
#Service
class TaskService {
#Autowired TaskRepository repository;
#Autowired FacebookMessageSender sender;
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void sendTaskNotification(Task task){
try {
task.setProcessed(1);
repository.save(task);
sender.sendMessageToFacebook(task);
}
catch (Throwable t) {
throw new RuntimeException(t);
}
}
}
Caller class
#Component
class TaskNotificationJob {
#Autowired TaskService service;
#Scheduled
#Transactional
public void sendTaskNotifications() {
for(Task task : repository.findByProcessed(0)) {
try {
service.sendTaskNotification(task);
}
catch (RuntimeException e) {
// Log the error, but don't rethrow the exception.
}
}
}
}
Explanation
a. Ensure that a Facebook message is not attempted if task cannot be saved
If a single task cannot be saved, an exception will be raised and the code will skip the part that attempts to send a Facebook message for the task. Therefore, this requirement is automatically met due to the way the code is structured.
b. Ensure that an error while sending a Facebook message rolls back the task
The lines catch(Throwable t) { throw new RuntimeException(t); } ensure that if any exception (including one that derives from Exception instead of RuntimeException) is thrown while either saving a task or sending a Facebook message for it, it gets re-thrown as a RuntimeException. This in turn will ensure that the #Transactional annotation surrounding the method will roll the database transaction back.
c. Ensure that an error while processing a task does not affect other tasks
Each task is processed in its own transaction (#Transactional(propagation = Propagation.REQUIRES_NEW)) so anything that gets committed before an error occurs, stays committed.
When an error occurs, the calling code catches it (catch (RuntimeException e)) so that the error does not bubble up to the transaction handler for the caller and caller is allowed to attempt and process remaining tasks, as required. Not having this catch clause would mean that the first RuntimeException raised in the service will also bubble up to the caller and will terminate the whole operation immediately, potentially leaving unprocessed tasks. It is important not to re-throw the caught exception at this point because re-throwing it would bubble it up and hence defeat the purpose of having the catch clause in the first place.

Related

Spring Boot scheduler thread stops randomly

I have an Scheduler in spring boot that fulfils a specific business task every X minutes. It works fine until it suddenly stops and does not engage anymore. There is no exception in the logs or any other logs. I need to restart the program for the scheduler to work again.
Sometimes the task of the scheduler goes wrong, and I throw an exception. To be able to handle those exceptions specifically, I wrote a custom ErrorHandler in Spring for the scheduler that resolves a seperate task for logging purposes. It is linked correctly to the scheduler and processes the task.
This issue can come up when an unhandled exception gets thrown inside of an ErrorHandler. I am not sure about the specifics, however a Runtime Exception thrown by an ErrorHandler (or a method inside of it) that gets propagated outside of it basically kills the scheduled thread for that task. Furthermore NOTHING gets written to the logs (no Exception message, nada).
The "easiest" way to resolve this is by wrapping the entirety of the method in a try/catch block catching Exception - although depending on why you have that Error Handler that might be a bad idea. This does not solve the underlying issue at hand, but it keeps the thread alive and allows you to log the issue.
Example:
public class MyErrorHandler implements ErrorHandler {
#Override
public void handleError(Throwable t) {
try {
//handle intended exception (ex. write to database or logs)
} catch (Exception e) {
//handle exception that was thrown while trying to handle the intended exception.
}
}

How to get exception thrown during state transition using spring state machine

I am trying to understand, how an exception thrown by an action during a state transition is possible. I‘ve this simple state machine configured:
transitions
.withExternal()
.source(State.A1)
.target(State.A2)
.event(Event.E1)
.action(executeAnActionThrowingAnException())
In my service class, I injected my state machine and send this event E1:
#Service
public class MyService() {
#Autowired
private StateMachine<State, Event> stateMachine;
public void executeMyLogic() {
stateMachine.start()
stateMachine.sendEvent(Event.E1);
// how to get thrown exception here
}
}
In my service I just want to know, if and why my state machine wasn‘t able to reached State.A2. Because a thrown exception is fetched by Spring state machine, I am not able to get any response after sending the event. But the state machine hasn‘t any error, which means that
stateMachine.hasStateMachineError()
will return false. So, how can I get the information in my service, that something went wrong and more importantly what?
I appriciate your help.
Best regards
For transitions exceptions, there's an overload for the actions method available in the TransitionConfigurer
action(Action<S,E> action, Action<S,E> error)
This means you can specify and additional action to be triggered, if there's an exception raised during the transition. The exception is available from the StateContext passed to the action.
When your error action is triggered, you can retrieve the exception with:
context.getException();
Inside the error action you can do a couple of things to deal with the exception:
do logging of the exception and the context
transition to some error state
clear the context and transition to the same state and try to execute some retry logic
add some additional info to the context and return the context to the caller
For example:
context.getVariables().put("hasError", true);
context.getVariables().put("error", ex);
And in your service(caller) you handle the exception as you like, for example:
public void executeMyLogic() {
stateMachine.start()
stateMachine.sendEvent(Event.E1);
if (stateMachine.getExtendedState().getVariables().containsKey("hasError") {
throw (RuntimeException)stateMachine.getExtendedState().getVariables().get("error")
}
}

EJB Interceptors and transaction lifecycle OR how to intercept a commit/failure event?

I have an EJB interceptor and I follow the BCE pattern suggested by Adam Bien, that is, all EJB calls on the boundary starts and finish a transaction which means there is no nested EJB calls (there might be nested CDI injected Bean calls though, but those should be inside the same transaction started at the ejb Boundary).
So in those ejb Boundaries I have an interceptor and I want to intercept or know if after the method call of the EJB the transacction commited already? (that is, if a EntityManager was involved that the COMMIT sql call was sent to the DB and returned succeesfuly)
Will I get that info from inside an Interceptor ?
If not, how can I get notified of a transaction that sucessfully commited or failed ?
NOTE: Of course, if I am the client of the EJB and I am calling the method, after the method call I know what happened with the transaction, but I am interested in intercepting that BEFORE the client receives the response from the EJB.
#AroundInvoke
public Object logMethodEntry(InvocationContext ctx) throws Exception {
Object proceed = null;
try {
proceed = ctx.proceed();
// is the transacction finished/commited already?
// is it still open ?
return proceed;
} catch (Exception e) {
throw e;
}
}
[UPDATE]: I accepted one good answer, but the thing is that THERE IS NO WAY in Java EE to receive an event of a transaction that HAS BEEN COMMITED. So regardless of the good answer, sadly there is no way to be notified in Java EE of a completed transaction, inside the server, of course, if you are the client caller, then you sure know the transaction commited or rolled back...
unless otherwise stated on the exception thrown, if an ejb method invocation throws an exception, it shall be rolled-back. Additionally, provided all calls to the DB were in the same transaction, they shall be deemed committed at the end of the transaction cycle.
In retrospect, all interceptors are invoked within the same transaction on which the ejb method it intercepts, was invoked (That's the reason the interceptor may decide in an event of an exception, to either roll-back or still commit the transaction).
Hence, you can know for sure, that the transaction completed successfully if within your interceptor call, after the proceed has been invoked and returned, there is no exception thrown, with a potential of transaction rollback.
So in your scenario:
#AroundInvoke
public Object logMethodEntry(InvocationContext ctx) throws Exception {
Object proceed = null;
try {
proceed = ctx.proceed();
// is the transacction finished/commited already?
// The transaction is successful, but afaik, it is not yet committed, until this method returns successfully
// is it still open ? More or less. You can still grab the Ejbtransaction and commit it manually or rollback if some other conditions have not been met yet
return proceed;
} catch (Exception e) {
//If this happens, and you propagate it, then for sure the transaction will be rolledback, and never get committed. Since all db calls were being done within this transaction, then no DB commit will be done.
throw e;
}
}
Edit:
for you to actually commit the transaction in an interceptor, you will need to be running application-managed transaction, otherwise, it is prohibited by the EJB specs to call commit on a container managed transaction, you can of course call setOnrollback method of the EJBContext.
Edit
If you truly want to do some DB changes, i would recommend:
user ApplicationManaged transaction, from which you manually start
and commit the transaction within the interceptor
Use the concept of the observer, and listen for #Observes(AFTER_SUCCESS) event which will be invoked when the
transaction is successfully committed and complete, and hence you
can be guaranteed to do a db call, and the new updates will be
available.
If you can ignore the BCE pattern, and spin off a new transaction to do the update, so that after it returns successfully, you will be guaranteed of commit, and then continue normally
```
#Stateless
public class TransactionService {
#TransactionAttribute(REQUIRES_NEW)
public Object executeTransaction(final Callable<Object> task) {
return task.call();
}
}
#Interceptor
public class MyInterceptor {
#EJB
private TransactionService service;
#AroundInvoke
public Object logMethodEntry(InvocationContext ctx) throws Exception {
Object proceed = null;
try {
proceed = service.executeTransactional(()->ctx.proceed());
//If you reach here, you will be guaranteed of commit and then you can do the elastic search update
return proceed;
} catch (Exception e) {
//If this happens, and you propagate it, then for sure the transaction will be rolledback, and never get committed. Since all db calls were being done within this transaction, then no DB commit will be done.
throw e;
}
}
}
Ok - the question is 4 years old now, but I think it makes still sense to give an answer.
You can for sure register a callback to get informed about the Transaction outcome. You simply use the registerSynchronization() API of javax.transaction.Transaction.
Transaction tx = ((TransactionManager) (new InitialContext()).lookup("java:/TransactionManager")).getTransaction();
tx.registerSynchronization(new Synchronization() {
public void beforeCompletion() {
// do stuff before completion
}
public void afterCompletion(int status) {
if (status == Status.STATUS_COMMITTED) {
// do something after successful commit }
}
});

Does the rollback transactions feature only work when an exception is thrown?

I have this declaration above my class declaration. Will spring only rollback when DAOException is thrown?
#Transactional(rollbackFor = { ManagerException.class, DAOException.class })
If that is the case, how can I rollback a bad commit without throwing an exception. Currently my database package functions return an error message, I want to roll back and still display the error message to the user without throwing an exception and crashing the app.
The documentation says:
You can also indicate a required rollback programmatically. Although very simple, this process is quite invasive, and tightly couples your code to the Spring Framework's transaction infrastructure:
public void resolvePosition() {
try {
// some business logic...
} catch (NoProductInStockException ex) {
// trigger rollback programmatically
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}

Catch Exceptions inside a Message Driven Bean (MDB)

How must I handle exceptions inside a mdb? I have the funny feeling that the exception happens after the try catch block so I'm not able to catch and log it. Glassfish v3 decides to repeat the whole message. It runns into a infinite loop and writes lot's of logfiles on the harddrive.
I'm using Glassfishv3.01 + Eclipselink 2.0.1
public class SaveAdMessageDrivenBean implements MessageListener {
#PersistenceContext(unitName="QIS")
private EntityManager em;
#Resource
private MessageDrivenContext mdc;
public void onMessage(Message message) {
try {
if (message instanceof ObjectMessage) {
ObjectMessage obj = (ObjectMessage)message;
AnalyzerResult alyzres = (AnalyzerResult)obj.getObject();
save(alyzres);
}
} catch (Throwable e) {
mdc.setRollbackOnly();
log.log(Level.SEVERE, e);
}
}
#TransactionAttribute(TransactionAttributeType.REQUIRED)
private void save(AnalyzerResult alyzres) throws PrdItemNotFoundException {
Some s = em.find(Some.class, somepk);
s.setSomeField("newvalue");
// SQL Exception happens after leaving this method because of missing field for ex.
}
}
You got a bad case of message poisoning...
The main issues I see are that:
you are calling directly the save() method in your onMessage(): this means thet the container has no way to inject the proper transaction handling proxy around the save method
in any case the save() method should have #TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) in order to commit in a separate transaction, otherwise it will join the onMessage transaction (which default to REQUIRED) and bypass your exception handling code, beign committed after the successful execution of onMessage
What I woud do is:
Move the save method to a new Stateless session bean:
#Stateless
public class AnalyzerResultSaver
{
#PersistenceContext(unitName="QIS")
private EntityManager em;
#TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
private void save(AnalyzerResult alyzres) throws PrdItemNotFoundException {
Some s = em.find(Some.class, somepk);
s.setSomeField("newvalue");
// SQL Exception happens after leaving this method
}
}
Inject this bean in your MDB:
public class SaveAdMessageDrivenBean implements MessageListener {
#Inject
private AnalyzerResultSaver saver;
#Resource
private MessageDrivenContext mdc;
public void onMessage(Message message) {
try {
if (message instanceof ObjectMessage) {
ObjectMessage obj = (ObjectMessage)message;
AnalyzerResult alyzres = (AnalyzerResult)obj.getObject();
saver.save(alyzres);
}
} catch (Throwable e) {
mdc.setRollbackOnly();
log.log(Level.SEVERE, e);
}
}
}
Another tip: in this code the message poisoning still exists. Now it derives from the line invoking mdc.setRollbackOnly();.
I'd suggest here to log the exception and transfer the message to a poison queue, thus preventing the container to resubmit the message ad infinitum.
UPDATE:
A 'poison queue' or 'error queue' is simply a mean to guarantee that your (hopefully recoverable) discarded messages will not be completely lost. It is used heavily in integration scenarios, where the correctness of the message data is not guaranteed.
Setting up a poison queue implies defining a destination queue or topic and redeliver the 'bad' messages to this destination.
Periodically, an operator should inspect this queue (via a dedicated application) and either modify the messages and resubmit to the 'good' queue, or discard the message and ask for a resumbit.
I believe that the code that you have posted is mostly OK.
Your use of
#TransactionAttribute(TransactionAttributeType.REQUIRED)
is completely ignored because this (and most other) annotations can only be applied to business methods (including onMessage). That doesn't matter though because your onMessage method gets an implicit one for free.
This leads to the fact that message handling is transactional in a Java EE container. If the transaction fails for any reason the container is required to try and deliver the message again.
Now, your code is catching the exception from the save method, which is good. But then you're explicitly marking the transaction for rollback. This has the effect of telling the container that message delivery failed and that it should try again.
Therefore, if you remove:
mdc.setRollbackOnly();
the container will stop trying to redeliver the message.
If I'm not mistaken, you're letting the container handle the transactions. This way, the entity manager will queue the operations that will be flushed after the method finishes, that's why you're having exceptions after the method is finished.
Using em.flush() directly as a final step in the method will execute all the related queries of the transaction, throwing the exceptions there instead of being thrown later when the flush() is made by the container while commiting the transaction.

Categories

Resources