I am doing few hibernate save operations in spring's transactional service class.
My expectation is that by the time method execution finishes hibernate should write data to database.
But hibernate is actually executing those queries only when controller is about to return a response.
My sample code is as follows.
#Controller
public class TestController {
#Autowired
private SaveService saveService;
#RequestMapping(value = "saveData", method = RequestMethod.POST)
public String saveData(HttpServletRequest request, Model model) {
try {
saveService.saveData(object1, object2, object3); // LINE 1
sendEmail(); // LINE 2
//some code here // LINE 3
} catch(Exception e) {
//log exception
//return error message
}
}
}
#Service("saveService")
#Transactional
public class SaveServiceImpl implements SaveService {
#Autowired
private SaveDAO saveDAO;
public void saveData(Object objec1, Object objec2, Object objec3) {
saveDAO.save(object1);
saveDAO.save(object2);
saveDAO.save(object3);
}
}
In above code I am calling SaveService.saveData() method from controller. And if save is successful I want to go ahead and send an email. But if for some reason SaveService.saveData() throws an exception i don't want
to send the email.
When I performed some tests it was observed that even if SaveService.saveData() throws an exception it's not thrown
until the email is sent from controller. I want that if a call to saveService.saveData() at 'LINE 1' in controller
throws an exception the next line that sends email should not get executed.
I wanted to know if this is expected hibernate behavior and if it is what can I do to tell hibernate to execute
queries before exiting service methods.
Thank you.
This behavior is due to hibernate optimizations. Hibernate will wait until the last time possible to execute the sentences.
You can avoid this with session.flush(), Flushing the session forces Hibernate to synchronize the in-memory state of the Session with the database (i.e. to write changes to the database).
The problem here is when an exception occurs, your the variables/objects are not initialized in the catch block and you are accessing it.
As it looks like you have just added a snippet of the code in question, so I guess the variables object1, object2, object3 needs to initalized to null.
for example: object1=null
Related
The below is a service class
#Service
class Test {
public Object findEmployees1(String id, String dbId) {
return employeeRepo.findByDatabaseIdAndIdentifier(dbId, id);
}
public Object findEmployees2(String name, String dbId) {
Object records = employeeRepo.findByNameAndDatabaseId(name, dbId);
}
#ExceptionHandler(Exception.class)
public void internalErrorExceptionHandler(Exception ex) {
LOGGER.error("Log the exception here");
throw new InternalErrorException(ex.getMessage());
}
}
I want if any exception(eg some SQLException) comes in any of the method in the class test , it gets caught by the #ExceptionHandler and is logged there and re thrown
I cannot use #ExceptionHandler with #ControllerAdvice as I don't have any class with #Controller annotation on it . I am writing just a common module to get data from the database.
When I execute the code , the log under #ExceptionHandler is not printed as it never gets called.
The requirement is to log and rethrow a common exception if any exception comes in any of the service class methods without having individual try catch statements in each method.
As suggested, you can use spring aop to handle such exceptions. Instead of duplicating I will link to the existing question I have tried to answer here.
The other way is, if you are using ByteBuddy, you can use following annotation on the method which is expected to throw exception.
#Advice.OnMethodExit(onThrowable = RuntimeException.class)
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).
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.
In my Spring Boot project I have implemented following service method:
#Transactional
public boolean validateBoard(Board board) {
boolean result = false;
if (inProgress(board)) {
if (!canPlayWithCurrentBoard(board)) {
update(board, new Date(), Board.AFK);
throw new InvalidStateException(ErrorMessage.BOARD_TIMEOUT_REACHED);
}
if (!canSelectCards(board)) {
update(board, new Date(), Board.COMPLETED);
throw new InvalidStateException(ErrorMessage.ALL_BOARD_CARDS_ALREADY_SELECTED);
}
result = true;
}
return result;
}
Inside this method I use another service method which is called update:
#Transactional(propagation = Propagation.REQUIRES_NEW)
public Board update(Board board, Date finishedDate, Integer status) {
board.setStatus(status);
board.setFinishedDate(finishedDate);
return boardRepository.save(board);
}
I need to commit changes to database in update method independently of the owner transaction which is started in validateBoard method. Right now any changes is rolling back in case of any exception.
Even with #Transactional(propagation = Propagation.REQUIRES_NEW) it doesn't work.
How to correctly do this with Spring and allow nested transactions ?
This documentation covers your problem - https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/data-access.html#transaction-declarative-annotations
In proxy mode (which is the default), only external method calls coming in through the proxy are intercepted. This means that self-invocation, in effect, a method within the target object calling another method of the target object, will not lead to an actual transaction at runtime even if the invoked method is marked with #Transactional. Also, the proxy must be fully initialized to provide the expected behaviour so you should not rely on this feature in your initialization code, i.e. #PostConstruct.
However, there is an option to switch to AspectJ mode
Using "self" inject pattern you can resolve this issue.
sample code like below:
#Service #Transactional
public class YourService {
//... your member
#Autowired
private YourService self; //inject proxy as an instance member variable ;
#Transactional(propagation= Propagation.REQUIRES_NEW)
public void methodFoo() {
//...
}
public void methodBar() {
//call self.methodFoo() rather than this.methodFoo()
self.methodFoo();
}
}
The point is using "self" rather than "this".
The basic thumb rule in terms of nested Transactions is that they are completely dependent on the underlying database, i.e. support for Nested Transactions and their handling is database dependent and varies with it.
In some databases, changes made by the nested transaction are not seen by the 'host' transaction until the nested transaction is committed. This can be achieved using Transaction isolation in #Transactional (isolation = "")
You need to identify the place in your code from where an exception is thrown, i.e. from the parent method: "validateBoard" or from the child method: "update".
Your code snippet shows that you are explicitly throwing the exceptions.
YOU MUST KNOW::
In its default configuration, 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.
But #Transactional never rolls back a transaction for any checked exception.
Thus, Spring allows you to define
Exception for which transaction should be rollbacked
Exception for which transaction shouldn't be rollbacked
Try annotating your child method: update with #Transactional(no-rollback-for="ExceptionName") or your parent method.
Your transaction annotation in update method will not be regarded by Spring transaction infrastructure if called from some method of same class. For more understanding on how Spring transaction infrastructure works please refer to this.
Your problem is a method's call from another method inside the same proxy.It's self-invocation.
In your case, you can easily fix it without moving a method inside another service (why do you need to create another service just for moving some method from one service to another just for avoid self-invocation?), just to call the second method not directly from current class, but from spring container. In this case you call proxy second method with transaction not with self-invocatio.
This principle is useful for any proxy-object when you need self-invocation, not only a transactional proxy.
#Service
class SomeService ..... {
-->> #Autorired
-->> private ApplicationContext context;
-->> //or with implementing ApplicationContextAware
#Transactional(any propagation , it's not important in this case)
public boolean methodOne(SomeObject object) {
.......
-->> here you get a proxy from context and call a method from this proxy
-->>context.getBean(SomeService.class).
methodTwo(object);
......
}
#Transactional(any propagation , it's not important in this case)public boolean
methodTwo(SomeObject object) {
.......
}
}
when you do call context.getBean(SomeService.class).methodTwo(object); container returns proxy object and on this proxy you can call methodTwo(...) with transaction.
You could create a new service (CustomTransactionalService) that will run your code in a new transaction :
#Service
public class CustomTransactionalService {
#Transactional(propagation= Propagation.REQUIRES_NEW)
public <U> U runInNewTransaction(final Supplier<U> supplier) {
return supplier.get();
}
#Transactional(propagation= Propagation.REQUIRES_NEW)
public void runInNewTransaction(final Runnable runnable) {
runnable.run();
}
}
And then :
#Service
public class YourService {
#Autowired
private CustomTransactionalService customTransactionalService;
#Transactional
public boolean validateBoard(Board board) {
// ...
}
public Board update(Board board, Date finishedDate, Integer status) {
this.customTransactionalService.runInNewTransaction(() -> {
// ...
});
}
}