Open Aviss opened 2 years ago
Came across the same issue on my side, while figuring out why entities are not persisted when there was no logical error with my code... @Aviss I'm extremly grateful that you wrote this bug report, as it's quite hard to figure out what's wrong when you expect the container to behave correctly!
/cc @Sanne, @gsmet
I remember seeing another issue about this behavior (which, I agree, is very strange). IIRC the conclusion was that, as per the spec, a transaction must be rolled back as soon as any method annotated with @Transactional
throws an exception. Here your method findOptionalValue
is annotated with @Transactional
(at the class level), so as soon as it throws an exception, the ongoing transaction is rolled back.
Of course I agree it would make more sense for the transaction to be rolled back when the method whose transaction interceptor started the transaction throws an exception, ignoring all "internal" caught exceptions. But if the spec says otherwise, who am I to contradict it...
I'll try to find the other issue and the part of the spec that mandates this nonsensical behavior.
I couldn't find the other issue I was talking about, but I had a look at the spec. While it's not extra clear, there are places where it seems to imply that the @Transactional
interceptor should only roll back transactions it created itself, such as here (emphasis mine):
For example if a transaction is begun as a result of a call to a bean annotated with Transactional(TxType.REQUIRES) and this bean in turn calls a second bean annotated with Transactional(TxType.NEVER), the transaction begun by the first bean will be marked for rollback
So... I would say the current behavior of the @Transactional
interceptor in Quarkus, where it seems to roll back transactions it did not even start, seems incorrect.
I.e. I would argue that here, handleException
should not lead to a rollback:
@gsmet @stuartwdouglas @Sanne do you agree? Can I start working on a fix (and tests)?
I know this is not the same spec, but the original @TransactionAttribute
from the EJB spec would roll back the callers transaction: https://jakarta.ee/specifications/enterprise-beans/4.0/jakarta-enterprise-beans-spec-core-4.0.html#a3008 table 7 row 1.
I would argue that the Jakarta Transaction spec also expects the same behavior, in 3.7 it talks about how the Transaction interceptor handles exceptions and when it rolls back, but this is not qualified in any way, i.e. the behavior should be the same no matter the transaction attribute or who started the transaction.
Note that this largely mirrors the behavior of the EJB spec that this was based on, so I don't think this is an oversight.
The passage @yrodiere quoted is not really relevant, the call never enters the business method of the NEVER bean, it is just saying that if you do cause the TX interceptor of an inner bean to throw an exception then the TX will be rolled back as normal.
Note that I am not arguing that this behavior is user friendly, I remember hating this semantic as an end user over a decade ago, but it is what the spec expects.
Actually the big difference here compared to the EJB spec is that we are doing self-interception, while EJBs did not.
This makes things even more confusing. If you make findValue private it would also fix the issue.
@mkouba just FYI self-interception + the transaction interceptor can produce some very unexpected results.
The passage @yrodiere quoted is not really relevant, the call never enters the business method of the NEVER bean, it is just saying that if you do cause the TX interceptor of an inner bean to throw an exception then the TX will be rolled back as normal.
I'm aware of that, but as you said the spec does not say which transactions should be rolled back and which should not, while this passage seems to go out of its way to mention the transaction being rolled back is the one that was started by the same interceptor. That's really the only hint I could find in the Transaction spec, as thin as it is.
Note that I am not arguing that this behavior is user friendly, I remember hating this semantic as an end user over a decade ago
I suspect a lot of people will share that feeling. I can't think of a scenario where I would want that behavior. It basically forces you to fiddle with @Transactional.rollbackOn
/@Transactional.dontRollbackOn
everytime your clients can possibly catch a runtime exception, effectively forcing you to work as if using checked exceptions. Dreadful.
Taking into account self-interception, which worsens the issue... maybe deviating from the spec might make sense here? Or rather, deviating from what the spec doesn't say explicitly :)
@mkouba just FYI self-interception + the transaction interceptor can produce some very unexpected results.
ACK.
Note that self-interception/self-invocation is something completely undefined in CDI.
Hi, not sure if this is relevant for discussion about what specs say, but javax.persistence.PersistenceException clearly states which subclasses should not cause rollback. https://docs.oracle.com/javaee/7/api/javax/persistence/PersistenceException.html In case an interceptor is handling the thrown exceptions, I think it's also it's obligation to follow the linked documentation. I just don't see how CDI/EJB specs could/should change the documentated behaviour, unless they explicity state so.
@atastrumf I think that specific bit of specification describes whether the JPA implementor (here, Hibernate ORM) triggers a rollback when it throws an exception. Code catching that exception later (like the transaction interceptor) is not bound by that spec. So I don't think this is relevant.
That said, I still think the current behavior is impractical at best and should change. I don't seem to be in the majority, though, so... :shrug:
Describe the bug
A caught
NoResultException
within a transaction causes changes made during the transaction to not be persisted. Interestingly, no Exception is thrown, the transaction is not marked for rollback and even with hibernate logging set to TRACE there is no indication as to why this happens. The step running theinsert
query is simply missing.Even weirder, inlining the function producing the
NoResultException
within the same class resolves the issue. I attached a reproducer project below but for completion:produces the behaviour, while
does not, when
findOptionalValue
is called from another method/class annotated@Transactional
If I need to take this bug up with hibernate instead, feel free to refer me with the necessary version information.
Expected behavior
Catching a
NoResultException
does not change the outcome of a transaction in any way. A transaction ending in a unexpected way should produce an exception or a warning.Actual behavior
The Entity created during the Transaction is not persisted, no error is thrown, no warning is produced.
How to Reproduce?
Reproducer: quarkus-reproducible.zip
Steps to reproduce:
mvnw clean install -DskipTests quarkus:dev
localhost:8080/q/swagger-ui
POST /some-entity
GET /some-entity
Or:
mvnw clean install -DskipTests=true
SomeEndpointIT.testCreate()
Output of
uname -a
orver
No response
Output of
java -version
openjdk version "11.0.2" 2019-01-15 OpenJDK Runtime Environment 18.9 (build 11.0.2+9) OpenJDK 64-Bit Server VM 18.9 (build 11.0.2+9, mixed mode)
GraalVM version (if different from Java)
No response
Quarkus version or git rev
2.6.2-Final
Build tool (ie. output of
mvnw --version
orgradlew --version
)Apache Maven 3.8.1 (05c21c65bdfed0f71a2f2ada8b84da59348c4c5d) Maven home: C:\Users___.m2\wrapper\dists\apache-maven-3.8.1-bin\2l5mhf2pq2clrde7f7qp1rdt5m\apache-maven-3.8.1 Java version: 11.0.2, vendor: Oracle Corporation, runtime: D:\Build\Java\jdk-11.0.2 Default locale: de_DE, platform encoding: Cp1252 OS name: "windows 10", version: "10.0", arch: "amd64", family: "windows"
Additional information
Here are the relevant Serverlogs for calling the function as outlined above: