Add #closeExceptionally(Throwable) to AutoClosable

Richard Clark rgc197.at.zepler.org at gmail.com
Sat Mar 23 17:27:24 UTC 2019


Hi.

Faced with similar issues I've generally found that there is a lot of
mileage in pushing this sort of code into the transaction manager itself.
The calling code then passes the business logic to an executeTransaction
method on the manager who then handles the begin, commit or rollback
internally.

This allows for even simpler code:

  manager.executeTransaction(this::businessLogic);

>From experience it's a good idea to provide at least two versions for code
which either does or does not require return values, so that you don't
force either mandatory null returns or returns via result holders:

  void executeTransaction(Runnable logic);
  <R> R executeTransaction(Supplier<R> logic);

If it's common for your business logic to want manually control the
transaction then you can throw in a few for variants to support this:

  void executeTransaction(Consumer<? super UserTransaction> logic);
  <R> R executeTransaction(Function<? super UserTransaction> logic);

.. of course the executeTransaction logic will have to inspect the status
of the transaction to determine which actions the business logic has
already performed.

Cheers,
Richard.

On Fri, 22 Mar 2019 at 18:40, Kasper Nielsen <kasperni at gmail.com> wrote:

> Hi,
>
> I was looking at some example on transaction management the other day.
> And no matter if it is JPA, JDBC og something else. They all look
> similar to this (or uses some kind of @Transactional annotation
> coupled with AOP)
>
> UserTransaction utx = entityManager.getTransaction();
>
> try {
>     utx.begin();
>     businessLogic();
>     utx.commit();
> } catch(Exception ex) {
>     utx.rollback();
>     throw ex;
> }
>
> I think we can all agree that this code is repetitive, error-prone and
> not very readable.
> And if you have a large codebase, chances are you might forget
> commit() or rollback() somewhere. And god forbid you start nesting the
> calls.
>
> You cannot really use try-with-resources here because you need to
> perform one kind of
> "close" operation if your business logic succeed and another if it fails.
>
> So I though why not add the following method to AutoClosable
>
> default closeExceptionally(Throwable cause) throws Exception {
>   close();
> }
>
> With the implementation of try-with-resources changing to
>
>     try ResourceSpecification_tail
>         Block
>     catch (Throwable #t) {
>         #primaryExc = #t;
>         throw #t;
>     } finally {
>         if (Identifier != null) {
>             if (#primaryExc != null) {
>                 try {
>                     Identifier.close(); ------------->
> Identifier.closeExceptionally(primaryExc);
>                 } catch (Throwable #suppressedExc) {
>                     #primaryExc.addSuppressed(#suppressedExc);
>                 }
>             } else {
>                 Identifier.close();
>             }
>         }
>     }
>
> This would allow you use something much simpler.
>
> try (var ignore = entityManager.runInNewTransaction()) {
>     businessLogic();
> }
>
> It does change the semantics on Closable a bit. And it might be
> problematic that your code will compile fine on older Java versions,
> but not work as expected because closeExceptionally is never invoked.
> However, I think it is a net win
>
> Thoughts?
>
> /Kasper
>


More information about the amber-dev mailing list