Spring #Transactional on suspend function - spring

I am kind of frustrated right now as I thought this will be much easier and the issue would be documented way better but I just can not find a solution. Therefore I seek help here.
I am working on a Kotlin project which leverages spring boot version 2.5.3 and uses spring data jpa for database access and schema definition. Pretty common and straight-forward. Now assume we have some kind of UserService which contains a method updateUsername which gets a username as a parameter und updates the username after it verified its validity by an external service. For demo purposes of the issue I want to highlight, before we verify the username we set the username manually to "foo". This whole unit of work should happend in a transaction which is why the method is annotated with #Transactional. But because of the call to the external service the method will suspend when we wait for the http response (note the suspend keyword on both methods).
#Service
class UserService(private val userRepository: UserRepository) {
#Transactional
suspend fun setUsername(id: UUID, username: String): Person {
logger.info { "Updating username..." }
val user = userRepository.findByIdentityId(identityId = id)
?: throw IllegalArgumentException("User does not exist!")
// we update the username here but the change will be overridden after the verification to the actual username!
user.userName = "foo"
verifyUsername(username)
user.userName = username
return userRepository.save(user)
}
private suspend fun verifyUsername(username: String) {
// assume we are doing some kind of network call here which will suspend while waiting got a response
logger.info { "Verifying..." }
delay(1000)
logger.info { "Finished verifying!" }
}
}
This compiles successfully and I can also execute the method and it starts a new transaction, but the transaction will be commited as soon as we suspend the invocation of the verifyUsername method on calling delay(1000). Therefore our database will acually hold the value "foo" as the username until it is overwritten. But if the code after verifyUsername would fail and throw an exception, we could not rollback this change as the transaction was already commited and foo would stay in the database forever!!! This is definitely not the expected behavior as we only want to commit the transaction at the very end of our method, so we can rollback the transaction at any time if something went wrong. Here you can see the logs:
DEBUG o.s.orm.jpa.JpaTransactionManager - Creating new transaction with name [x.x.x.UserService.setUsername]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
DEBUG o.s.orm.jpa.JpaTransactionManager - Opened new EntityManager [SessionImpl(1406394125<open>)] for JPA transaction
DEBUG o.s.orm.jpa.JpaTransactionManager - Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle#9ec4d42]
INFO x.x.x.UserService - Updating username...
DEBUG org.hibernate.SQL - select person0_.id as id1_6_, person0_.email as email2_6_, person0_.family_name as family_n3_6_, person0_.given_name as given_na4_6_, person0_.identity_id as identity5_6_, person0_.user_name as user_nam6_6_ from person person0_ where person0_.identity_id=?
INFO x.x.x.UserService - Verifying...
DEBUG o.s.orm.jpa.JpaTransactionManager - Initiating transaction commit
DEBUG o.s.orm.jpa.JpaTransactionManager - Committing JPA transaction on EntityManager [SessionImpl(1406394125<open>)]
DEBUG org.hibernate.SQL - update person set email=?, family_name=?, given_name=?, identity_id=?, user_name=? where id=?
DEBUG o.s.orm.jpa.JpaTransactionManager - Closing JPA EntityManager [SessionImpl(1406394125<open>)] after transaction
INFO x.x.x.UserService - Finished verifying
DEBUG o.s.orm.jpa.JpaTransactionManager - Creating new transaction with name [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
DEBUG o.s.orm.jpa.JpaTransactionManager - Opened new EntityManager [SessionImpl(319912425<open>)] for JPA transaction
DEBUG o.s.orm.jpa.JpaTransactionManager - Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle#1dd261da]
DEBUG org.hibernate.SQL - select person0_.id as id1_6_0_, person0_.email as email2_6_0_, person0_.family_name as family_n3_6_0_, person0_.given_name as given_na4_6_0_, person0_.identity_id as identity5_6_0_, person0_.user_name as user_nam6_6_0_ from person person0_ where person0_.id=?
DEBUG o.s.orm.jpa.JpaTransactionManager - Initiating transaction commit
DEBUG o.s.orm.jpa.JpaTransactionManager - Committing JPA transaction on EntityManager [SessionImpl(319912425<open>)]
DEBUG org.hibernate.SQL - update person set email=?, family_name=?, given_name=?, identity_id=?, user_name=? where id=?
DEBUG o.s.orm.jpa.JpaTransactionManager - Closing JPA EntityManager [SessionImpl(319912425<open>)] after transaction
In this spring article it says that "Transactions on Coroutines are supported via the programmatic variant of the Reactive transaction management provided as of Spring Framework 5.2. For suspending functions, a TransactionalOperator.executeAndAwait extension is provided."
Does that mean that #Transactional just cannot be used on suspending methods and you should programatically handle transaction management? Update: This thread states that #Transactional should work for suspend functions.
I am also aware, that all my database operations are running synchronously, and I could use r2dbc to make them asynchronously (as long as my database provides the driver implementing the specification), but I think that my question does not relate to how I communicate with the database but more about how spring handles suspending calls withing #Transactional annotated methods.
What are your thoughts and recommendations here? I am definitely not the first dev who does suspending work in a transactional method in Kotlin, and still I am not able to find helpful resources on this issue.
Thanks guys!

JPA isn't supported with Coroutine Transactions as JPA is fully synchronous. Coroutine Transaction propagation works only with technologies that provide a reactive integration such as MongoDB, R2DBC, or Neo4j.
JPA assumes an imperative programming model hence its transaction manager stores transactional state in ThreadLocal storage. Reactive integrations and specifically Coroutines employ Coroutines context/Reactor's subscription context to keep track of the transaction status. There's no link between ThreadLocal and the context feature of Coroutines/Project Reactor.
Going forward, using blocking integrations such as JPA require special attention in the context of coroutines and their transactional scope needs to be constrained onto a single thread.

Related

Deep understanding of Spring boot with HikariCP

I have a spring boot app which uses spring data and hikaricp for db connection pooling. I noticed the following behaviour that looks strange to me:
I have one method which is not transactional and in that method several db queries are executed using spring data repositories
public void testMethod(final Long firstRepositoryId, final Long secondRepositoryId) {
final DomainObject result = firstRepository.findById(firstRepositoryId);
// here there's some code that is processing the result without db queries
secondRepository.findById(secondRepositoryId);
// some more logic here without additional db queries
}
So as expected when there's no transaction on the method then the spring data methods opens a transaction for executing the query and complete it after the methods returns. I have enabled transaction logging so there's the following log output:
2021-06-03 15:34:30.961 TRACE c681f76a-5d7e-41d5-9e50-fb6f96169681 --- [tp659271212-291] o.s.t.i.TransactionInterceptor : Getting transaction for [com.test.FirstRepository.findById]
2021-06-03 15:34:30.966 TRACE c681f76a-5d7e-41d5-9e50-fb6f96169681 --- [tp659271212-291] o.s.t.i.TransactionInterceptor : Completing transaction for [com.test.FirstRepository.findById]
2021-06-03 15:34:30.967 TRACE c681f76a-5d7e-41d5-9e50-fb6f96169681 --- [tp659271212-291] o.s.t.i.TransactionInterceptor : Getting transaction for [com.test.SecondRepository.findById]
2021-06-03 15:34:30.972 TRACE c681f76a-5d7e-41d5-9e50-fb6f96169681 --- [tp659271212-291] o.s.t.i.TransactionInterceptor : Completing transaction for [com.test.SecondRepository.findById]
Everything seems to be exactly how I expects to be. The thing I can't understand is the hikari behaviour. This method is invoked within a http request. A connection is taken from hikari cp right after the execution of the firstRepository.findById but this connection is returned in the pool only after the http controller returns response. What I expect is that a connection is taken after a transaction is opened and returned back after the transaction is completed. Is there something that I miss or maybe I have some wrong configuration?
P.S. I'm monitoring the active hikari connections through the spring boot actuator prometheus data. And to be able to reproduce the behavior I explained above I'm suspending the connection thread with several debug breakpoints.
I found out what causes this behaviour- it's Spring functionality to maintain the hibernate session in view in order to be able to retrieve lazy loaded data in the view. In order to disable this you need the following property:
spring.jpa.open-in-view=false
Here's another SO post where is explained what it does:
What is this spring.jpa.open-in-view=true property in Spring Boot?

JPA transactional proxy within a thread issue

In my Controller I have injected (#Autowired) this Service, which implements Runnable (I need multi-threading) and I call it like so:
Thread t = new Thread(service);
t.start();
t.join();
Then, in my Service's run() I call this Repository (simple JPARepository), which is injected in Service also with #Autowired:
repository.save(someEntity);
The problem is that it doesn't persist the entity with id=1. The transactional proxy (and Hibernate connection pool) is initialized after the unsuccessful saving of the first entity. After that, it works fine.
Can anyone point me to the right direction with my issue: how to force the Thread to initialize the Hibernate transactional proxy before persisting the first entity?
You should consider to start the thread after Spring context is refreshed. This is safer because all your beans may be in an inconsistent state.
#EventListener(ContextRefreshedEvent.class)
public void handleContextStart() {
// ...
}

What precisely means setSessionTransacted in JMSTemplate?

Please explain me if I understood correctly Spring documentation.
Spring docs states: https://docs.spring.io/spring/docs/current/spring-framework-reference/integration.html#jms-tx
(...)When you use the JmsTemplate in an unmanaged environment, you can specify these values (transaction and acknowledgment modes) through the use of the properties sessionTransacted and sessionAcknowledgeMode.
When you use a PlatformTransactionManager with JmsTemplate, the template is always given a transactional JMS Session.(..)
(BTW, that is true - session is transactional)
Javadoc states : https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/jms/core/JmsTemplate.html
Default settings for JMS Sessions are "not transacted" and "auto-acknowledge". As defined by the Java EE specification, the transaction and acknowledgement parameters are ignored when a JMS Session is created inside an active transaction, no matter if a JTA transaction or a Spring-managed transaction.
I understood that if transaction is active, JMS Template session transaction settings are ignored - that is true - and the session should participate active transaction - that is not true.
I debug why it is not true and I found: https://github.com/spring-projects/spring-framework/blame/master/spring-jms/src/main/java/org/springframework/jms/connection/ConnectionFactoryUtils.java#L353
if (resourceHolderToUse != resourceHolder) {
TransactionSynchronizationManager.registerSynchronization(
new JmsResourceSynchronization(resourceHolderToUse, connectionFactory,
resourceFactory.isSynchedLocalTransactionAllowed()));
resourceHolderToUse.setSynchronizedWithTransaction(true);
TransactionSynchronizationManager.bindResource(connectionFactory, resourceHolderToUse);
}
The line resourceHolderToUse.setSynchronizedWithTransaction(true) is align the documentation.
The issue here: resourceFactory.isSynchedLocalTransactionAllowed()
Because resourceFactory is org.springframework.jms.core.JmsTemplate.JmsTemplateResourceFactory#isSynchedLocalTransactionAllowed which points to JmsTemplate#sessionTransacted.
Conclusion:
According to documentation, if transaction is active, JmsTemplate#sessionTransacted should be ignored. But it is not true - although session is transactional, cannot not participate in commit.
JmsTemplate#sessionTransacted is finally mapped to ConnectionFactoryUtils.JmsResourceSynchronization#transacted and default=false prevents commit being called at the end of transaction (JmsResourceSynchronization "thinks" that it does not participate transaction)
Do I understand documentation right and there is really bug here?
Guided by M. Deinum, I made more experiments and it seems I wrongly understood term Spring-managed transaction
I simply thought that Spring managed transaction is started by platformTransactionManager. But:
If platformTransactionManager is JtaTransactionManager and transaction is started, it IS Spring managed transaction; JMS template attribute sessionTransacted is ignored and JMS template is part of transaction
if platformTransactionManager is DataSourceTransactionManager or JpaTransactionManager then
if sessionTransacted is false, JMS template is not in transaction
if sessionTransacted is true, JMS template is synchronized with transaction: after callback/rollback on JDBC/JPA transaction correspondent commit/rollback is called on JMS transaction

Is Entity Manager cleared automatically after each request?

Spring is providing one Entity Manager per thread. But I can`t find info if Spring clears Entity Manager after #RestControllers method is finished executing? So for example, if I have a method similar to this
#GetMapping("/{id}")
public ResponseEntity<SomeEntity> someRequest() {
SomeEntity res = someService.doSomeJpaRelatedWork();
return new ResponseEntity<>(res), HttpStatus.OK);
}
Will spring call EntityManager.clear() after the request or will Entity Manager keep entities for further requests on that thread?
Since your method doesn't use an EntityManager nor has it #Transactional annotation it is completely independent of the EntityManager and will on its own not affect any EntityManager.
Also, I doubt anything is Spring will call clear implicitly.
BUT Spring doesn't use one EntityManager per Thread but one per request.
So the next request in your web application will get a fresh EntityManager with a clear 1st level cache. So while the correct answer for the question you asked is "No, clear isn't called", the answer which is probably relevant is "Yes, the EntityManager is clear on each call of your controller method."

How to track JTA transactions

We have JTA transactions(Atomikos) configured using Spring annotations across different places in our application. I need to get trace logs whenever a transaction started and completed.
For example, whenever the below method invoked within a new transaction,
#Transactional
void createAgent() { ... }
I need to log a message saying
Transaction started on AgentFactory::createAgent() ...
Transaction ended on AgentFactory::createAgent() ...
Can you please provide if there is any way to enable trace logging on transactions?
If you set loglevel to DEBUG or TRACE for org.springframework.transaction
you get the log entries you want. May be not in the exact format, but the information is provided.

Resources