{"id":616,"date":"2025-07-19T23:20:13","date_gmt":"2025-07-19T22:20:13","guid":{"rendered":"https:\/\/www.javaeinfacherkl\u00e4rt.de\/?p=616"},"modified":"2025-08-07T23:22:44","modified_gmt":"2025-08-07T22:22:44","slug":"die-transactional-annotation-in-spring-und-ihr-verhalten-bei-privaten-methoden","status":"publish","type":"post","link":"https:\/\/www.xn--javaeinfacherklrt-4qb.de\/?p=616","title":{"rendered":"Die @Transactional-Annotation in Spring und ihr Verhalten bei privaten Methoden"},"content":{"rendered":"\n<p>Die Annotation <code>@Transactional<\/code> ist ein zentrales Werkzeug in Spring zur Verwaltung von Datenbanktransaktionen. Sie erm\u00f6glicht es, Transaktionen deklarativ zu steuern, etwa um sicherzustellen, dass ein Datenbankzugriff vollst\u00e4ndig abgeschlossen wird oder bei einem Fehler vollst\u00e4ndig zur\u00fcckgerollt wird.<\/p>\n\n\n\n<p>Viele Entwicklerinnen und Entwickler gehen davon aus, dass <code>@Transactional<\/code> unabh\u00e4ngig von der Sichtbarkeit der annotierten Methode funktioniert. In der Praxis jedoch wird <code>@Transactional<\/code> <strong>nicht<\/strong> angewendet, wenn die Methode <code>private<\/code> ist. Dieses Verhalten kann zu subtilen Fehlern f\u00fchren, die schwer zu erkennen sind. In diesem Artikel erkl\u00e4ren wir, warum das so ist, wie Spring dabei arbeitet und wie man korrekt mit solchen Situationen umgeht.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">1. Grundlagen: Was bewirkt <code>@Transactional<\/code>?<\/h2>\n\n\n\n<p>Die Annotation <code>@Transactional<\/code> signalisiert dem Spring-Framework, dass eine Methode innerhalb eines transaktionalen Kontexts ausgef\u00fchrt werden soll. Spring \u00fcbernimmt dabei automatisch die Verwaltung der Transaktion: Sie wird zu Beginn der Methode gestartet und am Ende \u2013 je nach Erfolg oder Fehler \u2013 entweder committed oder zur\u00fcckgerollt.<\/p>\n\n\n\n<p>Beispiel:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">@Service\n<span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">OrderService<\/span> <\/span>{\n\n    @Transactional\n    <span class=\"hljs-keyword\">public<\/span> void createOrder(Order order) {\n        <span class=\"hljs-comment\">\/\/ Datenbankoperationen<\/span>\n    }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Hier sorgt Spring daf\u00fcr, dass alle in <code>createOrder<\/code> enthaltenen Datenbankzugriffe innerhalb einer Transaktion stattfinden.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">2. Technischer Hintergrund: AOP und Proxies in Spring<\/h2>\n\n\n\n<p>Spring implementiert <code>@Transactional<\/code> in der Regel mit Hilfe von <strong>AOP (Aspect-Oriented Programming)<\/strong>, genauer gesagt durch <strong>Proxies<\/strong>.<\/p>\n\n\n\n<p>Diese Proxies werden um Spring-Beans herum erstellt und k\u00f6nnen Methodenaufrufe abfangen und erweitern \u2013 zum Beispiel durch das Starten und Beenden von Transaktionen. Dabei gibt es zwei relevante Arten von Proxies:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>JDK Dynamic Proxies<\/strong>: Funktionieren nur, wenn die Bean ein Interface implementiert. Der Proxy wird dann auf Basis des Interfaces erzeugt.<\/li>\n\n\n\n<li><strong>CGLIB Proxies<\/strong>: Hierbei wird eine Subklasse der Bean erzeugt, die die Methoden \u00fcberschreibt. Das erlaubt auch die Proxy-Erzeugung f\u00fcr Klassen ohne Interfaces.<\/li>\n<\/ul>\n\n\n\n<p>Beide Proxy-Arten haben gemeinsam: <strong>Nur Methodenaufrufe, die \u00fcber den Proxy laufen, k\u00f6nnen abgefangen werden.<\/strong><\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">3. Warum <code>@Transactional<\/code> bei privaten Methoden nicht funktioniert<\/h2>\n\n\n\n<p>Private Methoden sind von au\u00dfen nicht sichtbar und damit auch nicht durch den Proxy erreichbar. Wird eine private Methode innerhalb derselben Bean aufgerufen, erfolgt der Aufruf direkt \u2013 ohne Umweg \u00fcber den Proxy. Dadurch wird die AOP-Logik, also auch die transaktionale Verarbeitung, <strong>nicht ausgef\u00fchrt<\/strong>.<\/p>\n\n\n\n<p>Beispiel:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">@Service\n<span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">UserService<\/span> <\/span>{\n\n    <span class=\"hljs-keyword\">public<\/span> void registerUser(User user) {\n        saveUser(user);\n    }\n\n    @Transactional\n    <span class=\"hljs-keyword\">private<\/span> void saveUser(User user) {\n        <span class=\"hljs-comment\">\/\/ Datenbankzugriffe<\/span>\n    }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>In diesem Fall wird die Methode <code>saveUser<\/code> innerhalb der Klasse direkt aufgerufen \u2013 der Proxy wird umgangen. Das bedeutet: Die Annotation <code>@Transactional<\/code> hat <strong>keine Wirkung<\/strong>, es findet <strong>keine Transaktion<\/strong> statt.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">4. Sichtbarkeiten und AOP-Unterst\u00fctzung<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Sichtbarkeit<\/th><th>Transaktion wirksam?<\/th><th>Bemerkung<\/th><\/tr><\/thead><tbody><tr><td><code>public<\/code><\/td><td>Ja<\/td><td>Wird vom Proxy abgefangen<\/td><\/tr><tr><td><code>protected<\/code><\/td><td>Eingeschr\u00e4nkt<\/td><td>Nur mit CGLIB nutzbar<\/td><\/tr><tr><td>Paket-privat<\/td><td>Eingeschr\u00e4nkt<\/td><td>Abh\u00e4ngig von der Proxy-Art<\/td><\/tr><tr><td><code>private<\/code><\/td><td>Nein<\/td><td>Nie vom Proxy erreichbar<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>Wichtig ist nicht nur die Sichtbarkeit, sondern auch die Art des Aufrufs. Selbst wenn eine Methode <code>public<\/code> ist, wird <code>@Transactional<\/code> nicht ausgef\u00fchrt, wenn der Methodenaufruf <strong>intern<\/strong> erfolgt \u2013 also innerhalb derselben Klasse.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">5. H\u00e4ufige Fehlerquelle: Interne Methodenaufrufe<\/h2>\n\n\n\n<p>Ein typischer Fehler besteht darin, eine <code>@Transactional<\/code>-Methode innerhalb derselben Bean aufzurufen. Auch wenn die Methode <code>public<\/code> ist, l\u00e4uft dieser interne Aufruf <strong>nicht \u00fcber den Proxy<\/strong> \u2013 und damit auch nicht durch die AOP-Logik.<\/p>\n\n\n\n<p>Beispiel:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">@Service\n<span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">EmailService<\/span> <\/span>{\n\n    <span class=\"hljs-keyword\">public<\/span> void processEmails() {\n        fetchMails(); <span class=\"hljs-comment\">\/\/ Direkter Aufruf, keine Transaktion<\/span>\n    }\n\n    @Transactional\n    <span class=\"hljs-keyword\">public<\/span> void fetchMails() {\n        <span class=\"hljs-comment\">\/\/ Erwartet transaktionales Verhalten<\/span>\n    }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Hier wird <code>fetchMails<\/code> direkt aus <code>processEmails<\/code> aufgerufen. Das transaktionale Verhalten wird dabei nicht aktiviert.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">6. L\u00f6sungen und Best Practices<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">1. Aufteilung in separate Beans<\/h3>\n\n\n\n<p>Die sauberste L\u00f6sung besteht darin, die transaktionale Logik in eine eigene Service-Klasse auszulagern. Dadurch wird sichergestellt, dass der Aufruf \u00fcber den Proxy erfolgt.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">@Service\n<span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">FetchService<\/span> <\/span>{\n\n    @Transactional\n    <span class=\"hljs-keyword\">public<\/span> void fetchMails() {\n        <span class=\"hljs-comment\">\/\/ Logik mit Transaktion<\/span>\n    }\n}\n\n@Service\n<span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">EmailService<\/span> <\/span>{\n\n    <span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">final<\/span> FetchService fetchService;\n\n    <span class=\"hljs-keyword\">public<\/span> EmailService(FetchService fetchService) {\n        this.fetchService = fetchService;\n    }\n\n    <span class=\"hljs-keyword\">public<\/span> void processEmails() {\n        fetchService.fetchMails(); <span class=\"hljs-comment\">\/\/ Aufruf \u00fcber Proxy<\/span>\n    }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h3 class=\"wp-block-heading\">2. Selbstreferenz \u00fcber den ApplicationContext<\/h3>\n\n\n\n<p>Ein Workaround ist der Zugriff auf sich selbst \u00fcber den Spring-Kontext:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript\">@Service\npublic <span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">UserService<\/span> <\/span>{\n\n    @Autowired\n    private ApplicationContext context;\n\n    public <span class=\"hljs-keyword\">void<\/span> registerUser(User user) {\n        context.getBean(UserService.class).saveUser(user);\n    }\n\n    @Transactional\n    public <span class=\"hljs-keyword\">void<\/span> saveUser(User user) {\n        <span class=\"hljs-comment\">\/\/ Transaktion wird gestartet<\/span>\n    }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code-Sprache:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Dieser Ansatz funktioniert technisch, wird jedoch nicht als Best Practice angesehen, da er den Code un\u00fcbersichtlich macht und die Kapselung verletzt.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">3. AspectJ-Weaving<\/h3>\n\n\n\n<p>Eine Alternative zum Proxy-basierten AOP ist das sogenannte <strong>Weaving<\/strong> mit AspectJ. Dabei wird der Bytecode direkt ver\u00e4ndert, sodass auch private Methoden interceptet werden k\u00f6nnen. Dies erfordert jedoch eine deutlich komplexere Konfiguration und ist in den meisten Projekten nicht notwendig.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">7. Zusammenfassung<\/h2>\n\n\n\n<p>Das Verhalten von <code>@Transactional<\/code> in Spring h\u00e4ngt eng mit der Funktionsweise von AOP und Proxies zusammen. Da Spring standardm\u00e4\u00dfig mit Proxies arbeitet, gelten folgende Regeln:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>@Transactional<\/code> wirkt nur bei Aufrufen, die <strong>\u00fcber den Proxy<\/strong> erfolgen.<\/li>\n\n\n\n<li>Private Methoden k\u00f6nnen <strong>niemals<\/strong> durch den Proxy aufgerufen werden \u2013 daher hat <code>@Transactional<\/code> dort <strong>keine Wirkung<\/strong>.<\/li>\n\n\n\n<li>Auch <code>public<\/code>-Methoden haben <strong>keine Wirkung<\/strong>, wenn sie intern aufgerufen werden.<\/li>\n\n\n\n<li>F\u00fcr transaktionale Logik sollten eigene Beans verwendet werden, damit der Proxy korrekt arbeiten kann.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Best Practices:<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Transaktionale Methoden immer <code>public<\/code> deklarieren<\/li>\n\n\n\n<li>Keine Selbstaufrufe innerhalb derselben Bean<\/li>\n\n\n\n<li>Transaktionale Logik in separate Services auslagern<\/li>\n\n\n\n<li>Klar dokumentieren, welche Methoden transaktional sein sollen<\/li>\n<\/ul>\n\n\n\n<p>Ein solides Verst\u00e4ndnis der internen Mechanismen von Spring AOP hilft, Fehler fr\u00fchzeitig zu vermeiden und wartbaren, zuverl\u00e4ssigen Code zu schreiben.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Die Annotation @Transactional ist ein zentrales Werkzeug in Spring zur Verwaltung von Datenbanktransaktionen. Sie erm\u00f6glicht es, Transaktionen deklarativ zu steuern, etwa um sicherzustellen, dass ein Datenbankzugriff vollst\u00e4ndig abgeschlossen wird oder bei einem Fehler vollst\u00e4ndig zur\u00fcckgerollt wird. Viele Entwicklerinnen und Entwickler gehen davon aus, dass @Transactional unabh\u00e4ngig von der Sichtbarkeit der annotierten Methode funktioniert. In der [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[5],"tags":[],"class_list":["post-616","post","type-post","status-publish","format-standard","hentry","category-spring"],"_links":{"self":[{"href":"https:\/\/www.xn--javaeinfacherklrt-4qb.de\/index.php?rest_route=\/wp\/v2\/posts\/616","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.xn--javaeinfacherklrt-4qb.de\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.xn--javaeinfacherklrt-4qb.de\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.xn--javaeinfacherklrt-4qb.de\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.xn--javaeinfacherklrt-4qb.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=616"}],"version-history":[{"count":1,"href":"https:\/\/www.xn--javaeinfacherklrt-4qb.de\/index.php?rest_route=\/wp\/v2\/posts\/616\/revisions"}],"predecessor-version":[{"id":617,"href":"https:\/\/www.xn--javaeinfacherklrt-4qb.de\/index.php?rest_route=\/wp\/v2\/posts\/616\/revisions\/617"}],"wp:attachment":[{"href":"https:\/\/www.xn--javaeinfacherklrt-4qb.de\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=616"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.xn--javaeinfacherklrt-4qb.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=616"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.xn--javaeinfacherklrt-4qb.de\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=616"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}