| = Exception Management |
| |
| Managing Exceptions should not be a core part of your business logic. |
| In fact with the Transaction Control Service is doing its job you shouldn't need to worry about exceptions at all! |
| A key design goal for the Transaction Control Service is to avoid the need for try/catch/finally blocks as far as possible. |
| |
| == Throwing Exceptions from scoped work |
| |
| Resources can be tempramental sometimes, and usually have defined exceptions that are thrown in certain error cases. |
| These can be generic, like `SQLException` or `JMSException`, or specific like `EntityNotFoundException`. |
| |
| In any event - exceptions indicate that a problem has occurred. |
| By default an exception thrown from inside a transactional scope will cause the transaction to roll back. |
| This means that the code can safely ignore any updates that were made. |
| Furthermore, because a piece of scoped work is defined as a `Callable` it is not necessary to catch or wrap an Exception raised in a scope. |
| |
| // An SQLException may be raised by the query, |
| // but we don't need to catch it |
| txControl.required(() -> connection.createStatement() |
| .executeQuery("Insert into TEST_TABLE values ( 'Hello World!' )")); |
| |
| == Catching Exceptions thrown from scoped work |
| |
| In general Exceptions should not form part of your client API, so catching an Exception from a piece of scoped work is rarely necessary. |
| Usually exceptions generated by scoped work are eventually handled by catch all collectors at the incoming request point (for example a servlet) and do not require special handling. |
| |
| Sometimes, however, we have to work within an existing API that _does_ use an Exception as a type of return value. |
| In that case it is important to know what happened to the Exception. |
| |
| === The `ScopedWorkException` |
| |
| Scoped work is free to throw checked or unchecked Exceptions, however these Exceptions cannot be directly thrown on by the TransactionControl implementation. |
| The primary reason for this is that directly rethrowing the Exception would force users of the Transaction Control Service to either _always_ declare `throws Exception` on their methods or to add try/catch blocks around every call. |
| |
| Exceptions generated as part of Scoped Work are therefore wrapped by the Transaction Control Service in a `ScopedWorkException`. |
| `ScopedWorkException` is an _unchecked exception_ and so can be ignored by your component if it does not require special handling (the typical case). |
| |
| === Unwrapping the `ScopedWorkException` |
| |
| As mentioned above, sometimes it is necessary for an API to throw a particular type of Exception as a return value. |
| |
| This model can be supported by unwrapping the ScopedWorkException. |
| |
| try { |
| txControl.required(() -> connection.createStatement() |
| .executeQuery("Insert into TEST_TABLE values ( 'Hello World!' )")); |
| } catch (ScopedWorkException swe) { |
| // This line throws the cause of the ScopedWorkException as |
| // an SQLException or as a RuntimeException if appropriate |
| throw swe.as(SQLException.class); |
| } |
| |
| This mechanism also supports multiple Exception types: |
| |
| try { |
| txControl.required(() -> connection.createStatement() |
| .executeQuery("Insert into TEST_TABLE values ( 'Hello World!' )")); |
| } catch (ScopedWorkException swe) { |
| // This line throws the cause of the ScopedWorkException |
| // as one of the two SQLException types or as a |
| // RuntimeException if appropriate |
| throw swe.asOneOf(SQLRecoverableException.class, SQLTransientException.class); |
| } |
| |
| Note that if you unwrap a `ScopedWorkException` into a checked exception then you will have to list that Exception in your `throws` clause. |