Support type-safe transaction rollback rules

Prior to this commit, there was no way to configure type-safe rollback
rules for transactions.

Even though a rollback rule could be defined using a Class reference
via the `rollbackFor` and `noRollbackFor` attributes in @Transactional,
those Class references got converted to Strings (as the fully qualified
class names of the exception types) in RollbackRuleAttribute which then
applied a pattern-based matching algorithm as if the Class references
had been supplied as Strings/patterns to begin with, thereby losing the
type information.

Pattern-based rollback rules suffer from the following three categories
of unintentional matches.

- identically named exceptions in different packages when the pattern
  does not include the package name -- for example,
  example.client.WebException and example.server.WebException both
  match against a "WebException" pattern.

- similarly named exceptions in the same package when a given exception
  name starts with the name of another exception -- for example,
  example.BusinessException and example.BusinessExceptionWithDetails
  both match against an "example.BusinessException" pattern.

- nested exceptions when an exception type is declared in another
  exception -- for example, example.BusinessException and
  example.BusinessException$NestedException both match against an
  "example.BusinessException" pattern.

This commit prevents the latter two categories of unintentional matches
for rollback rules defined using a Class reference by storing the
exceptionType in RollbackRuleAttribute and using that type in the
implementation of RollbackRuleAttribute.getDepth(Class, int), resulting
in type-safe rollback rules whenever the `rollbackFor` and
`noRollbackFor` attributes in `@Transactional` are used.

Note that the first category of unintentional matches never applied to
rollback rules created from a Class reference since the fully qualified
name of a Class reference always includes the package name.

Closes gh-28098
This commit is contained in:
Sam Brannen
2022-03-04 18:28:48 +01:00
parent 9cb4783296
commit c1033dbfb3
4 changed files with 140 additions and 100 deletions

View File

@@ -1042,40 +1042,45 @@ including checked exceptions by specifying _rollback rules_.
[NOTE]
====
Rollback rules determine if a transaction should be rolled back when a given exception is
thrown, and the rules are based on patterns. A pattern can be a fully qualified class
name or a substring of a fully qualified class name for an exception type (which must be
a subclass of `Throwable`), with no wildcard support at present. For example, a value of
`"jakarta.servlet.ServletException"` or `"ServletException"` will match
`jakarta.servlet.ServletException` and its subclasses.
thrown, and the rules are based on exception types or exception patterns.
Rollback rules may be configured in XML via the `rollback-for` and `no-rollback-for`
attributes, which allow patterns to be specified as strings. When using
attributes, which allow rules to be defined as patterns. When using
<<transaction-declarative-attransactional-settings,`@Transactional`>>, rollback rules may
be configured via the `rollbackFor`/`noRollbackFor` and
`rollbackForClassName`/`noRollbackForClassName` attributes, which allow patterns to be
specified as `Class` references or strings, respectively. When an exception type is
specified as a class reference its fully qualified name will be used as the pattern.
Consequently, `@Transactional(rollbackFor = example.CustomException.class)` is equivalent
to `@Transactional(rollbackForClassName = "example.CustomException")`.
`rollbackForClassName`/`noRollbackForClassName` attributes, which allow rules to be
defined based on exception types or patterns, respectively.
When a rollback rule is defined with an exception type, that type will be used to match
against the type of a thrown exception and its super types, providing type safety and
avoiding any unintentional matches that may occur when using a pattern. For example, a
value of `jakarta.servlet.ServletException.class` will only match thrown exceptions of
type `jakarta.servlet.ServletException` and its subclasses.
When a rollback rule is defined with an exception pattern, the pattern can be a fully
qualified class name or a substring of a fully qualified class name for an exception type
(which must be a subclass of `Throwable`), with no wildcard support at present. For
example, a value of `"jakarta.servlet.ServletException"` or `"ServletException"` will
match `jakarta.servlet.ServletException` and its subclasses.
[WARNING]
=====
You must carefully consider how specific the pattern is and whether to include package
You must carefully consider how specific a pattern is and whether to include package
information (which isn't mandatory). For example, `"Exception"` will match nearly
anything and will probably hide other rules. `"java.lang.Exception"` would be correct if
`"Exception"` were meant to define a rule for all checked exceptions. With more unique
exception names such as `"BaseBusinessException"` there is likely no need to use the
fully qualified class name for the exception pattern.
Furthermore, rollback rules may result in unintentional matches for similarly named
exceptions and nested classes. This is due to the fact that a thrown exception is
considered to be a match for a given rollback rule if the name of thrown exception
contains the exception pattern configured for the rollback rule. For example, given a
rule configured to match on `com.example.CustomException`, that rule would match against
an exception named `com.example.CustomExceptionV2` (an exception in the same package as
`CustomException` but with an additional suffix) or an exception named
`com.example.CustomException$AnotherException` (an exception declared as a nested class
in `CustomException`).
Furthermore, pattern-based rollback rules may result in unintentional matches for
similarly named exceptions and nested classes. This is due to the fact that a thrown
exception is considered to be a match for a given pattern-based rollback rule if the name
of the thrown exception contains the exception pattern configured for the rollback rule.
For example, given a rule configured to match on `"com.example.CustomException"`, that
rule will match against an exception named `com.example.CustomExceptionV2` (an exception
in the same package as `CustomException` but with an additional suffix) or an exception
named `com.example.CustomException$AnotherException` (an exception declared as a nested
class in `CustomException`).
=====
====
@@ -1770,7 +1775,7 @@ properties of the `@Transactional` annotation:
TIP: See <<transaction-declarative-rollback-rules, Rollback rules>> for further details
on rollback rule semantics, patterns, and warnings regarding possible unintentional
matches.
matches for pattern-based rollback rules.
Currently, you cannot have explicit control over the name of a transaction, where 'name'
means the transaction name that appears in a transaction monitor, if applicable