Why spring transactional executes code after repository save exception? - spring

Using the annotation #Transactional spring executes the function past the repository save error to the following lines that shouldn't have been executed!!
Example: Using #Transactional on method
iEmployeeRepository.save(employee); <<-- Error occurs here
System.out.println("Hello"); <<-- This line still gets executed
I know #Transactional behaviour doesn't need a save, it automatically updates the ORM entity, according to this behaviour it looks like its running all the code and executes save repository by itself after all function code has run.
How can make sure #Transactional stop executing code when an error occurs when save repository is called?

save doesn't throw an exception. Due to JPAs write cache SQL statements are moved back as far as possible, which often mean to the end of the transaction, at which time all the collected changes will be flushed to the database. Any exceptions caused by that interaction will be raised at that time only.
This is JPA behaviour and independent of Spring.
You can avoid this by using JpaRepository.saveAndFlush(..)
This will force a flush right after the save and thereby trigger all errors early.

Related

What can cause a LazyInitatializationException whereas Spring Open In View is enabled?

I am analysing a "classic" Hibernate error :
org.hibernate.LazyInitializationException : could not initialize proxy – no Session.
I am wondering how it could happen whereas the Spring Open In View mode is enabled?
If you have any documentation or knowledge on a possible reason, please share.
Thanks
Here are some feebacks on my context, and an example of what could cause LazyInitializationException event if Spring Open In View is enabled.
Context
My application is a REST API server developed with Spring Boot 2.5.3.
Spring Open In View is kept enabled by default.
A lot of Services are annotated with #Transactional methods.
Spring-data is used, and Entitymanager too, to create some native queries.
The error
One REST API request, that generates a lot requests to the database (both selections and insertions), fails with LazyInitializationException.
Debugging
By setting a breakpoint when LazyInitializationException is thrown, I discovered that the exception is thrown in org.hibernate.proxy.AbstractLazyInitializer#initialize, because the session is null. Then I discovered that the session is set to null, when the method EntityManager.clear is called. For any reason I don't know, this method was explicitely called in the code.
Fixing
So I just removed the call to EntityManager.clear, and the request works. I'm still wondering why previous developers wanted to clear the EntityManager, probably because they were confused with the transaction management.
Conclusion
Even in Spring Open In View is enabled, and as a result, event if a Hibernate Session is opened, calling EntityManager.clear unset the session to the entities loaded before. Then trying to access a Lazy Loaded field on those entities throws LazyInitializationException.
I hope this will help someone.
Here is the documentation from the latest hibernate version.
As you can see
Indicates an attempt to access not-yet-fetched data outside of a
session context. For example, when an uninitialized proxy or
collection is accessed after the session was closed.
Most probably somewhere you read some entity and then outside of a transaction you try to read some collection which was by default lazy loaded. So then another request needs to be done to database, but since you are out of transaction you receive this exception.
This usually occurs in case you load an entity without the annotation #Transactional or some other configuration to load it inside a transaction and then you give this to your controller to be returned to the user. Then the controller will use jackson or some other library to convert the entity object into a json and will try to read any field. At this point trying to read any lazy loaded collections from the entity, will result in this exception since the read will be outside of a transaction.

Spring Transaction - Do not rollback for error under specific method

I have a Spring Boot application in which a service is responsible to create a Business Entity. For simplicity, let's consider:
create(Object toCreate) {
validationService.validate(toCreate);
Object created = repository.save(toCreate);
notificationService.notify(created);
}
Business has changed and now I would like the creation to not fail if notification fails.
I therefore wrapped the notify() method in a try-catch block that only logs the error (which is a RuntimeException).
However when tested, a transaction rollback error was thrown, saying the connection was closed. I do not want any rollback, especially since the NotificationService does not modify the database.
How can I tell Spring that any exception happening in the NotificationService is fine and does not require a rollback? I tried annotating the class/method with #Transactional(propagation=NEVER) but got existing transaction found for transaction marked with propagation 'never'
Perhaps refactoring your code would help better than the introduction of more complex transaction handling.
Assuming your #Transactional is on the create() method, we can see that you have:
Pre-persistence business logic
Persistence logic
Post-persistence business logic
It depends on your use-case, but I would not expect pts. 1 & 3 to contain persistence logic. If that is the case, you could extract your persistence logic in its own service that would itself be a transaction, not the parent.
If you have the need for a transaction in those steps, you could also extract them in their own transaction.
Other leads might be:
The default rollback behavior is for runtime unchecked exceptions. You could use checked exceptions to avoid the rollback.
If your notification is separate from persistence and you do not care about the transaction, you could make it an asynchronous call (e.g. with #Async). It would be scheduled outside of the transaction in its own context.
You can use the option norollbackfor of #Transactional so you have to specify an exception class not the service and when an error occurs in notifications try to throw a specifc error which would not cause a rollback.

spring #transactional noRollbackFor unexpectedly rollsback...any clue?

I have following problem :
Servers runs on WL 12.
The main code is in a EAR, in methods with #Transactional explicitly written as NoRollbackFor=RuntimeException.class
When the exception occurs inside the EAR code, the noRollbackFor is correctly taken into account.
However, when the error occurs in a remote call to another WAR, the transaction is flagged as setRollbackOnly, and subsequent calls to the dabatase (read operations) fail (even if the exception is trapped in the calling code).
Any idea as to why this happens / how to avoid this ?
Thanks !
Make sure that your exception is of type RuntimeException, which triggers a rollback, but checked exception does not

Commiting transaction within test with Spring test

I am testing my repository for update operation
#Test
public void updateStatusByEmailWithEmailCustomer()
{
customerQuickRegisterRepository.save(standardEmailCustomer());
assertEquals(CUST_STATUS_EMAIL,customerQuickRegisterRepository.findByEmail(CUST_EMAIL).getStatus());
customerQuickRegisterRepository.updateStatusByEmail(CUST_EMAIL,STATUS_EMAIL_VERFIED );
assertEquals(STATUS_EMAIL_VERFIED,customerQuickRegisterRepository.findByEmail(CUST_EMAIL).getStatus());
}
In the test case i saving my entity with default status and after that i am changing it to other using updateStatusByEmail(CUST_EMAIL,STATUS_EMAIL_VERFIED ) but still next assert statement is failing, this is due to fact that the updates during the test execution are commited after completion of test....Is there any way that I can commit my changes within the test?
You can likely get by with flushing the underlying Hibernate Session, as this will push your changes to the underlying tables in the database (within the current test-managed transaction).
Search for "false positives" in the testing chapter of the Spring reference manual for details.
Basically, you will want to call flush() on the current Hibernate Session after your update call and before the corresponding assertion.
If that does not solve your problem, with Spring Framework 4.1, you can use the new TestTransaction API for programmatic transaction management within tests.
Regards,
Sam (lead of the spring-test module)

Why am I getting a SpringFramework UnexpectedRollbackException?

I am getting the following Spring Framework error message:
Invocation of getLogoForGlobalConext() in class $Proxy44 threw exception
org.springframework.transaction.UnexpectedRollbackException:
Transaction rolled back because it has been marked as rollback-only
at template/includes/macros.vm line 1651, column 43
I opened macros.vm and looked up line 1651 and it looks like this:
#set ($globalLogo = $spaceManager.getLogoForGlobalContext());
From my research it looks like $Proxy44 is actually the $spaceManager variable (or an instance of DefaultSpaceManager.java).
This message appears randomly and when the web app tries to download an image/attachment sitting somewhere on the web server / database.
The attachmentmanager is governed by Spring's Transaction Management and the following transaction attributes are used when an image/attachment is downloaded:
propagation - for all methods in the attachment manager
propagation and read-only - for all methods in attachment manager starting with "get".
The attributes are defined in Spring Framework - Chapter 9. Transaction management.
What I am thinking is I need to set a timeout on the transactions (like set it to infinity).
It turns out one of the getter methods was performing a write to database. Specifically, it was updating the cache with some information every few minutes. When this update occurred, the UnexpectedRollbackException was thrown. Since this transaction is supposed to be "read only" as defined by the transaction attributes mentioned above, we are not allowed to perform updates during a getter operation.
I changed the getter method to not perform any updates to cache and to simply use the cache even if it expired, and the error goes away.
Hope this helps someone else.

Resources