Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

@Transactional should treat UndeclaredThrowableException as a checked exception #26854

Closed
Sam-Kruglov opened this issue Apr 23, 2021 · 6 comments
Assignees
Labels
in: data Issues in data modules (jdbc, orm, oxm, tx) status: declined A suggestion or change that we don't feel we should currently apply

Comments

@Sam-Kruglov
Copy link
Contributor

Sam-Kruglov commented Apr 23, 2021

I read this blogpost describing this problem:

@SneakyThrows
@Transactional
public void lombokSurprise() {
    jdbcTemplate.execute("insert into test_table values('lombok!')");
    throw new Exception("Simple exception");
}

This code is supposed to commit the transaction but it does not. The problem is that Spring wraps checked exceptions that aren't declared in the signature within an UndeclaredThrowableException over here and from that point treats it as unchecked.

I think we should treat this one as a checked exception to preserve the promised behavior. I believe it's controlled here:

/**
* The default behavior is as with EJB: rollback on unchecked exception
* ({@link RuntimeException}), assuming an unexpected outcome outside of any
* business rules. Additionally, we also attempt to rollback on {@link Error} which
* is clearly an unexpected outcome as well. By contrast, a checked exception is
* considered a business exception and therefore a regular expected outcome of the
* transactional business method, i.e. a kind of alternative return value which
* still allows for regular completion of resource operations.
* <p>This is largely consistent with TransactionTemplate's default behavior,
* except that TransactionTemplate also rolls back on undeclared checked exceptions
* (a corner case). For declarative transactions, we expect checked exceptions to be
* intentionally declared as business exceptions, leading to a commit by default.
* @see org.springframework.transaction.support.TransactionTemplate#execute
*/
@Override
public boolean rollbackOn(Throwable ex) {
return (ex instanceof RuntimeException || ex instanceof Error);
}

So, we could append || ex instanceof UndeclaredThrowableException to solve it.

It will be a breaking change, but I think most people expect it to work that way anyway.

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Apr 23, 2021
@sbrannen
Copy link
Member

So, we could append || ex instanceof UndeclaredThrowableException to solve it.

If you are proposing that be added in the rollbackOn() method in DefaultTransactionAttribute, that would not have any effect since UndeclaredThrowableException extends RuntimeException.

Are you perhaps proposing that the cause of the UndeclaredThrowableException be taken into consideration?

@sbrannen sbrannen added in: data Issues in data modules (jdbc, orm, oxm, tx) status: waiting-for-feedback We need additional information before we can continue labels Apr 27, 2021
@sbrannen sbrannen changed the title Suggestion: @Transaction should recognize UndeclaredThrowableException as checked exception @Transactional should treat UndeclaredThrowableException as a checked exception Apr 27, 2021
@sbrannen
Copy link
Member

@Sam-Kruglov, are you aware that you can configure @Transactional as follows?

@Transactional(noRollbackFor = UndeclaredThrowableException.class)
@SneakyThrows
public void lombokSurprise() {
    jdbcTemplate.execute("insert into test_table values('lombok!')");
    throw new Exception("Simple exception");
}

That should help you achieve your goal.

@Sam-Kruglov
Copy link
Contributor Author

Sam-Kruglov commented Apr 27, 2021

Editing @Transactional seems like a workaround, I think it’s more intuitive if it’s the default behavior.

The exception thrown is checked if it extends Exception, not if it’s declared in the method signature, so it feels like we’re checking the consequence rather than the source of truth.

I think your suggestion about using cause is better, sorry, I guess I didn’t read enough of the code :)

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Apr 27, 2021
@sbrannen
Copy link
Member

Editing @Transactional seems like a workaround, I think it’s more intuitive if it’s the default behavior.

That's not actually a workaround but rather the supported mechanism for configuring commit/rollback semantics with declarative transaction management in Spring.

The exception thrown is checked if it extends Exception, not if it’s declared in the method signature, so it feels like we’re checking the consequence rather than the source of truth.

Well, we don't know why the UndeclaredThrowableException was thrown (at least not without analyzing the stack trace, which we would not want to do). It could have been thrown from a JDK dynamic proxy not created by Spring or from some other source.

In other words, we would not want to change the current behavior since it would indeed be breaking change.

I think your suggestion about using cause is better, sorry, I guess I didn’t read enough of the code :)

No worries. 😉

In any case, in light of the configuration option outlined in #26854 (comment), I am closing this issue.

@sbrannen sbrannen added status: declined A suggestion or change that we don't feel we should currently apply and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels Apr 27, 2021
@sbrannen sbrannen self-assigned this Apr 27, 2021
@sbrannen sbrannen removed the status: feedback-provided Feedback has been provided label Apr 27, 2021
@Sam-Kruglov
Copy link
Contributor Author

Wait but we do know where it comes from:

Also, I’m aware of this configuration, still seems like a workaround to me.

Thanks for your time either way!

@Sam-Kruglov
Copy link
Contributor Author

Even if that’s something standard in the JDK, I’m fine with wrapping it, still, feels like we should treat it as checked because this wrapper is literally for wrapping checked exceptions if I understood correctly

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: data Issues in data modules (jdbc, orm, oxm, tx) status: declined A suggestion or change that we don't feel we should currently apply
Projects
None yet
Development

No branches or pull requests

3 participants