# JPA: Wie mit UniqueConstraintViolations umgehen?!



## tuxedo (14. Jan 2011)

Hallo zusammen,

hab mal (wieder) ne JPA Frage:

Wenn ich Entities habe, die nach einem gewissen Muster "Unique" sein müssen, wie handhabe ich da am besten den Versuch doppelte Einträge anzulegen? Klar, da kommt eine Exception. Doch die ist vom Typ 
	
	
	
	





```
RollbackException
```
 und sagt leider in erster Linie erstmal nix über die Ursache aus. Erst wenn man die darunterliegenden Exceptions sich anschaut entdeckt man: Oh, geht ja gar nicht da es den Eintrag schon gibt.
Parallel dazu spuckt Eclipselink und Hibernate noch ein paar Warnungen aus die sich im Log nicht wirklich gut machen. Die Loglevels entsprechend zu setzen und die Meldungen abzuklemmen ist auch unschön.

Und je nach verwendeter JPA Implementierung (EclipseLink, Hibernate, ...) sehen die darunterliegenden Exceptions anders aus. 

Wie mach ich das dann also wenn ich nicht davon ausgehen kann dass eine spezielle JPA Implementierung verwendet wird und so nur die 
	
	
	
	





```
RollbackException
```
zur Hilfe heranziehen kann, die mir aber nicht verrät WAS schief gelaufen ist?

Mein Aktueller Ansatz: VOR dem commit eine Abfrage starten und schauen ob es diesen Eintrag schon gibt. Das verhindert die nichtssagende Exception und die Warn-/Error-Meldungen der JPA Implementierungen aus den untiefen der Implementierung selbst.

Aber den Ansatz finde ich etwas "unschön" und auch recht aufwendig (da ich ja immer erst eine Abfrage starten muss). 

Gibts hier ein Pattern oder eine "empfohlene Vorgehensweise"? Wie geht ihr mit dem "Problem" um?

- Alex


----------



## tuxedo (14. Jan 2011)

Schon 27 Hits und keiner kann was dazu beitragen ;(


----------



## maki (14. Jan 2011)

Ist ein klassiches Problem, dein Vorschlag ist leider keine Lösung für dieses Problem (ausser man sperrt die Tabelle komplett, nicht praktikabel bzw. immer möglich und meist hässlich, von dem Bottleneck ganz zu schweigen) .
Was würde denn passieren wenn zwischen der Abfrage und dem Einfügen ein anderer User genau so einen Datensatz speichert?

Finde da ist man besser dran wenn man JPA-Implementor spezifischen Code nutzt, für jede JPA Implementierung nach der konkreten Exception suchen.


----------



## tuxedo (14. Jan 2011)

Ja, das mit der Abfrage vor dem persistieren: Da hast du natürlich recht. 

Aber die Exception auswerten ist wirklich unschön, da man sich fest an die Implementierung bindet.

Was ich nicht verstehen kann: Das Problem gibts mit sicherheit schon lange. Warum konnte man das mit JPA2.0 nicht "verbessern" ???:L

Ist doch zum k$&"§%tzen ;(

- Alex


----------



## tuxedo (14. Jan 2011)

Hmm, hab mal ein wenig gegoogelt und meine eBooks befragt.

Resultat: 

Die eBooks "Pro EJB 3 - Java Persistence API" und "Pro JPA 2: Mastering the Java Persistence API" lassen sich über das Thema nicht aus. Konnte zumindest nix dergleichen in den Büchern finden.

Google hat ergeben, dass es eine EntityExistsException gibt (EntityExistsException (Java EE 5 SDK)) gibt. Und javaDoc sagt hierzu: 



> Thrown by the persistence provider when EntityManager.persist(Object) is called and the entity already exists. The current transaction, if one is active, will be marked for rollback.



Super. Das wäre des Rätsels lösung. Nur dummerweise wird die nicht geworfen. 
Ich lege eine EntityInstanz mit den selben Werten an, wie schon ein Objekt in der DB existiert. 

Wenn ich dann 
	
	
	
	





```
em.persist(myObject)
```
 aufrufe, dann kommt keine Exception. Das Objekt wird erzeugt und bekommt eine ID (eine sehr hohe. Die DB ist noch jungfräulich und hat erst 2 Einträge. Dennoch wird das Objekt mit irgendwas um 32768 als ID erzeugt).

Erst wenn ich den 
	
	
	
	





```
persist()
```
 Aufruf mit einer Transaction "umgebe", wirft mit der 
	
	
	
	





```
commit()
```
 aufruf die RollbackException...

Hat jemand nen Plan ob das

a) an Hibernate liegt
b) meine Entity noch irgendwas anderes mitbringen muss, dass schon beim 
	
	
	
	





```
persist()
```
 Aufruf die Exception auftritt?

Gruß
Alex


----------



## maki (14. Jan 2011)

> Super. Das wäre des Rätsels lösung. Nur dummerweise wird die nicht geworfen.
> Ich lege eine EntityInstanz mit den selben Werten an, wie schon ein Objekt in der DB existiert.


Das Problem hierbei ist, dass sich dass nur auf equals bezieht, nicht auf irgendwelche UniqueContraints und deren Violations die nicht Teil des Schlüssels/equals sind, dass dürfte auch eine Möglichkeit sein.


----------



## tuxedo (14. Jan 2011)

Ah, das könnte sein. Probier ich gleich aus. Danke für den Tipp.

[update]
Scheint nicht zu gehen. 
	
	
	
	





```
equals()
```
 der Entities wird erst gar nicht aufgerufen :-(


----------



## tuxedo (14. Jan 2011)

*Mist* Hab jetzt in nem Mailarchiv gelesen, dass de EntityExistsException dann von persist() geworfen wird, wenn die Identität (das ID Feld) der Entity die gleiche ist, wie die eines bereits bekannten Objekts gleicher Art im PersistenceKontext. Beispiel:

Ich persistiere ein Objekt und merke mir das Objekt. Dann mach ich ein Query für das gerade gespeicherte Objekt und erhalte ein weiteres Objekt. Wenn ich das nun versuche zu persistieren, dann sollte die Exception auftreten. So zumindest die beiträge aus dem Mailarchiv. 

Es gibt offenbar keinen wirklich praktikablen weg JPA unabhängig von der Implementierung zu benutzen... Man man man. Ist doch zum kotzen. Dabei ist/wäre das herausfordern der Exception doch gängige Praxis...
Wozu ein offenes API benutzen wo ich mir den Provider raussuchen kann, wenn ich mich dann doch an den Provider und seine Spezialitäten binde? Dann kann ich mir die API Spec auch sparen und jeder kocht sein eigenes Süppchen....

So, genug aufgeregt für heute....

- Alex


----------



## maki (14. Jan 2011)

> *Mist* Hab jetzt in nem Mailarchiv gelesen, dass de EntityExistsException dann von persist() geworfen wird, wenn die Identität (das ID Feld) der Entity die gleiche ist, wie die eines bereits bekannten Objekts gleicher Art im PersistenceKontext.


Da war mein "Tipp" mit equals wohl der Holzweg, keine Absicht 



> Es gibt offenbar keinen wirklich praktikablen weg JPA unabhängig von der Implementierung zu benutzen... Man man man. Ist doch zum kotzen. Dabei ist/wäre das herausfordern der Exception doch gängige Praxis...


Ja, JPA2.0 ist leider nicht der Weisheit letzter Schluss, solche Lücken kommen vor.


----------



## Gelöschtes Mitglied 5909 (15. Jan 2011)

Verwendest du Spring? Wenn ja, hast du den VendorAdapter und einen ExceptionTranslator gesetzt?
Du kannst auch einen eigenen ExceptionTranslator schreiben.

Hast du mal von der RollbackException getCause aufgerufen?

Also bei mir steht mit EclipseLink ganz klar dass ich einen Constraint verletzt habe...


----------



## tuxedo (15. Jan 2011)

Nein, ich verwendet kein Spring.

Und ja, ich hab getCause() schon angeschaut. Aber der "cause" ist keine JPA Exception, sondern eine JPA Provider spezifische Exception. 

Wenn ich das also für Eclipselink umsetze, der Provider darunter aber Hibernate ist (ist ja dank JPA EIGENTLICH modular/austauschbar), dann funktioniert's nicht mehr. 

Und genau da liegt der Hund begraben...

Wenn doch noch jemand nen Tipp hat wie man, ohne Abhängigkeiten zum JPA Provider aufzubauen herausbekommt was der Auslöser der doch sehr allgemeinen RollbackException ist: Nur her damit 

- Alex


----------



## KSG9|sebastian (17. Jan 2011)

Ja, das ist eines der Probleme von JPA (2.0). 

Problem ist das der Provider auch nicht immer die genaue Fehlerursache kennt. Oft kommt nur eine nichtssagende SQLException. Der JPA-Provider müsste dafür auch irgendeine Art von Mapping anbieten.

Der praktikabelste Weg ist wohl analog zu Spring eine Art ExceptionTranslator zu bauen....


----------



## tuxedo (17. Jan 2011)

KSG9|sebastian hat gesagt.:


> Ja, das ist eines der Probleme von JPA (2.0).
> 
> Problem ist das der Provider auch nicht immer die genaue Fehlerursache kennt. Oft kommt nur eine nichtssagende SQLException. Der JPA-Provider müsste dafür auch irgendeine Art von Mapping anbieten.



Ja, ganz einfach ist das nicht. Aber wenn sich jede Schicht (DB, JDBC, JPA Provider, ...) auf den Lohrbeeren ausruht und nix dagegen tut, wird's nicht besser. Und damit's besser wird, müssten mehrere Schichten das ganze mal gerade ziehen.



> Der praktikabelste Weg ist wohl analog zu Spring eine Art ExceptionTranslator zu bauen....


Ja, entweder das (und damit nen weiteren Dirty-Workaround schaffen), oder es in kauf nehmen dass man die ganze persistierung synchronisieren muss. Also vorher selbst abfragen ob das Objekt schon extistiert. Wobei man das in JPA ja ebenso implementieren könnte und somit das ganze ne Schicht tiefer legt und ggf. auch performanter gestaltet. 

Naja. Werde das "Problem" wohl mit einem allgemeinen "Exception Translator" lösen. Am besten mit einem den man konfigurieren kann. 

Gruß
Alex


----------



## KSG9|sebastian (18. Jan 2011)

tuxedo hat gesagt.:


> ... Ja, entweder das (und damit nen weiteren Dirty-Workaround schaffen), oder es in kauf nehmen dass man die ganze persistierung synchronisieren muss. Also vorher selbst abfragen ob das Objekt schon extistiert. Wobei man das in JPA ja ebenso implementieren könnte und somit das ganze ne Schicht tiefer legt und ggf. auch performanter gestaltet.



Das wird nicht funktionieren. Bei Multi-User-Betrieb und vielleicht sogar mehreren Servern kannst du das nicht sicherstellen da zwischen der Abfrage und der Persistierung Zeit vergeht. Da müsstest du die ganze Tabelle sperren was nicht unbedingt zu raten ist.


----------



## tuxedo (18. Jan 2011)

KSG9|sebastian hat gesagt.:


> Das wird nicht funktionieren. Bei Multi-User-Betrieb und vielleicht sogar mehreren Servern kannst du das nicht sicherstellen da zwischen der Abfrage und der Persistierung Zeit vergeht. Da müsstest du die ganze Tabelle sperren was nicht unbedingt zu raten ist.



Auch bei mehreren Servern ließe sich synchronisation betreiben. Ist halt aufwendig und mit diversen gravierenden Nachteilen behaftet.

Aber wie ich schon schrieb: Ich geh den Spring-Weg und werte die Exception und die darunterliegenden Exceptions aus. In einem ersten Test funktioniert das recht gut. Werde das ganze aber ein wenig weiter ausbauen und konfigurierbar machen, statt alles hardcodiert in den Quelltext zu legen. Dann lassen sich auch Exceptions von DB Exoten recht einfach handhaben.

- Alex


----------



## KSG9|sebastian (18. Jan 2011)

Wie willst du denn über mehrere Server hinweg synchronisieren? Zudem müsstest du nicht zur den AppServer sondern auch noch die Datenbank/Verbindung mit in die Synchronisation aufnehmen...das wird lustig


----------



## tuxedo (18. Jan 2011)

Ich hab nicht gesagt dass es einfach ist ;-) Und ich hab nicht gesagt dass ich's so implementiere. Aber machbar (mit einigem Aufwand) ist es.

- Alex


----------

