Hi Mark,

Thanks for your message.

It's even worse than you describe. The Scala folks went ahead and declared
"Throwable is all you ever need," i.e. to them the distinction between
Error and Exception is useless. Which it is, if you assume that Throwables
are all just Java things that Scala developers tend to avoid entirely
because of a perfect monadic world (they tend wrap all Java code in some IO
monad to make stuff more idiomatic).

Even within the Java community, not everyone agrees that this wrapping of
exceptions in chains of unchecked exceptions is really valuable. The
"idiom" was introduced by JavaEE and Spring, I believe? Every API adds
their own exception class hierarchy, wrapping the underlying API's
hierarchies. For example, using Spring+jOOQ+JDBC, you'd get Spring
exceptions wrapping jOOQ exceptions wrapping JDBC exceptions, all for
exceptions that you usually can't really recover from, so the only useful
exception in logs is the original one anyway. One reason why this might
have happened is again because of checked exceptions. Back in the early
2000s, when a lot more exceptions used to be checked, you *had to* wrap,
because you couldn't just throw any undeclared exception. But we learned
(the hard way), that hardly anyone really benefits from this feature, while
most suffer from it.

The main purpose of jOOQ's DataAccessException has always been to "hide"
the checked SQLException, which is one of the two annoying JDK checked
exceptions that everyone hates most (the other being IOException). Checked
exceptions can be a cool feature for "business exceptions", where you want
to emulate a union type in a funny way that strictly binds it to control
flow (weird, but kinda cool). Other languages formalised this idiom better,
e.g. using Either or other means of type checking different outcomes. In my
opinion, actual union types are the best solution here. Regrettably, few
languages have them (Scala 3 does, for example, or TypeScript). But neither
SQLException nor IOExceptions are such business exceptions (with a few
"exceptions," such as e.g. SQLIntegrityConstraintViolationException, which
can indeed act like a "business exception").

Re-throwing the checked exception is certainly possible with this
well-known generic erasure hack, but do note that it is not possible to
catch it in Java, without annoying glue code. For example, javac will
reject this program:

jshell> try {} catch (java.sql.SQLException e) {}
|  Error:
|  exception java.sql.SQLException is never thrown in body of corresponding
try statement
|  try {} catch (java.sql.SQLException e) {}
|         ^--------------------------------^

So, if jOOQ re-throws the checked exception, then, currently, it wouldn't
be possible to catch it. We would have to declare TransactionalRunnable et
al. to be generic with the exception type, i.e.

public interface TransactionalRunnable<T extends Throwable> {
    void run(Configuration c) throws T;
}

And then declare T as well in the transaction() methods. That would at
least make the change compile-time incompatible, instead of just
behaviourally incompatible, which is certainly better. But again, this
would go against anything else a jOOQ user would expect, where the
SQLException never surfaces client code. In fact, it still probably
wouldn't because the exception that rolls back the transaction might be a
jOOQ DataAccessException that again wraps the SQLException, so the idea
here is also to avoid re-wrapping the jOOQ exception. I guess that was the
main reason for the current design.

Perhaps that's the problem here? The fact that *some* exceptions don't get
re-wrapped like all the others? Perhaps rather than re-throwing
*everything* (and wrapping nothing), we should re-throw *nothing* (and wrap
everything)?

Clearly, either change is a drastic, behaviourally incompatible change, and
that alone makes it unlikely to be implemented soon.

Automatic Kotlin specific behaviour is also often not a good idea because
it's very hard to document, maintain, and make sure it really is what
people want. What if someone uses *both* Java and Kotlin in their code
base? Wouldn't it be terrible for one Spring service to behave this way,
and the other to behave that way?

The only way I can see to make this customisable would be to allow for
TransactionListener instances to override the cause that will eventually be
thrown. That way, users are in full control over this detail. I'm actually
surprised this isn't being done, since ExecuteListeners can do this (the
overriding, they also implement the same semantics regarding
RuntimeException). I'll look into this detail:
https://github.com/jOOQ/jOOQ/issues/15321

But again, I can't promise that such an ability to override an exception
will allow for throwing checked exceptions due to the above caveat and this
being very debatable in the Java ecosystem. You could try your luck and
copy paste your email to the issue tracker in a new feature request:
https://github.com/jOOQ/jOOQ/issues/new/choose

That way, we can collect user feedback from the kotlin community. If
there's significant buzz, it at least becomes clear that the problem is
important enough to address *somehow*.

I hope this helps,
Lukas





On Sun, Jul 2, 2023 at 5:29 PM Mark Amery <markrobertam...@gmail.com> wrote:

> I am a dev with close to zero experience writing Java who currently works
> on a Kotlin web application that uses jOOQ - my first significant
> application in a JVM language. As such, I frequently find myself surprised
> when I encounter interfaces or behaviours in Java libraries that seen to
> make little sense from a Kotlin perspective. This message is about such an
> experience with jOOQ.
>
> The Kotlin web application has a global exception handler; it has some
> special exception types that trigger special behavior in the global error
> handler; and at various places the application does stuff in transactions
> using code like this:
>
>     dsl.transaction() { config ->
>         // Logic that might throw exceptions
>     }
>
> As it happens, some of the types of exception we have special handling for
> in the global error handler extend from `Exception`, while others extend
> from `RuntimeException` - a fact I was not especially conscious of and that
> is mostly of no significance in Kotlin, where unchecked exceptions don't
> exist and `throw Exception("some exception message")` is a perfectly
> acceptable idiom. Imagine my surprise, then, to discover that SOME of those
> special exceptions, if I throw them from within a
> `DSLContext.transaction()` callback, don't trigger their special behaviours
> in the global exception handler, which instead receives a
> DataAccessException. Some digging revealed that this code in
> `transactionResult0` was to blame, which handles exceptions of types that
> inherit from `RuntimeException` differently from those whose type inherits
> directly from `Exception` - specifically by wrapping the latter in a
> `DataAccessException` instead of rethrowing them directly:
>
>     // [#6608] [#7167] Errors are no longer handled differently
>     if (cause instanceof RuntimeException e)
>         throw e;
>     else if (cause instanceof Error e)
>         throw e;
>     else
>         throw new DataAccessException(committed
>             ? "Exception after commit"
>             : "Rollback caused"
>             , cause
>         );
>
> No doubt this behavior seems intuitive and reasonable to Java programmers,
> who work in a world with checked exceptions. To a dev using jOOQ from
> Kotlin and unfamiliar with Java, though, it felt rather like an arbitrary
> trap that didn't need to exist, and my first instinct was to file a bug
> report. After all, jOOQ officially supports Kotlin, so shouldn't it refrain
> from doing this arbitrary wrapping of (some) exceptions that has no reason
> to happen from a Kotlin perspective?
>
> On reflection, though, I'm struggling to figure out how I ought to feel
> about this. What's the "correct" behavior for jOOQ to have here? Should
> anything change?
>
> I thought for a while about whether jOOQ should behave differently when
> being used "from Kotlin" than when being used "from Java", but I think that
> - given that Java code can call functions defined Kotlin code that calls
> functions defined in Java code and so on ad infinitum - the very concept of
> jOOQ being used "from Kotlin" or "from Java" is probably too ill-defined
> for this to make sense. Doing something like having behavior change based
> on the inclusion of the jOOQ-kotlin package also seems like a disgusting
> trap in its own right.
>
> That leaves me with a couple of thoughts on things that jOOQ could do that
> might be good ideas:
>
> 1. Maybe that block I quoted above should just unconditionally throw `e`?
> Obviously just `throw e` won't even compile, but there are hacks (see e.g.
> https://stackoverflow.com/q/31316581/1709587) to let you throw exceptions
> of checked types without listing them in a `throws` clause in the method
> declaration, so it is at least POSSIBLE for jOOQ to behave this way.
> Ignoring backwards compatibility, would this indeed be the better behavior
> for jOOQ to have? If so, is it worth breaking backwards compatibility over?
>
> 2. Maybe having some Kotlin-specific alternative to
> `DSLContext.transaction` (and whatever other methods are affected by this
> same issue) in jOOQ-kotlin? This could probably be a simple wrapper around
> `DSLContext.transaction` that "unwraps" exceptions that are wrapped in
> `DataAccessException`.
>
> Alternatively, perhaps the right perspective on this is that we Kotlin
> programmers are newcomers in Java lands, and need to assimilate into the
> local culture - including understanding Java practices like handling
> Exceptions and RuntimeExceptions differently, and anticipating their use.
> From this perspective, perhaps jOOQ is doing nothing wrong or ill-advised
> whatsoever, and I've simply learned a necessary lesson about Java.
> Certainly a workaround is possible: I can implement the wrapper around
> `DSLContext.transaction` that I contemplated above in my application and
> use it instead of using `DSLContext.transaction` directly.
>
> What's the right way to think about this, do you reckon? Should anything
> in jOOQ change?
>
> Cheers,
> Mark
>
> --
> You received this message because you are subscribed to the Google Groups
> "jOOQ User Group" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to jooq-user+unsubscr...@googlegroups.com.
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/jooq-user/c7a318bb-4f76-4734-ba2a-eb5afba20533n%40googlegroups.com
> <https://groups.google.com/d/msgid/jooq-user/c7a318bb-4f76-4734-ba2a-eb5afba20533n%40googlegroups.com?utm_medium=email&utm_source=footer>
> .
>

-- 
You received this message because you are subscribed to the Google Groups "jOOQ 
User Group" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to jooq-user+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/jooq-user/CAB4ELO4C8x%2Bb5gEzAA%3DDpRA6GanU2XAF5-%3DH7Ssf8H2pP9NitA%40mail.gmail.com.

Reply via email to