# PreparedStatement und null



## Camino (30. Apr 2012)

Hallo,

ich dachte schon, ich hätte mir eine schöne Lösung geschrieben, mit der ich über eine Methode PreparedStatements ausführen kann. Diese Methode habe ich in einer DBHandler-Klasse, auf die ich aus anderen Klassen zugreife und die Parameter übergebe. Aber jetzt ist ein Problem aufgetreten, weil einer der Werte/Objekte null ist.

Und zwar hab ich folgende Methode:

```
...
	protected void executeSQL2( String sql, Object[] values ) {
		
		PreparedStatement st = null;
		
		int z = 1;
		
		// DB-Verbindung holen
		Connection conn = DBConnection.getInstance();
						
		try {
			st = conn.prepareStatement( sql );
			
			for( Object obj : values ) {
			
				if ( Integer.class.isInstance(obj) ) {
					st.setInt( z, (Integer)obj );
				}
				
				if ( Double.class.isInstance(obj) ) {
					st.setDouble( z, (Double)obj );
				}
					
				if ( String.class.isInstance(obj) ) {
					st.setString( z, (String)obj );
				}
				
				if ( Boolean.class.isInstance(obj) ) {
					st.setBoolean( z, (Boolean)obj );
				}
				
				if ( BigDecimal.class.isInstance(obj) ) {
					st.setBigDecimal( z, (BigDecimal)obj );
				}
								
				if ( GregorianCalendar.class.isInstance(obj) ) {
					st.setTimestamp( z, new Timestamp( ((GregorianCalendar)obj).getTimeInMillis() ) );
				}
				
				if ( Date.class.isInstance(obj) ) {
					st.setDate( z, (Date)obj );
				}
				
				System.out.print( obj + "  " );
				
				z++;
			}

			st.executeUpdate();
			st.close();
		} catch ( SQLException e ) {
			e.printStackTrace();
		}
...
```

Das heisst, ich übergebe dieser Methode den SQL-String für das PreparedStatement und ein Object[ ] values mit den Werten für das PreparedStatement.
Der SQL-String sieht etwa so aus:

```
String sql = "INSERT INTO person VALUES " + 
		   "(DEFAULT, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, DEFAULT, " +
		   "?, DEFAULT, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
```
In der Methode gehe ich dann durch den Object-Array durch und schaue, welcher Typ das Object ist und setze es dementsprechend in das PreparedStatement.
Ist der Wert aber null, gibt es ein Problem, weil ja kein Objekt-Typ bekannt ist. Es gibt zwar die Möglichkeit mit setNull(), aber dafür bräuchte ich ja auch die Typangabe.

Jetzt weiss ich leider gerade nicht mehr weiter. Gibt es die Möglichkeit, diese Methode weiterhin zu nutzen und auch einen null-Wert irgendwie abzufangen? War halt ziemlich praktisch, weil ich diese Methode so für mehrere unterschiedliche Fälle universell einsetzen konnte. Oder muss ich dass zusammensetzen des PreparedStatements für jeden Fall, den ich habe, separat machen?

Ich hoffe, ich hab mein Problem einigermassen verständlich beschrieben. Vielleicht habt ihr ja Tipps, wie ihr das macht.


----------



## turtle (30. Apr 2012)

Ich schlage vor, entweder ein bereits ausgereiftes Framework, nämlich myBATIS zu nutzen, oder sich anzuschauen (Open source), wie dort mit nullable columns umgegangen wird.


----------



## irgendjemand (1. Mai 2012)

@turtle
ich denke ein großes framework *wobei ich hier wenn überhaupt SPRING empfehlen würde* wäre hier overkill ... diese aufgabe sollte jeder driver selbst erledigen können ... siehe unten

@TO
warum machst du dir eigentlich selbst die mühe die dir der treiber abnimmt ?


```
for(int i=0; i<values.length; i++)
{
	st.setObject(i+1, values[i]);
}
```

und den rest sollte dann eigentlich der driver machen ... da brauchst du dich gar nicht selbst drum kümmern ... denn [c]PreparedStatement.setObject(int, Object)[/code] ist ja im interface vorgeschrieben ... warum also nicht nutzen ?


----------



## Camino (1. Mai 2012)

irgendjemand hat gesagt.:


> warum machst du dir eigentlich selbst die mühe die dir der treiber abnimmt ?
> 
> 
> ```
> ...



Grrrr, du hast recht, das sieht natürlich viel besser aus. Am Anfang hatte ich das alles noch mit "normalen" Statements gemacht, und dann auf PreparedStatements umgeschrieben. Dann war ich froh, diese Lösung mit der Methode und der Unterscheidung der Objekttypen gemacht zu haben. Aber so ist es natürlich perfekt. Muss ich gleich mal testen, ob der Treiber das auch so umsetzt. Und vor allem auch, wenn der Wert dann null ist. Danke für diesen Tipp...


----------



## Camino (1. Mai 2012)

turtle hat gesagt.:


> Ich schlage vor, entweder ein bereits ausgereiftes Framework, nämlich myBATIS zu nutzen, oder sich anzuschauen (Open source), wie dort mit nullable columns umgegangen wird.



Danke für den Tipp. Ich möchte mich eigentlich erstmal direkt mit JDBC rumschlagen, bevor ich mich an andere externe Sachen ranwage. Aber vielleicht schau ich mir das ja mal an, wie das dort gemacht wird. Danke.


----------



## Camino (1. Mai 2012)

irgendjemand hat gesagt.:


> ```
> for(int i=0; i<values.length; i++)
> {
> st.setObject(i+1, values[i]);
> ...



OK prima, hab das gerade mal getestet und es sieht gut aus. Auch der null-Wert scheint richtig in die DB gewandert zu sein. Danke nochmal...


----------



## irgendjemand (1. Mai 2012)

wie gesagt : es steht zwar auch in der doc das man diese methode nicht verwenden sollte ... und es daher einem entwickler eines drivers zum gewissen grad frei gestellt ist ob und wie er diese methode implementiert ...
aber da diese im interface nun mal vorgegeben ist kann man immer mal ruhig versuchen diese auch zu nutzen ...


----------



## Camino (2. Mai 2012)

Hmm, soweit ich das verstanden habe, sollte das nicht verwendet werden, weil es nicht immer gewährleistet ist, ob das bei allen Treibern oder Datenbanken auch einwandfrei funktioniert, und dass bei null-Werten eher setNull() oder setObject() mit Typangabe verwendet werden sollte.

Jetzt weiss ich nicht, was tun. Es funktioniert ja so mit dem setObject(), auch bei null-Werten, und ist natürlich viel kürzer und unkomplizierter. Ansonsten müsste ich mir überlegen, wie ich der Methode auch den Typ bei einem null-Wert mit übergebe.


----------



## irgendjemand (3. Mai 2012)

das meinte ich halt mit "sollte nicht verwendet werden" ...
da ich aber selber noch nie versucht habe "NULL" in eine datenbank zu schreiben und auch nicht weis welche du verwendest kann ich dazu leider nichts sagen ...

MySQL z.b. bietet es an ein feld mit default "NULL" zu belegen ... dann brauchst du nur deinen query entsprechend schreiben und lässt die felder die nach default eh NULL werden und in die du auch NULL schreiben willst einfach aus dem query weg ... den rest macht MySQL selbst ...
wie das allerdings bei anderen , und vor allem embedded , datenbanken aussieht weis ich nicht ... ist aber glaube zum teil in SQL92 oder so spezifiziert


----------



## Camino (3. Mai 2012)

Ich arbeite mit PostgreSQL. Hatte das nicht erwähnt, da ich dachte, das Problem wäre unabhängig davon.

Das Feld war vorher ein VARCHAR NOT NULL UNIQUE. Um das UNIQUE beizubehalten geht es halt nicht, einfach nur einen leeren String reinzuschreiben, nur NULL wird nicht durch UNIQUE mehrfach verhindert. Deshalb musste ich das NOT NULL rausnehmen und NULL-Werte zulassen. Das Problem mit den PreparedStatements und NULL hatte ich vorher halt auch nicht.

Das mit dem DEFAULT NULL wird wohl auch nicht richtig klappen bzw. zu kompliziert, weil ich dann ja das PreparedStatement danach anpassen muss, also anstatt ein ? dann DEFAULT oder NULL schreiben müsste.


----------



## irgendjemand (3. Mai 2012)

nein ...
wenn du ein feld beim anlegen der tabelle "DEFAULT <irgendwas>" angibst ... dann lässt du es einfach komplett aus dem statement raus ... sowohl im ersten teil wo du die einzelnen felder aufzählst
[sql]INSERT INTO irgendwas ('feld1', 'feld2', 'feld4')[/sql]
als auch das entsprechende "?" im VALUES ...

denn DEFAULT steht doch genau dafür das eben beim einfügen vom datenbankserver der default wert gesetzt wird wenn kein anderer wert übergeben wird ... da musst du kein keyword ala "DEFAULT" im statement haben ... einfach das feld komplett weglassen ...


warum UNIQUE ? ... wenn du etwas nur einmal in der datenbank haben willst mache vorher ein SELECT ob es schon vorhanden ist ... denn das was du versuchst wäre missbrauch des exception-handling ... da du bewusst auf eine exception prüfen würdest die der driver liefert wenn eben versucht wird UNIQUE zu "brechen" ... also zwei mal der selbe wert ...
sowas macht man nicht ... der bessere weg wäre vorher mit dem select selbst zu prüfen und in der programm-logik zu verarbeiten ... extra den server darum bemühen und dann noch exception-handling missbrauchen ist der falsche ansatz ...


----------



## Camino (4. Mai 2012)

irgendjemand hat gesagt.:


> nein ...
> wenn du ein feld beim anlegen der tabelle "DEFAULT <irgendwas>" angibst ... dann lässt du es einfach komplett aus dem statement raus ...


Na ja, aber ich brauch ja nicht immer den Default-Wert, manchmal wird ja auch was eingetragen. Dann bräuchte ich ja verschiedene Statements oder dynamisch erstellte. Oder seh ich das gerde irgendwie falsch?



> warum UNIQUE ? ... wenn du etwas nur einmal in der datenbank haben willst mache vorher ein SELECT ob es schon vorhanden ist ... denn das was du versuchst wäre missbrauch des exception-handling ... da du bewusst auf eine exception prüfen würdest die der driver liefert wenn eben versucht wird UNIQUE zu "brechen" ... also zwei mal der selbe wert ...
> sowas macht man nicht ... der bessere weg wäre vorher mit dem select selbst zu prüfen und in der programm-logik zu verarbeiten ... extra den server darum bemühen und dann noch exception-handling missbrauchen ist der falsche ansatz ...


Ich hab natürlich auch im Programm eine Überprüfung dieser Werte und eine Reaktion, wenn der identische Wert schon in der DB steht. Vielleicht hatte ich das UNIQUE nur noch aus Sicherheitsgründen dort stehen, damit auch wirklich keine identischen Werte dort einegtragen werden können.


----------



## irgendjemand (4. Mai 2012)

naja ... statements dynamisch zusammenzukleben mag gehen ... aber es wird doch wohl keine X sonderfälle geben sondern vielleicht nur 3 oder 4 verschiedene ... oder ?
würde hier je nach situation dann mit switch das richtige auswählen ...


hmm ... gut ... aber diese "sicherheit" der datenbank durch UNIQUE kannst du ja nur prüfen wenn du im catch spezielle auf diese exception prüfst das eben ein "UNIQUE-DUPLICATE-ERROR" gefolgen ist .. und das ist nun mal schlechtes design wegen missbrauch des exception-handling ...
heißt : du darfst die datenbank nicht als letztes "rettungsboot" haben ... sondern musst deine software so konzipieren das es schon dort 100% ausgeschlossen wird ... was eben mit einem vorheringen SELECT möglich ist ...

ob man jetzt transactions dazu missbrauchen sollte ... hmm .. würde ich jedenfalls auch nicht machen


----------



## Camino (4. Mai 2012)

OK, danke. Ich werde mir das nochmal durch den Kopf gehen lassen...


----------

