Transaktionssteuerung mit @Transactional

Transaktionen sind nötig, um Daten atomar, konsistent, isoliert und dauerhaft bearbeiten und speichern zu können (ACID-Prinzip). Methoden von CDI-Beans können durch eine simple Annotation mit einem aktiven Transaktionskontext versehen werden.

Transaktionsinterceptor mit dem Binding @Transactional

Schon seit der Version 1.1 von CDI steht ein global aktivierter Transaktionsinterceptor zur Verfügung. Er wird mit der Annotation @Transactional einer Methode einer CDI-Bean zugeordnet:

public class SomeService {
  ...
  @Transactional
  public void doSomethingTransactionally() {
    ...
  }

Beim Aufruf der Methode ohne aktive Transaktion wird nun eine Transaktion gestartet und nach dem Verlassen der Methode wieder geschlossen. Die Regeln für das Transaktionsende sind etwas seltsam:

  • Endet die Methode mit einem Return, wird ein Commit versucht.
  • Wirft die Methode eine sog. System Exception, resultiert dies in einem Rollback. System Exceptions sind i. W. die unchecked Exceptions (ergänzt um die RemoteException, die aber explizit kaum eine Rolle spielt).
  • Wirft die Methode eine Application Exception – das sind alle anderen Exeptions – wird ein Commit (!) versucht.

Insbesondere der letzte Punkt ist (zumindest mir) unverständlich: Für mich modellieren Exceptions immer Ausnahmesituationen, die den jeweiligen Geschäftsprozess ungültig machen. Glücklicherweise kann man mit einem Parameter der Annotation auch in diesem Fall ein Rollback ansteuern:

  @Transactional(rollbackOn=Exception.class)
  public void doSomethingTransactionally() {

Transaktionsmodus

Die Art der Transaktionssteuerung durch @Transactional kann mit dem Parameter value bestimmt werden:

  • TxType.REQUIRED ist der Default, der in den allermeisten Fällen passend ist. Beim Aufruf einer davon betroffenen Methode wird geprüft, ob bereits eine aktive Transaktion vorliegt. Wenn ja, wird diese für den Methodenaufruf genutzt. Andernfalls wird eine neue Transaktion gestartet und am Methodenende wieder beendet.
  • TxType.REQUIRES_NEW startet bei Methodenbeginn stets eine neue Transaktion, die am Methodenende wieder beendet wird. Eine ggf. bereits aktive Transaktion wird zuvor suspendiert. Dieser Modus sollte dann verwendet werden, wenn die Ergebnisse des Methodenaufrufs auch dann dauerhaft abgespeichert werden sollen, wenn die umgebende Transaktoin zurückgerollt wird. Beispiel: Auditing/Protokollierung von Methodenaufrufen.
  • TxType.MANDATORY verlangt eine aktive Transaktion bei Methodenbeginn und wirft andernfalls eine TransactionRequiredException. Dieser Modus ist könnte prinzipiell für Repositories (a.k.a DAOs) interessant sein, da deren feingranulare Methoden i. A. von einem Service aufgerufen werden, der selbst transaktional ist. Leider werden Lifecycle-Methoden (@PostConstruct-annotierte Methoden etc.) nicht durch die @Transactional-Interceptoren intercepted. Häufig werden darin Repository-Methoden aufgerufen. Dann sollte für Repositories wie auch bei anderen Services TxType.REQUIRED verwendet werden.
  • Die weiteren Modi TxType.NOT_SUPPORTED, TxType.SUPPORTS und TxType.NEVER sind i. A. unbrauchbar.

Danke für’s Lesen und bis bald – vielleicht auch in einem unserer Trainings in Berlin, Bielefeld, Köln oder bei Ihnen!
https://gedoplan-it-training.de/

Werbeanzeigen

CDI: Injektionen filtern nach Modulzugehörigkeit

In der vergangenen Woche habe ich in unseren Seminarräumen in Berlin ein Training Java EE Masterclass durchgeführt, worin neben JPA, EJB und JSF auch das Thema CDI behandelt wird. Mit den engagierten Teilnehmern entwickelte sich insbesondere am Kursende eine interessante Diskussion zu diversen weiterführenden Themen, u. a. darüber, ob es möglich sei, eine Bibliothek so aus CDI-Komponenten aufzubauen, dass diese innerhalb der Bibliothek beliebig zur Injektion bereit gestellt werden, eine die Bibliothek nutzende Anwendung jedoch nur bestimmte Beans daraus angeboten bekommt, also bspw. so:

  • Library cdi-injectfilter-lib
    • definiert einen Service GreetingService, der mit Hilfe eines weiteres Services PartOfDayService realisiert wird.
    • GreetingService und PartOfDayService sind CDI Beans, die mittels Injektion miteinander verknüpft werden.
  • Anwendung cdi-injectfilter-web
    • nutzt GreetingService aus cdi-injectfilter-lib mittels Injektion,
    • soll PartOfDayService nicht (mittels Injektion) nutzen.

Die spontane Idee, PartOfDayService durch Annotation mit @Vetoed (bzw. @Typed) aus dem (CDI-) Rennen zu nehmen, greift zu kurz, weil dann nicht nur die Injektion eines PartOfDayService in cdi-injectfilter-web verhindert wird, sondern die Nutzung von PartOfDayService als CDI Bean generell, also auch in cdi-injectfilter-lib, unterdrückt wird.

Gesucht ist also eine Möglichkeit, die Injektion einer CDI Bean in Abhängikeit vom Injektionsziel zu erlauben oder verwehren. Dies gelingt relativ einfach damit, dass die betreffende Bibliothek – im Beispiel cdi-injectfilter-lib – zu einer CDI Extension gemacht wird. Dann können mit Hilfe eines Observers für den Lifecycle Event, den der Container während des Anwendungsdeployments für eine Injektionsstelle feuert, unerwünschte Injektionen abgelehtn werden.

Die folgenden Schritte sind dafür nötig:

  • Bibliothek zu einer CDI Extension machen:
    • Klasse als Implementierung von javax.enterprise.inject.spi.Extension entwerfen und im Service Descriptor META-INF/services/javax.enterprise.inject.spi.Extension registrieren
      (⇒de.gedoplan.beantrial.cdi.injectfilter.lib.extension.InjectFilterExtension),
    • darin eine Observer-Methode für den Event ProcessInjectionPoint<T, X> vorsehen. Diese Methode wird vom Container während des initialen Scans für jeden erkannten Injektionspunkt aufgerufen. Am übergebenen Event-Objekt kann man erkennen, was wohin injiziert werden soll. Unerwünschte Kombinationen können durch Aufruf der Event-Methode addDefinitionError mit Schimpf und Schande belegt werden.
  • Zur Unterscheidung der nur innerhalb des Bibliotheksmoduls frei nutzbaren Beans von denen, die auch ausserhalb injizierbar sein sollen, empfiehlt sich die Einführung eines entsprechenden CDI Qualifiers
    (⇒de.gedoplan.beantrial.cdi.injectfilter.lib.LibraryService).
    Die Injektion innerhalb der Library geschieht dann (natürlich) ausschliesslich unter Nutzung dieses Qualifiers.
    In der o. a. Observer-Methode können die betroffenen Injektionsstellen anhand des Qualifiers erkannt werden und Injektionen in Ziele ausserhalb der Library abgelehnt werden.
  • Eine unerwünschte Injektion sollte nun mit einen Deployment-Fehler belegt sein
    (⇒de.gedoplan.beantrial.cdi.injectfilter.web.presentation.ServicePresenter).

Der gesamte Beispielcode mit den genannten Klassen (⇒ …) findet sich auf GitHub.

Es sei darauf hingewiesen, dass die geschilderte Lösung ein paar kleine Contras hat:

  1. Der Beispielcode macht die „Injektionserlaubnis“ am Paket der Zielklasse fest. Das setzt eine „gutmütige“ Paketstruktur voraus. Würde ein(e) „kreative(r)“ Entwickler(in) der nutzenden Anwendung die Beans in Paketen platzieren, deren Name mit denen der genutzten Bibliothek übereinstimmen, würde er/sie die Injektionseinschränkung damit umgehen.
  2. Ebenfalls umgehen ließe sich die Injektionseinschränkung durch die Bereitstellung eines passenden Producers in der aufrufenden Anwendung.
  3. Die Einschränkung wird erst zum Deployent-Zeitpunkt erkennbar. Eine IDE wie Eclipse oder NetBeans wird vorher nicht warnen, wenn eine unerwünschte Injektion programmiert wird.

Die ersten beiden Nachteile treten bei „gutmütiger“ Softwareentwicklung nicht auf. Oder anders gesagt: Denen mit zuviel kreativer Energie kann so nicht geholfen werden. Der dritte Nachteil wird durch die empfohlene Nutzung eines Qualifiers zumindest weitgehend entschärft – soweit der Quallifier nicht kreativ misbraucht wird – aber das hatten wir ja schon.

Viel Spass/Erfolg mit komponentenbasierter Softwareentwicklung auf Basis von Java EE!

Zur Vertiefung sei auf unser umfangreiches Seminarangebot hingewiesen: GEDOPLAN IT Training. Alle Kurse gibt es auch kundenspezifisch angepasst!

Lifecycle-Events der CDI-Scopes

Beim Aktivieren und Deaktivieren von CDI-Scopes sendet der Container Events, die von der Anwendung bspw. zur Initialisierung genutzt werden können. Die Events sind Objekte, die mit dem Qualifier @Initialized(XyzScoped.class) bzw. @Destroyed(XyzScoped.class) qualifiziert sind.

So könnte der folgende Code verwendet werden, um nach dem Start bzw. Deployment der Anwendung Code auszuführen:

public class AppInit {
  private void doSomething(@Observes @Initialized(ApplicationScoped.class) Object event) {
    ...
  }
}

Es ist allerdings zu berücksichtigen, dass zum Zeitpunkt der Methodenausführung noch nicht alle Kontexte aktiv sind. Im Beispiel oben wurde ja gerade der Application Scope gestartet, was noch lange nicht bedeutet, dass auch bspw. der Request Scope schon aktiviert wurde.