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).
Related
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
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.
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.
I am using JpaRepository from Spring Data JPA framework. I have a snippet of code below:
#Repository
public interface PresetFolderRepository extends JpaRepository<PresetFolder, Integer>{
#Modifying
#Transactional
#Query("update PresetFolder pf set pf.parentId = :parentId where pf.id = :id")
int updateParentId(#Param("id") int id, #Param("parentId") int parentId);
}
When I invoke this method:
#Autowired PresetFolderRepository repo;
repo.updateParentId(1,2);
public void test(){
Do I have to surround it with a try-catch? How can I know if the self-defined method 'updateParentId' has try-catch implementation in it?
Thanks!
EDIT:
My concern is, if my database went down, does this method catch the exception.
Repositories will always tell you something if a problem happens (i.e. they never swallow exceptions). You'll always get a runtime exception if that's the case.
And you should probably not catch such an exception either, except at the very top of the call stack, where you have the possibility to display an error message to the end user.
No you don't need it to surround by try-catch block. Most of the Spring-Data repositories throw runtime exceptions. With respect to your concern, if the database is down then a runtime exception is generated and you can catch it at the controller level(if you are writing a web application).
The test case would throw out an error for unreachable host, if you are executing the test case when the DB is down.
I'm having the strangest thing happening and I can't figure out why. The best way to describe this is to provide a simplistic example:
#Service
#Transactional
public class Foo{
public ModelAndView delete(#ModelAttribute("abc") Long id) {
ModelAndView mav = new ModelAndView();
try {
getDaoService().delete(id); //Calls Bar.delete()
} catch (final Exception e) {
// Add a custom error message to the mav for the user to see
mav.getModelMap().addAttribute(blah, blah);
}
return mav;
}
}
#Service
#Transactional
public class Bar {
public void delete(final E entity) throws HibernateException {
if (null != entity) {
try {
sessionFactory.getCurrentSession().delete(entity);
} finally {
sessionFactory.getCurrentSession().flush();
}
}
}
}
In this particular case, I am trying to delete an object which has a constraint violation (ORA-02292). I expect the delete to fail because of this. When the delete fails, I wish to show the user an appropriate custom message.
Instead of being able to show the user a custom message, the call fails and displays the following to the screen:
org.springframework.transaction.UnexpectedRollbackException: Transaction
rolled back because it has been marked as rollback-only
When I use a debugger, I can see that the error is appropriately caught and that the ModelAndView object has the custom message inside of it. So, I have no clue why an exception is still being thrown after it has been caught and dealt with. Does anyone have insight into why this is happening?
On the #Transactional annotation, you can state whether or not to roll back your transaction due to a given exception using the noRollbackForClassName attribute. You can do it similar to this.
#Service
#Transactional(noRollbackForClassName = "java.lang.Exception")
public class YourClass {
...
}
However, note that just saying noRollbackForClassName = "java.lang.Exception" would mean it will not rollback for any Exception (or its subclasses), hence its not a good practice.
What you should do is, figure out what exception is actually thrown first (may be by printing out the e.getClass().getName()), then set that class name as the noRollbackForClassName value.
Reason wise, this is happening because if some exception is thrown while attempting to delete(), the current transaction is automatically marked as roll back only, and if it is attempted to be committed, the exception you see will be thrown. The way to get passed this is to explicitly state that this certain exception should not cause a roll back.
The issue is because once an exception is thrown, Spring internally marks the tx as rollback-only. This is completely separate from Java exception handling. You have several options:
Make sure your expected exception does not throw exceptions which extend RuntimeException; Spring only rolls back tx's when its a type RuntimeException (see this page, section 10.5.3). HibernateException extends RuntimeException, so that's why you're getting the rollback marker.
Run each tx in its own transaction by moving the transactional method to its own class and annotating it using #Transactional(propagation=Propagation.REQUIRES_NEW). Then each call will run in its own tx and will not affect the overall tx.
Use the noRollbackForClassName style venushka mentioned. But use with caution, for the reason mentioned.
The Exception is being thrown in Bar#delete and is caught in Foo#delete. There is a #Transactional annotation on Bar#delete which is crossed before the exception is caught. This inner transaction is participating in the outer transaction and so the entire transaction is marked for rollback.
To avoid this you could remove the #Transactional annotation for Bar#delete. This method is already called within the scope of the other transaction.
Add property "globalRollbackOnParticipationFailure" to the hibernateTransactionManager bean definition as follows.
<bean id="hibernateTransactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="hibernateSessionFactory" />
**<property name="globalRollbackOnParticipationFailure" value="false" />**
</bean>