# ' maskieren in SQL-Statement?



## Verjigorm (11. Sep 2008)

Hallo,

ich lese Daten von einer Datenbank ein und schreibe sie in eine andere.

Beim Einlesen der Daten habe ich auch Kommentare, die ein ' enthalten.
Wenn ich nun damit z.b. ein SQL-Insert bilde, kracht es, weil ich versuche ja dann einen Wert einzufügen, der so aussieht:


```
...'Wiesbaden Ost - N'lahnstein'...
```

Ok, das ' maskieren dachte ich mir, aber es geht weder


```
...'Wiesbaden Ost - N\'lahnstein'...
```
noch


```
...'Wiesbaden Ost - N\\'lahnstein'...
```

(ohne ' gehts durch, es liegt also genau an dieser Stelle)

Steh wohl grad aufm Schlauch *kopfkratz*

EDIT: Und ja, PreparedStatement benutze ich auch

mfg Verjigorm


----------



## musiKk (11. Sep 2008)

Manche (viele? alle?) Datenbanken maskieren das wohl mit einem doppelten Apostrophen. Für sowas solltest du aber vielleicht ein PreparedStatement mit Platzhalter verwenden, da wird die Maskierung abgenommen.

_edit:_ Nagut, dann vergiss den zweiten Satz.

Nochmal ein Nachtrag: Welche Datenbank wird denn verwendet? Ich habe hier lokal gerade nur Derby aber damit klappt das wunderbar.


----------



## ARadauer (11. Sep 2008)

Verjigorm hat gesagt.:
			
		

> Hallo,
> EDIT: Und ja, PreparedStatement benutze ich auch



zeig mal bitte deinen code,


----------



## Verjigorm (12. Sep 2008)

Wird in Access geschrieben


```
public void insert(String sql) 
	{
		PreparedStatement stmt = null;
		try {
			stmt = connection.prepareStatement(sql);
			
			try {
				stmt.execute();
				
			}
			catch (SQLException e) 
			{
				if(e.getMessage().contains("General error"))
				{
					main.logDebug("EquipmentNr bereits vorhanden: " + e.getMessage() + ": " + sql);
				}
				else
				{
					main.logDebug(e.getMessage() + ": " + sql);
					e.printStackTrace();
				}
			}

		} catch (SQLException e) 
		{
			main.logDebug(e.getMessage() + ": " + sql);
			e.printStackTrace();
		}
		finally
		{
			if(stmt != null)
				try {stmt.close(); stmt = null;} catch (SQLException e) {e.printStackTrace();}
		}
	}
```


----------



## The_S (12. Sep 2008)

Ein PreparedStatement nützt dir in der Hinsicht gar nichts, wenn die Variablen nicht über die entsprechenden Setter (setString, setInt, ...) setzt.


----------



## musiKk (12. Sep 2008)

So verwendet man ein PreparedStatement nicht. Da, wo die Daten reinkommen, schreibst du im SQL-Code nur ein Fragezeichen und über die diversen set... (z. B. setString() wäre für dich relevant)-Methoden weist du diesen Platzhaltern dann konkrete Werte zu. Dabei wird die Maskierung übernommen.


----------



## maki (12. Sep 2008)

Jetzt müsstest du uns noch den sql String dazu geben 

Abgesehen davon sind geschachtelte try/ctach Blöcke welche beide dieselbe Exception werfen und fangen sinnfrei, was bitte ist den main.logDebug() ??? Wozu einen Stacktrace auf sysout printen wenn man logging verwendet?
Wieso erst durch eine Exception erfahren dass der Datensatz schon angelegt war?

Fragen über fragen...


----------



## ARadauer (12. Sep 2008)

stimmt poste mal deinen sql string...

es ist wichtig, dass darüber gesprochen wird. die richtige verwendung von prepared statements, ist essentiel für sicherheit (injections) und performance...


----------



## musiKk (12. Sep 2008)

Nunja. An der direkten Abfolge von

```
stmt = connection.prepareStatement(sql);
```
und

```
stmt.execute();
```
sieht man schon, dass da nix mit Maskierung passieren kann.

Was nicht heißen soll, dass der SQL-String nicht interessant sein könnte.


----------



## maki (12. Sep 2008)

auch wieder richtig musiKk...


----------



## Verjigorm (12. Sep 2008)

Genau deswegen frag ich ja, weil ich nicht weiss, wie es besser geht.

Ausserdem ist der Code weit entfernt vom Status "fertig", das ist die Pre-Alpha-Version 

Insert:

```
"INSERT INTO B4SKZ VALUES('3507', 'Wiesbaden Ost - N'lahnstein', '36.245', '42.736', '6.49', '312041',
 'Mainz','310623', 'Mainz', '310367', 'Frankfurt/', 'F-', 'Ffm+Saarbr.', '06', 'Hessen', '', '', '01', '01', '01', '01', '03', '02', '08', 
'01', '07', '01', '24', '', '2e', 'D4', 'VG1', '26 V-Netz G  120 kmh', 'KSW4MI', '16M', '16FM', 'FM1', 'KSW4MI Rh.-Main/Spessart',
 '35071036.245')
```


----------



## musiKk (12. Sep 2008)

Hammerst. Zunächst mal: Sind das wirklich alles Strings? Weil da sind ja auch viele Zahlen dabei.

Zum Maskieren: Schau mal in der API-Dokumentation bei PreparedStatement, da ist ein kleines Beispiel gegeben. Das läuft dann auf etwas hinaus wie

```
stmt = connection.prepareStatement("INSERT INTO B4SZK VALUES(?, ?, ?, ..........
stmt.setString(1, ...
stmt.setString(2, ...
stmt.setString(3, ...
...
```
Dabei kümmerst du dich nicht um die Apostrophen, das übernimmt der Datenbank-Treiber.


----------



## ARadauer (12. Sep 2008)

Verjigorm hat gesagt.:
			
		

> Genau deswegen frag ich ja, weil ich nicht weiss, wie es besser geht.
> 
> Ausserdem ist der Code weit entfernt vom Status "fertig", das ist die Pre-Alpha-Version
> 
> ...



ich hoffe nicht, dass dieser string aus eingaben vom benutzer zusammen gebaut wird...
http://de.wikipedia.org/wiki/SQL_Injection


----------



## Michael... (12. Sep 2008)

in Oracle wird einem ' maskiert. Also müsste das so ausschauen:
'Wiesbaden Ost - N''lahnstein'


----------



## Verjigorm (12. Sep 2008)

Im Endeffekt schon, das sind Inhalte einer anderen datenbank, die halt wohl irgendwann mal von Hand gefüllt wurden.



			
				ARadauer hat gesagt.:
			
		

> Verjigorm hat gesagt.:
> 
> 
> 
> ...


----------



## maki (12. Sep 2008)

>> Im Endeffekt schon, das sind Inhalte einer anderen datenbank, die halt wohl irgendwann mal von Hand gefüllt wurden. 

Solange du verstanden hast dass du deine PreparedStatements falsch einsetzt - nämlich so als ob es gar keine sind - und verstanden hast dass du sie richtig einsetzen musst um deren Vorteile zu geniessen, ist alles in Ordnung 

@Michael...
Themaverfehlung


----------



## Michael... (12. Sep 2008)

??? hab doch nur auf die Frage im Titel geantwortet :-(


----------



## SlaterB (12. Sep 2008)

aber die Hauptmessage der anden Antworten ist ja, dass diese Frage gar nicht zu stellen ist,
sondern dass man das unbedingt dem PreparedStatement überlassen soll


----------



## maki (12. Sep 2008)

>> ??? hab doch nur auf die Frage im Titel geantwortet :-(

Schon klar, haben wir ihm aber (hoffentlich) ausgeredet , da er dieses Problem (und viele andere, siehe SQL Injection) gar nicht hätte wenn er die PreparedStatements richtig nutzen würde.


----------



## Verjigorm (12. Sep 2008)

also ich hab mir jetzt mal
http://java.sun.com/docs/books/tutorial/jdbc/basics/prepared.html
durchgelesen.

Da steht dann 





> Supplying Values for PreparedStatement Parameters
> 
> You need to supply values to be used in place of the question mark placeholders (if there are any) before you can execute a PreparedStatement object.



Placeholders ... da steht schonmal nicht, welche das sind.
Erst im beispiel unten wird klarer:
Es handelt sich wohl um die ? (Fragezeichen)

Nun kann ich aber keine Fragezeichen als Placeholder benutzen, weil die bereits auch im Text benutzt werden.

2. Frage die ich mir stelle:
Ich hab jetzt mal in das obige Beispiel ein Fragezeichen eingefügt,
wenn ich dann 
	
	
	
	





```
stmt.setString(1, "ersatz");
```
 mache, kommt ne NullpointerException obwohl stmt != null ist

3. Verstehe ich nicht, wo ich da etwas gewinne?


----------



## SlaterB (12. Sep 2008)

> Nun kann ich aber keine Fragezeichen als Placeholder benutzen, weil die bereits auch im Text benutzt werden. 

dieser Satz ist nicht verständlich, 
in einem PreparedStatement kann man ? benutzen,
nicht mehr und nicht weniger gilt,

was ist nun deine Frage/ Problem?

--------

> kommt ne NullpointerException obwohl stmt != null ist

tja, 
'ich verwende Code und es kommt eine Exception'
da wird jeder eine Antwort wissen 

Code posten?..


----------



## The_S (12. Sep 2008)

1.) Vermutlich werden die ? in deinem PreparedStatement im Text vorkommen oder? Von daher verwendest du in deinem SQL keine Placeholders, sondern nur in den Parametern. Das ist völlig ohne Probleme!

2.) stmt muss aber null sein, sonst kann es bei diesem Code keine NPE geben!

3.) Du gewinnst, indem du dich nicht mehr ums Escapen kümmern musst.


----------



## Verjigorm (12. Sep 2008)

Ok, das ist imho eins der schlechtesten Tuts, die ich von Sun je gelesen habe, da grade bei INSERTS nicht klar ist, wo das Fragezeichen hinkommt.

ich dachte ich kann sowas machen:

```
stmt = con.prepareStatement("INSERT INTO B4SKZ VALUES('3507', 'Wiesbaden Ost - N?lahnstein', '36.245', ....");
stmt.setString(1, "'");
```

aber es muss so lauten:


```
stmt = con.prepareStatement("INSERT INTO B4SKZ VALUES('3507', ?, '36.245', ....");
stmt.setString(1, "Wiesbaden Ost - N'lahnstein");
```


----------



## The_S (12. Sep 2008)

Joa, und das solltest du eigentlich auch mit allen Parametern so machen!


----------



## Verjigorm (12. Sep 2008)

Hobbit_Im_Blutrausch hat gesagt.:
			
		

> Joa, und das solltest du eigentlich auch mit allen Parametern so machen!



ahso  :shock:


----------



## ARadauer (12. Sep 2008)

```
stmt = con.prepareStatement("INSERT INTO B4SKZ VALUES(?,?,?)"); 
   stmt.setString(1, 3507);
   stmt.setString(2 "Wiesbaden Ost - N?lahnstein");
   stmt.setFloat(3, 6.245);
```
so würde man das machen...

was hast du davon? performance gewinn.. vielleicht merkt mans bei einem nicht, aber bei ein paar updates

zb 

```
PreparedStatement updateSales;
String updateString = "update COFFEES " +
                      "set SALES = ? where COF_NAME like ?";
updateSales = con.prepareStatement(updateString);
int [] salesForWeek = {175, 150, 60, 155, 90};
String [] coffees = {"Colombian", "French_Roast", "Espresso",
                     "Colombian_Decaf", "French_Roast_Decaf"};
int len = coffees.length;
for(int i = 0; i < len; i++) {
                updateSales.setInt(1, salesForWeek[i]);
                updateSales.setString(2, coffees[i]);
                updateSales.executeUpdate();
        }
```
wird man merken

und du musst dich nicht ums escapen kümmern, was dir einige probleme erstpart und die ganze sache sicherer macht (sql injections..)


----------



## Verjigorm (12. Sep 2008)

Also ich bau dann ne "Grundstruktur"


```
stmt = con.prepareStatement("INSERT INTO ? VALUES(?, ?, ?, ....., ?)";
```

und nutze dann für sämtliche Parameter die setXXX-Funktionen?


edit: Danke ARadauer war etwas schneller =)


----------



## maki (12. Sep 2008)

INSERT INTO ?

ist quatsch, den Tabellennamen will man ja nicht über parameter steuern, ist ziemlich sinnfrei...


----------



## SlaterB (12. Sep 2008)

Tabellennamen kann man nicht unbedingt parametrisieren,
sollten aber auch keine Benutzereingabe sein und eh keine Sonderzeichen enthalten

da auch von maki schon geschrieben wenigstens noch was lustiges:
stmt = con.prepareStatement("? ? ? ?(?, ?, ?, ....., ?)";


----------



## Verjigorm (12. Sep 2008)

wieso nicht?

Ich exportiere ja xx Tabelleninhalte von A nach B, irgendwo muss ich ja den Tabellennamen angeben


----------



## maki (12. Sep 2008)

Naja, die "echten" Parameter sollten per setXXX gesetzt werden.

Ändern sich die Tabellennamen in B?
Wenn ja, musst du deinen SQL String immer noch zusammenbauen, ala 

String sql = "INSERT INTO " +tablename+ " VALUES(?, ?, ?, ?)";

Aber wenn du Tabelleninhalte nur kopierst, wozu dann überhaupt Java???
Das geht doch pro Tabelle mit nurr einem SQL Statement...


----------



## Verjigorm (12. Sep 2008)

maki hat gesagt.:
			
		

> Ändern sich die Tabellennamen in B?



Ja



			
				maki hat gesagt.:
			
		

> String sql = "INSERT INTO " +tablename+ " VALUES(?, ?, ?, ?)";



Jo, so siehts jetzt auch aus



			
				maki hat gesagt.:
			
		

> Aber wenn du Tabelleninhalte nur kopierst, wozu dann überhaupt Java???
> Das geht doch pro Tabelle mit nurr einem SQL Statement...



1. Man kann in Access nicht jede Tabellenstruktur übernehmen.
Access is da ziemlich hartnäckig mit inkompatiblen Parametern

```
CREATE TABLE test (
  doublewert double(15,3) default NULL
)
```
geht z.b. nicht, weil Access nur double kennt, kein double(15,3)
d.h. alle Parameter müssen von hand angepasst werden, jedenfalls hab ich noch nix anderes dazu gefunden.
Eigendlich dachte ich, der ODBC-Treiber nimmt mir genau diese Arbeit ab, macht er aber nicht.

2. 
Jeder Datensatz durchläuft eine Plausibilitätsprüfung und wird dann entweder als Insert oder Update behandelt oder weggeschmissen ^^

Die alte Superbase-Tabelle kannte z.B. keine Primary/ForeignKeys oder sonstwas und das ändert sich nun.


----------



## maki (12. Sep 2008)

Ach stimmt, dass war ja die "Konvertiere Access DBs mit Java zu anderen DBs" Geschichte... übrigens ist JDBC ziemlich low-level, da lohnt sich der Einsatz von Frameworks, zB. wäre iBatis in so einem Fall ideal imho, aber dafür ist es wohl zu spät.


----------



## robertpic71 (12. Sep 2008)

Zum variablen Tabellennamen: Insofern nicht sinnvoll, da mit einer anderen Tabelle wahrscheinlich auch eine andere Feldanzahl=Parameteranzahl=Fragezeichen dahinter erwünscht sind.

Die Vorteile nocheinmal zusammengefasst: 
* Schutz vor SQL-Injection
* kein Escapen
* kein Komma oder Punkt Problem bei numerischen Werten (Spracheinstellungen Server vs. Client)
* kein Datumsformatproblem (jjjj-mm-tt oder tt... wieder Spracheinstellungen), einfach Date als Parameter
* Performancevorteil (es muss nur der Befehl geparst werden nicht die Parameter, u.U. Wiederverwendung vom Statement)

Nachteile:
Bei vielen Feldern wird die Fragezeichenflut etwas unübersichtlich. Wenn man kein Persitenzframework nehmen will oder kann (Access? ODBC-Bridge?), bieteten sich z.B. die Spring-JDBC-Templates an, welche NamedParameters unterstützen.

also z.B.


```
"update t_actor set first_name = :firstName, last_name = :lastName where id = :id" ..
..
Map<String, Object> parameters = new HashMap<String, Object>(3);
parameters.put("id", actor.getId());
parameters.put("first_name", actor.getFirstName());
parameters.put("last_name", actor.getLastName());
```

/Robert


----------



## Verjigorm (12. Sep 2008)

Nee, migriere Superbase in Access


----------



## Verjigorm (12. Sep 2008)

Noch ne Frage:

Kann ich mir den endgültigen Query-String irgendwie ausgeben lassen?
Also nachdem alle setXXX-Methoden ausgeführt wurden?
Finde da irgendwie keine Funktion für


----------



## The_S (12. Sep 2008)

nein


----------



## maki (12. Sep 2008)

Wenn du log4j nutzt versuche doch mal

log4j.logger.java.sql=debug


----------



## Verjigorm (12. Sep 2008)

Hobbit_Im_Blutrausch hat gesagt.:
			
		

> nein



das ist schlecht, weil der muss mitgeloggt werden


----------



## SlaterB (12. Sep 2008)

an so einer Stelle ist es immer wieder schön, dass man ja eine Programmiersprache bei der Hand hat,

blitzschnell ist da PreparedStatement erstetzt durch MyBetterStatement, 
welches QueryString + Parameter annimmt und sowohl ein PreparedStatement zusammenbaut als auch einen String fürs Logging

da kann man dann auch gleich wieder einen Konstruktor oder eine Factory-Methode

```
createInsertStatement(tabellenname,"3507", stadtName, ..);
```
basteln und muss sich überhaupt nicht mehr um ? usw kümmern, 
bei verschiedenen Parametertypen (nicht nur String) wirds generisch etwas unschön, aber machbar

Vorteil bei sowas ist auch, dass man eine abhängige Sache wie PreparedStatement mit seinen ? nicht im ganzen Programm verteilt,
wechselt man zu einer anderen Library wie Hibernate, kann man dann die entscheidenen Teile komfortabel an nur einer Stelle umbauen


----------



## Verjigorm (12. Sep 2008)

würde es eine vorgefertigte Methode geben, die einem das komplette Statement zurückgibt, wär alles ok.

Wieso gibts das nicht?
das ist doch absolutes must-have, zu sehen was fürn Query ausgeführt wird


----------



## SlaterB (12. Sep 2008)

exakt kann man das vielleicht gar nicht machen, weil der Parameter einzeln an die DB übergeben und erst dort maskiert wird usw (Oracle '', MySQL \'),

allzuviel Verständnis dafür habe ich aber auch nicht, 
zumindest eine Vorschau mit direkt eingesetzen Parametern in der unescapten Java-Schreibweise wäre leicht möglich 
(so wie man es in der eigenen Logging-Klasse dann machen wird)


ist aber Standard, selbst bei Hibernate QueryLog werden Parameter nicht eingesetzt 
(edit: evtl. gibts dafür doch ne Konfigurationsmöglichkeit, erinnere mich schwach an soetwas in der Richtung, für JDBC aber uninteressant)


----------



## maki (12. Sep 2008)

Verjigorm hat gesagt.:
			
		

> würde es eine vorgefertigte Methode geben, die einem das komplette Statement zurückgibt, wär alles ok.
> 
> Wieso gibts das nicht?
> das ist doch absolutes must-have, zu sehen was fürn Query ausgeführt wird


Hast du meinen Post gelsen?


----------



## Verjigorm (12. Sep 2008)

jo, aber ich kenn mich mit log4j nicht aus.

ich benutz nur das "Simple Logging" welches hier: http://www.torsten-horn.de/techdocs/java-log4j.htm#Simple-Logging vorgeschlagen wird.


----------



## maki (12. Sep 2008)

Dann mach es doch richtig 

"Logging mit Properties-Konfigurationsdatei" steht da auch drinn, bin kein Fan von der Seite.... echt dämlich Leuten ernsthaft vorzuschlagen log4j mit einer Klasse anstatt der Datei zu konfigurieren.


----------



## Verjigorm (12. Sep 2008)

Das ich für dieses "Simple-Logging" keine lib brauch, sondern es mit nem normalen Filewriter auch machen könnte, hab ich schon gemerkt


----------



## maki (12. Sep 2008)

Verjigorm hat gesagt.:
			
		

> Das ich für dieses "Simple-Logging" keine lib brauch, sondern es mit nem normalen Filewriter auch machen könnte, hab ich schon gemerkt


Ein FileWriter ist kein echtes Logging, nur eine andere Version von System.out

Von allen Logging Frameworks bietet log4j am meisten, zumindest wenn man es mit der Java Logging API und dem commons-logging vergleicht.


----------



## Verjigorm (12. Sep 2008)

jo, ich wollte damit nur unterstreichen, dass dieses "Simple-Logging" kein echtes Logging ist


----------

