You can imagine I have some service, say it will be money service. Also assume I have one method, that perform actual transfer (Quite mundane example, I know). And I have to return true if transaction ended up successfully, and false, if it is not. So, here is the think that I do not actually grasp - how do I track the result of transaction in Spring Framework? (May be even for just simple logging purposes) Example of my transfer method is present below. Appreciate any help.
#Transactional
public boolean transferMoneyFromOneAccountToAnother(MoneyTransferForm moneyTransferForm) {
final UserBankAccount sourceBankAccount = bankAccountRepository.findBankAccountByIdentifier(
moneyTransferForm.getSourceAccountIdentifier()
);
final UserBankAccount targetBankAccount = bankAccountRepository.findBankAccountByIdentifier(
moneyTransferForm.getTargetAccountIdentifier()
);
subtractMoneyFromSourceAccount(moneyTransferForm, sourceBankAccount);
appendMoneyToTargetAccount(moneyTransferForm, targetBankAccount);
bankAccountRepository.updateUserBankAccount(sourceBankAccount);
bankAccountRepository.updateUserBankAccount(targetBankAccount);
}
I can think of two ways to do it:
You can simply enclose your method call with try/catch block and if there are no exception then your transaction was committed successfully.
try{
transferMoneyFromOneAccountToAnother()
logger.info("Transacton Done Successfully");
}catch(Exception ex){
//transaction failed
logger.error("Transaction failed")
}
You can have a method which is annotated with #TransactionalEventListener and listening to your custom event. You can check these links for more understanding of how it works:
https://www.baeldung.com/spring-events
#TransactionalEventListener annotated method not invoked in #Transactional test
Related
I have a case where I need to initiate calls to four different services in the same method. According to our business logic all four steps must succeed, rollback otherwise. The problem is that since all the steps occur in the same transaction (via #Transactional annotation) the second call in the method cant process the result of the first one. I have tried Propagation.REQUIRES_NEW on the first call but since it starts a new transaction the changes it makes are committed and not rollbacked whether the other calls succeed or not. At some point it seemed like Propagation.NESTED could do the work but I get a NestedTransactionNotSupportedException which warns me of setting the nestedTransactionAllowed property to true. But I could not found any document about how to do that. Any ideas? And also would appreciate any other suggestion for the problem. Thanks.
Root method:
#Override
#Transactional
public GuestUserDto createGuestUser(GuestUserRegisterRequest existingGuestUser) {
guestUserDeactivationDelegateService.deactivateGuestUser(existingGuestUser);
UserDto userDto = guestUserRegistrationDelegateService.registerGuestUser(
guestUserRegisterDtoConverter.convert(existingGuestUser));
createGuestUserAddress(existingGuestUser);
guestUserRegisterHistoryService.createRegisterHistory(
guestUserRegistrationHistoryDtoBuilder.build(userDto.getMemberId(), existingGuestUser));
return guestUserDtoConverter.convert(userDto);
}
Deactivation method:
#Transactional
public void deactivateGuestUser(GuestUserRegisterRequest existingUser) {
deactivateSmsVerification(existingUser.getMemberId());
String emailPlaceholder = existingUser.getMemberId() + existingUser.getEmail();
userGraphClientService.updateUserStatus(createUserRequest(existingUser.getMemberId()));
updateGuestUserStatus(existingUser.getMemberId(), emailPlaceholder);
}
Seems that the main cause was Hibernate's flush operations order, as it was giving priority to insert over update. Flushing manually after updating in DAO layer solved the problem.
NestedTransactionAllowed can be set in HibernateTransactionManager as follows.
HibernateTransactionManager manager = new HibernateTransactionManager(sessionFactory);
manager.setNestedTransactionAllowed(true);
return manager;
Please refer to https://www.demo2s.com/java/spring-hibernatetransactionmanager-setnestedtransactionallowed-boolean.html
I have a simple listener with 3 methods. and a repository with autowired on that. While saving an object from afterWrite it works nicely. but when saving item from onError methods no exception occurs, however it is not saving any data. Thankful for suggestions.
public class WriteListener implements ItemWriteListener{
public void beforeWrite(List items) {
System.out.println("Going to write following items: "+ items.toString());
}
public void onWriteError(Exception exception, List items) {
System.out.println("Error occurred when writing items!");
testRepository.save(items.get(0)); //not working
}
public void afterWrite(List items) {
testRepository.save(items.get(0)); //not nicely and save data
Based on the limited information provided, most likely the cause is the exception itself. The exception would have marked current transaction as dirty thus spring would have rolled it back.
If you still want to store data in your listener despite existing exception, use it in a separate transaction context. Simplest way for that would be to use #Async annotation on your listener and marking it Transactional explicitly to ensure it initiate a new transaction. Check out Spring Event which covers this topic in little bit more depth.
I'm little confused. I though #Transactional on method means all operations or none.
Say I have this method:
#Transactional
public void fewDbOpeations(){
calculation1();
myDao.saveResult();
calculation2();
myDao.saveResult();
}
Say calculation2() throw exception or my second call to myDao.saveResult goes wrong , what I see is the even though the whole method annotated with #Transactional the saving result after calculation1() call is successful.
That is my first interaction with database saved the records I want but the second one failed but I thought because the method is #Transactinal even the first call to save to database should be rolled back.
Do I miss something?
#Transactional (rollbackFor = Exception.class)
public void fewDbOpeations(){
calculation1();
myDao.saveResult();
calculation2();
myDao.saveResult();
}
Try using this as well and throw Exceptions.
It depends on how you handle exceptions and if there are still #Transactional annotated on those internal method calls.
To have "all or nothing" behaviour in fewDbOpeations(), make sure the followings for all the internal method calls :
Do not annotated with #Transactional(propagation=REQUIRES_NEW)
Do not catch the exception inside and not throw out. Throw RuntimeException or Error but not checked Exception (Assume you are using default setting).
My question is regarding spring-retry.
Assume a simple sample code where I have a the Service layer and Controller class.,
This is the testService Interface
public interface testService{
#Retryable(value = { KnownExceptiomn.class }, backoff = #Backoff(delay = 1000), maxAttempts = 2)
Address getAddress(String emailAddress);
}
This is the implementation of the service
public class testServiceImpl{
public Address getAddress(String emailAddress){
//addressRepository is a crud repository
return addressRepository.getAddressFromEmail(emailAddress);
}
}
And the controller is
#GetMapping("path/{emailId}")
public ResponseEntity<?> getAddress(#PathVariable("emailId") final String email){
final Address address;
try{
address= testService.getAddress(String emailAddress);
if(address != null) return new ResponseEntity<Address>(address,HttpStatus.OK);
return new ResponseEntity<String>("Invalid Email", HttpStatus.BAD_REQUEST);
}catch(KnownException e){
return errorMessage("Error");
}
}
As seen the #Retryble is in the Service interface. However I have not implemented an #Recover method. My thought here was since i dont really have any secondary DB and If the DB is down there really isn't a recovery option I did not add a #Recovery method. Instead the exception was caught in the controller and handled.
My questions are:
Is the above approach wrong. If so, how to do it the right way?
Is it always necessary to have a recovery method? If so what would be recovery in these kind of scenarios like DB being down and no alternative source of Data.
Is it wrong to catch exceptions in controller and handle
them accordingly? (I was told the service lay should handle all the
exceptions in some discussions).
Everywhere I've see is some sort of recovery method but couldn't find a solid example with proper recovery handler for this type of scenario if there is.
#Recovery is optional; if there is none, after retries are exhausted, the last exception is thrown to the caller, who has to handle the exception.
It's perfectly normal to not have a #Recovery and handle the exception in the caller, by whatever means you like.
#Vipin Menon: I think you are referring to #Recover annotation, so the answer is No. #Recover annotation is provided just to give the programmer flexibility to create a recovery method to return a default (fallback) response if retry attempts also fails.
In a nutshell, don't create a recovery method using #Recover annotation if you don't need any default behavior when retry attempts fail. Simply use #Retryable annotation on the actual service method.
In the service layer, I have some method who have a transactional annotation.
#Transactional
public void process() throws ProcessPaymentException{
try{
.... do some operation
catch (ProcessPaymentException ppe) {
save db problem issue.
}
}
It seem like if there are a issue, there are roll back... and nothing is saved in the db...
ProcessPaymentException extend Exception
Is there a way to rollback the process in the try but do the save in the catch?
Edit
Nested transaction could be a solution if this link is ok
https://www.credera.com/blog/technology-insights/java/common-oversights-utilizing-nested-transactions-spring/
Existing answer of using ControllerAdvise should help in normal setup that incoming requests are coming through Spring MVC (i.e. through a Controller).
For cases that is not, or you do not want to tie your exception handling logic to Spring MVC, here are some alternatives I can think of
(Here I assume you want to rely on declarative transaction control instead of programmatically controlling transactions yourself)
Separate service/component to save error in different transaction.
In short, you can have a separate service, which create its own transaction by propagation REQUIRES_NEW. e.g.
#Service
public class FooService
#Inject
private ErrorAuditService errorAuditService;
#Transactional
public void process() throws ProcessPaymentException{
try{
.... do some operation
catch (ProcessPaymentException ppe) {
errorAuditService.saveErrorAudit(ppe.getErrorText());
throw ppe; // I guess you want to re-throw the exception
}
}
}
#Service
public class ErrorAuditService
#Transactional(propagation=REQUIRES_NEW)
public void saveErrorAudit() {
// save to DB
}
}
One step further, if the error handling it the same for different services, you may create an advise, which will be called when service method throws exception. In that advise, you can save the error in db (using ErrorAuditService), and rethrow the exception.
Because processes of try-catch are wrapped by the same transaction.
The transaction manager do rollback whenever an exception is thrown. So, not thing would be saved.
Is there a way to rollback the process in the try but do the save in the catch?
Yes. Create Exception Handler to save db problem issue after rollback.
this is the idea
#ControllerAdvice
public class HandlerName {
#ExceptionHandler(ProcessPaymentException.class)
public void saveDbIssue(ProcessPaymentException ex) {
// save db problem issue.
}
But it only works if u want to save static data.