# Hibernate - java.sql.BatchUpdateException - Dublicate Entry



## dwn8 (2. Apr 2009)

Hallo,


Caused by: java.sql.BatchUpdateException: Duplicate entry 'http://www.asdd.de/asdasd' for key 'PRIMARY'

Diese Exception wird geworfen, wenn ich versuche einen Primary Key einzufügen, obwohl dieser bereits vorhanden ist. Bevor ich Hibernate benutzt habe, löste ich dies mit "ignore insert into". Wie geht man bei Hibernate damit am besten um? Exceptions abfangen oder vor dem Eintrag nach einem doppelten Primary Key prüfen oder gibts noch andere Möglichkeiten?

Gruß
dwn8


----------



## tfa (2. Apr 2009)

Am besten lässt man Hibernate bzw. die DB die Primärschlüssel erzeugen.
'http://www.asdd.de/asdasd'  sieht fast so aus, als ob Fachdaten als Primary Key missbraucht werden.


----------



## maki (2. Apr 2009)

Wie wäre es damit nicht zu versuchen einen eindeutigen Schlüssel mehrfach einzufügen?


----------



## dwn8 (2. Apr 2009)

Ich kenne mich Dbs nicht so gut aus. Ich habe eine Url als Primary Key gewählt, da diese nicht doppelt enthalten darf und man damit einen Eintrag identifizieren kann. Nun ist mir bekannt, dass man Primary Key automatisch erzeugen kann (sowas wie eine "long id"). Leider kann ich mit meinem Wissen darin keinen Vorteil sehen. Und wie sollte ich dann einen doppelten Eintrag von einer Url am besten verhindern?


----------



## tfa (2. Apr 2009)

Lege einen Unique-Index in der Tabelle an. Oder lass das besser noch Hibernate für dich erledigen. Ich verwende hierfür die JPA-Annotation @UniqueConstraint.
Schlüssel in Datenbanken dürfen absolut keine fachliche Bedeutung haben.


----------



## dwn8 (2. Apr 2009)

So hab jetzt den Primary Key geändert und einen Constraint eingefügt.


```
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
@Column(insertable=true, updatable=false, nullable=false)
private int id;
@Column(columnDefinition="VarChar(300)", unique=true, insertable=true, updatable=false, nullable=false)
private String url;
```

Jetzt kommt jedoch eine andere Excpetion. Was ich erwartet habe.

```
Exception in thread "main" org.hibernate.exception.ConstraintViolationException: could not insert: [com.honey.datasource.Url]
```

Nun bin ich sozusagen wieder am Anfang. Wie kann ich die Exception am besten verhindern?


----------



## maki (2. Apr 2009)

euqals & hashcode sind überschrieben?


----------



## dwn8 (2. Apr 2009)

Hab sie gerade überschrieben.


```
@Override
	public boolean equals(Object obj) {
		if (obj == null)
			return false;
		
		Url url = (Url) obj;
		return url.equals(url.getUrl());
	}
	
	@Override
	public int hashCode() {
		return url.hashCode();
	}
```

Jedoch hat sich an der Exception nichts geändert.
Um Missverständnisse vorzubäugen. Dieser Eintrag ist bereits in der DB. Was ich jedoch verhindern will, ist die Exception. Beim einem Versuch einen doppelten Eintrag hinzuzufügen, sollte nichts oder vielleicht nur eine Warnung ausgegeben werden.


----------



## tfa (2. Apr 2009)

Da sollte es eine root-cause geben. Ganz unten im Stacktrace. 
Hast du noch irgendwelche anderen abhängigen Klassen gemappt?


----------



## maki (2. Apr 2009)

Die equals von URL sollte niemals(!!!) aufgerufen werden, ist eine Missgeburt: URL (Java 2 Platform SE 5.0))

Brauchst du überhaupt eine URL oder wäre ein einfacher String nicht auch möglich?
Oder ist Url deine eigene Klasse?

Müsstest selbst prüfen ob ein Eintrag mit diesem Wert bereits existiert.


----------



## dwn8 (2. Apr 2009)

```
MySQLIntegrityConstraintViolationException: Duplicate entry
```
Die klasse ist unabhängig.
(siehe oben edit)

Edit: Url ist meine eigene Klasse.


----------



## tfa (2. Apr 2009)

Ja, das wolltest du doch mit dem Index. Vorher prüfen, ob es schon eine solche URL gibt. Oder zur Not die Exception abfangen, was allerdings nicht ganz so sauber ist.


----------



## dwn8 (2. Apr 2009)

Ja ich wollte halt wissen, wie ich die Exception am Besten verhindern kann. Es gibt die Optionen vor der Transaktion in der Db nachzuschauen, ob der Eintrag bereits vorhanden ist oder die Exception abzufangen. Das sind jedenfalls die Optionen, die mir einfallen.
Bei der ersten Option müßte ich ja ne Anfrage dazu basteln.
Jedoch habe ich mich gefragt, ob es vielleicht einen Befehl gibt, der die Exception unterbindet. Sowas wie bei reinem SQL "ignore insert into".


----------



## tfa (2. Apr 2009)

Gibt es nicht. Das solltest du vorher prüfen.



> Sowas wie bei reinem SQL "ignore insert into".


Das ist kein Standard-SQL sondern MySQL-Frickelkram.


----------



## dwn8 (2. Apr 2009)

Noch ne kurze Frage. 
Ich tätige beispielsweise eine Transaktion mit Hilfe meiner Url Klasse. Darin ist ja nun der UniqueContraint defniert. Nun wird ein Versuch unternommen einen eintrag mit den gleichen Werten zu machen. Folglich wird eine Exception geworfen. Gibt es nun vielleicht sowas wie "Aha diese Entity hat nen UniqueContraint" und es wird automatisch danach in der Db gesucht? Falls man sowas machen kann, hast du nen kleines Bsp dazu?


----------



## dwn8 (2. Apr 2009)

Ich habe es jetzt so gelöst.

```
public void save(Url url) {
	Transaction tx = null;
	Session session = SessionFactoryUtil.getInstance().getCurrentSession();
	try {
		tx = session.beginTransaction();
		Criteria crit = session.createCriteria(Url.class).add(Example.create(url));
		crit.setMaxResults(1);
		Url queryObj = (Url) crit.uniqueResult();
	        if(queryObj == null || !url.equals(queryObj)) {
				session.save(url);
	        }
	        tx.commit();
	}
	catch (RuntimeException e) {
		if (tx != null && tx.isActive()) {
			try {tx.rollback();} 
			catch (HibernateException e1) {
				// logger.debug("Error rolling back transaction");
			}
			// throw again the first exception
			throw e;
		}
	}
}
```
Falls noch jemand Anmerkungen oder Ideen hat, immer her damit


----------



## JanHH (3. Apr 2009)

Also ohne das jetzt wirklich zu verstehen liegt wohl ein Unterschied darin, ob man Hibernate als sowas wie "SQL light" benutzt, aber eigentlich eine klassische SQL-Struktur programmiert, oder ob die Applikation wirklich auf diesem ORM-Konzept aufsetzt. Wenn man Hibernate benutzt, sollte natürlich letzteres der Fall sein, und dann dürften solche Probleme eigentlich gar nicht auftreten. Nur mal so als spontanen Gedanken aus dem Bauch heraus.


----------



## GilbertGrape (3. Apr 2009)

Warum fängst du nicht direkt die ConstraintViolationException ab?


----------



## maki (3. Apr 2009)

UniqueContraints lassen sich leider nicht besser prüfen, entweder vorher testen (so wie dwn8 ) oder Exceptions fangen, viel mehr bleibt da nicht.

Nachtrag: Finde das testen vorher besser.


----------



## KSG9|sebastian (3. Apr 2009)

Session#saveOrUpdate() dürfte dein Problem lösen!

Dann wird zuerst ein SELECT auf die Entity gemacht und wenn der Satz vorhanden ist wird ein Update gemacht. Wenn der Update nicht gewünscht ist dann musst du's so lösen wir bisher. Erst selektieren, wenn nicht vorhanden -> insert


----------



## maki (3. Apr 2009)

KSG9|sebastian hat gesagt.:


> Session#saveOrUpdate() dürfte dein Problem lösen!
> 
> Dann wird zuerst ein SELECT auf die Entity gemacht und wenn der Satz vorhanden ist wird ein Update gemacht. Wenn der Update nicht gewünscht ist dann musst du's so lösen wir bisher. Erst selektieren, wenn nicht vorhanden -> insert


Würde es nicht.

Ein neues (transientes) Objekt hat keine ID, würde also über ein insert eingefügt werden, und dann fliegt die ContraintViolation wenn ein eindeutiges Feld einen bereits vergebenen Wert hat.

Wie gesagt, es gibt nicht viele Möglichkeiten damit umzugehen, eignetlich genau 2


----------



## tfa (3. Apr 2009)

KSG9|sebastian hat gesagt.:


> Session#saveOrUpdate() dürfte dein Problem lösen!


Nein. Die Exception fliegt nicht wegen eines doppelten Primärschlüssels. Das ist ein einfacher unique Index.


----------



## KSG9|sebastian (3. Apr 2009)

maki hat gesagt.:


> Würde es nicht.
> 
> Ein neues (transientes) Objekt hat keine ID, würde also über ein insert eingefügt werden, und dann fliegt die ContraintViolation wenn ein eindeutiges Feld einen bereits vergebenen Wert hat.
> 
> Wie gesagt, es gibt nicht viele Möglichkeiten damit umzugehen, eignetlich genau 2



saveOrUpdate ist es herzlich egal ob das Objekt transient ist oder nicht. Sofern der Primärschlüssel nicht von der Datenbank erzeugt wird prüft Hibernate das ganze. JPA reagiert da anderst, Session#persist ebenso.
Bei saveOrUpdate reagiert Hibernate anderst: Es wird geprüft ob der Primärschlüssel des Objekts = unsaved-value (also null). Wenn ja dann wird ein Insert gemacht, wenn nicht ein Select und dann Update/Insert, jenachdem ob vorhanden oder nicht. 
Zumindest reagiert mein Hibernate (3.3.1 GA) so.

@tfa
Sofern das Feld als Primärschlüssel definiert ist (so hab ich das zumindest verstanden) dann fliegt die Exception weil ein Datensatz mit demselben Primärschlüssel schon existiert. Oder hab ich im Lauf der Diskussion etwas falsch verstanden?


----------



## dwn8 (3. Apr 2009)

Primary Key ist nun eine Id, die automatisch generiert wird,

```
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
@Column(insertable=true, updatable=false, nullable=false)
private int id;
```
Das entsprechende Feld ist als Unique definiert.

```
@Column(columnDefinition="VarChar(300)", unique=true, insertable=true, updatable=false, nullable=false)
private String url;
```


----------



## maki (4. Apr 2009)

KSG9|sebastian hat gesagt.:


> saveOrUpdate ist es herzlich egal ob das Objekt transient ist oder nicht.


Eben nicht.
Ein transientes Objekt hat nunmal noch keine Id.
Aber das beschreibst du ja selbst:


KSG9|sebastian hat gesagt.:


> Bei saveOrUpdate reagiert Hibernate anderst: Es wird geprüft ob der Primärschlüssel des Objekts = unsaved-value (also null). Wenn ja dann wird ein Insert gemacht, wenn nicht ein Select und dann Update/Insert, jenachdem ob vorhanden oder nicht.
> Zumindest reagiert mein Hibernate (3.3.1 GA) so.


Deswegen verstehe ich nicht wieso du das nochmals Postest?


----------

