Das Spring Framework (http://www.springsource.org/) ist eines der am weitesten verbreiteten Frameworks im Java Enterprise Umfeld. Selbst mit JavaEE 6 findet in vielen Projekten Spring noch Verwendung. Mit diesem kleinen Beitrag möchte ein paar Tipps zum debuggen einer – in meinen Augen – frustrierenden wie zeitraubenden Situation geben:

org.springframework.transaction.TransactionSystemException: Could not
commit JPA transaction; nested exception is javax.persistence.RollbackException: Error while committing
the transaction	at
org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:476) at
org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit
(AbstractPlatformTransactionManager.java:754) at
org.springframework.transaction.support.AbstractPlatformTransactionManager.commit
(AbstractPlatformTransactionManager.java:723) at
org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning
(TransactionAspectSupport.java:374)

Was ist da los? Ein Fehler bei der Ausfuehrung einer Transaktion! Und woher kommt der? Schaut man in den JpaTransactionManager in die doCommit Methode, so sieht es erst mal so aus, als handele es sich um eine Exception die von „weit weg“ kommt. Vielleicht ein Fehler in der Datenbank, dem Treiber oder etwas in der Richtung. Leider fehlt der Exception auch der „cause“ – eine geschachtelte Exception, die normalerweise schnellen Aufschluss über die Art des Programmierfehlers gibt. (Gern gesehen: NullpointerException 🙂 )
Doch davon war in meinem Fall nichts zu sehen. Es sollte ein Rollback stattfinden – ok. Nur wieso? Ich konnte keinen Fehler finden. Die Datenbankstruktur ist in Ordnung, MySQL ist mit InnoDB ausgestattet und unterstützt Transaktionen.

Ich hatte schließlich eine Idee das Problem besser einzugrenzen: Ich rief auf dem EntityManager direkt die persist/merge Methoden für die jeweiligen Objekte und dann die flush Methode auf. Damit wird der JPA Entitymanager (in diesem Fall Hibernate) dazu gebracht, direkt die Transaktion durchzufuehren, und die Abwicklung nicht über den Spring AOP Proxy durchgeführt.

Damit wird dann auch verhindert, dass das Spring Exception Mapping/Translation zum Tragen kommt, bei dem offenbar irgendwie etwas Informationen verloren gehen. (Vielleicht koennte man sowas sogar als Bug betrachten?) Anschliessend praesentierte sich mir auch eine Situation, die in wenigen Minuten zu analysieren, testen und korrigieren ist:

javax.validation.ConstraintViolationException: validation failed for classes [xxx] during update time
for groups [javax.validation.groups.Default, ]
at org.hibernate.cfg.beanvalidation.BeanValidationEventListener.validate(BeanValidationEventListener.java:132)
at org.hibernate.cfg.beanvalidation.BeanValidationEventListener.onPreUpdate(BeanValidationEventListener.java:79)
at org.hibernate.action.EntityUpdateAction.preUpdate(EntityUpdateAction.java:236)
at org.hibernate.action.EntityUpdateAction.execute(EntityUpdateAction.java:87)

Was war also der Grund? Die bei einem Update zu aktualisierende Klasse hatte ein Property, dass gleichzeitig mit @NotNull (für die Validierung) und @Transient (JPA) annotiert war. Bei der ersten Speicherung war mit dem Attribut alles in Ordnung – nur beim Update ging es dann schief.
Vielleicht hilft der grundsätzliche Ansatz dem einen oder anderen bei ähnlich vertrackten Problemen weiter.

PS: Noch ein Tipp: Testfälle sollten auf keinen Fall so geschrieben sein, dass bestimmte Erwartungen bereits enthalten sind. Sonst helfen auch Tests nicht solche Fehler zu finden.

PPS: Wer sich wundert, dass man auf den Webseiten von Springsource keinen Download durchführen kann –  Adblock etc. sollten deaktiviert werden und JavaScript aktiviert, da SpringSource merkwürdige Analytics Integration verwendet. (Ich hatte darüber schonmal geschrieben, offenbar ist das Problem noch/wieder da.)