You're maybe overlooking the fact that jOOQ is consistent with this
behaviour throughout its API. For example, DSLContext::connection or
DSLContext::batched work the same way.

Any change would be a *significant* change and is thus unlikely, even if
you now think that your design would be more desirable than the status quo,
of which I'm not entirely convinced.

On Mon, Jul 3, 2023 at 3:53 PM Mark Amery <markrobertam...@gmail.com> wrote:

> Very interesting, and thanks for the reply.
>
> I hadn't realised that type parameters could be used in the "throws"
> clause of a generic method in Java! That's pretty cool! I am in no position
> to speak authoritatively since I am not a Java dev, but the more I think
> about it the more I think your "throws T" approach would actually be the
> perfect approach here if it weren't for backwards-compatibility concerns,
> not just for the sake of Kotlin devs but also for Java devs. After all,
> if the user passes a lambda to `transaction` that throws a checked
> exception, which they'd be forced to explicitly catch and handle without
> jOOQ in the middle, isn't it a bit weird for jOOQ to intercept that and
> neutralise the enforcement the compiler would otherwise provide that the
> dev has remembered to handle the exception? jOOQ does this right now even
> for what you call "business exceptions"; if I throw such a checked business
> exception from within the lambda passed to `transaction`, jOOQ catches it
> and wraps it in a DataAccessException - effectively telling me "nope, you
> don't need to worry about catching that checked business exception". That
> seems like the wrong behaviour on jOOQ's part!
>
> Some tinkering around with toy Java programs in IntelliJ suggests to me
> that the Java compiler IS clever enough to infer what checked exceptions
> can be thrown from a lambda and that the `throws T` approach would
> therefore behave the way we'd want it to - i.e. propagating the
> compile-time need for checked exceptions to be declared or caught up to the
> caller of transaction(). (Probably you already know this, but I wasn't sure
> until I tinkered and so I note it here for anyone else in doubt.) This
> seems really nice to me; IMO the behaviour you'd get with `throws T` is the
> perfect behaviour to have here in both Java and Kotlin and would be a
> no-brainer if it were not a backwards-compatibility break. What a shame
> that it is!
>
> Anyway, I might go ahead and create an issue as you suggest and see if I
> stir up further discussion.
>
> Cheers,
> Mark
> On Monday, 3 July 2023 at 12:45:33 UTC+5:30 lukas...@gmail.com wrote:
>
>> 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 <markrob...@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+...@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/a05feb85-864b-46d4-a1c6-ceccb762c74cn%40googlegroups.com
> <https://groups.google.com/d/msgid/jooq-user/a05feb85-864b-46d4-a1c6-ceccb762c74cn%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/CAB4ELO525u_FUhDXS4-A5bXFdfjFciiHG1qn1yyxft%3DhS5igYA%40mail.gmail.com.

Reply via email to