PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : SQL Prep Statements in Java/JDBC


Gast
2009-08-21, 17:44:25
Hi Leute,
bin grade dabei eine Datenbank-Klasse in Java mit JDBC zur Kapselung zu schreiben und habe hierzu eine Frage. Und zwar bin ich mir unschlüssig was die beste Möglichkeit ist, mit PreparedStatements umzugehen da ich zum ersten mal mit ihnen arbeite. Konkret gehts um die Initialisierung.
Zwei Beispiele:
Beispiel 1 - Hashtable

class Datenbank{
Hashtable prepStatements;

private Datenbank(){
// connect usw
prepStatements.put(prepStatement1, "prepStmt1");
...}

public boolean methode1(){
prepStatements.get("prepStmt1");
// query ausführen
};

}

Beispiel 2 - globale Variablen

class Datenbank{
prepStatement1;
prepStatement2;

private Datenbank(){
// connect usw ...}

public boolean methode1(){
tempStmt = prepStatement1;
// query ausführen
};

}


Welche version würdet ihr bevorzugen und warum? Oder lieg ich vielleicht komplett daneben?

Monger
2009-08-24, 13:06:03
Ich hab mir die beiden Beispiele jetzt einige Male durchgelesen, und mir fällt es ehrlich gesagt schwer, da eine vernünftige Empfehlung auszusprechen. Dafür sind die Beispiele einfach zu abstrakt.

Ich kann so aus der hohlen Hand heraus nur folgende Tipps geben:

- String Literale - insbesondere als Schlüssel - sind ausgesprochen bäh. Bei Strings schleichen sich gerne mal Tippfehler und kulturabhängige Anomalien ein, und eigentlich ist ja dein Ziel, dich genau von solchen Problemen zu entfernen. Sonst würdest du ja keine Prepared Statements verwenden, sondern einfach jedesmal den selben String reinklatschen.

- KISS-Prinzip. Wenn du zwei Lösungen hast die funktional genau das selbe tun, wählst du diejenige die weniger Codezeilen hat und besser zu verstehen ist. Im Endeffekt muss man es am realen Beispiel ausprobieren, aber meinem Gefühl nach glaube ich nicht, dass die HashTable hier irgendwas vereinfacht.

Berni
2009-08-24, 20:06:49
Benötigst du diese Kapselung mit Caching der Prepared Statements denn wirklich? Ist die Anwendung so dermaßen performancekritisch (das bringt in der Regel nur recht minimale Vorteile!)?

Gast
2009-08-24, 21:24:50
Erstmal danke für die Antworten. Mein erster Gedanke beim coden der Klasse war das ich eine Hashtable dafür nehmen könnte, einfach nur aus dem Grund das ich nich 50+ Prepared Statements als globale Variablen deklarieren muss. Eben um das übersichtlicher zu halten. Momentan tendiere ich aber auch eher zu der Variante mit den globalen Variablen, weil ich dann die String-Literals nich mehr brauche.

Was meinst du mit Caching? die Hashtable?

Monger
2009-08-24, 22:46:18
Was meinst du mit Caching? die Hashtable?
Mit Caching meint er hier, dass du Instanz für jedes Prepared Statement schon als Klassenattribut anlegst - also zur Instanziierung des Objekts initialisierst. Im Prinzip könntest du das ja auch in jedem Methodenaufruf machen - da wo die Statements halt gebraucht werden.

Mir fällt es gerade schwer zu glauben, dass du mehr als 50 prepared Statements brauchst - die auch nicht voneinander ableitbar sind, und im beliebigen Kontext stehen können.

Wenn du einen typisierten Zugriff auf etwas brauchst was eventuell auch konfigurierbar ist, ist das möglicherweise auch ein Fall für Resourcen/Settings. Bin aber gerade unsicher, wie das in Java mittlerweile konkret umgesetzt wird, bzw. das kommt dann auch auf die Entwicklungsumgebung an.

Kannst du nicht noch ein Beispiel machen, wo du tatsächlich zwei, drei Statements verwendest?

Eigentlich würde ich bei einem Objekt das vom Typ "Datenbank" ist annehmen, dass diese Objekt alle Informationen trägt die zu einer spezifischen Datenbank gehören. Sprich: ich würde schonmal erwarten, dass die URI zur Datenbank im Konstruktor gefordert wird.

Abnaxos
2009-08-24, 23:20:56
Eigentlich macht man solche Dinge mit Wrapper. Du beziehst deine Connection aus einer DataSource, das PreparedStatement aus der Connection etc. Und immer gibst du einen Wrapper zurück. Als Key für den Cache (in einer Connection) für PreparedStatements nimmst du den SQL-String selber. Dann kannst du auf prepareStatement() für ein identisches Statement ein entsprechendes PreparedStatement aus dem Cache nehmen.

Damit du die ganze Wrapperei nicht selber machen musst, würde ich commons-dbcp (http://commons.apache.org/dbcp/) empfehlen.

Es kommt stark auf den JDBC-Treiber und die Statements an, ob das überhaupt etwas bringt. Einige Treiber machen temporäre Stored Procedures aus Prepared Statements, d.h. sie haben den ganzen Ausführungsplan etc. bereits parat liegen, was der Performance zugute kommt. Bei einfachen Statements hingegen kann es wiederum gut sein, dass auch das absolut nichts bringt. Solche Dinge musst du in der Doku des JDBC-Treibers nachlesen.

Berni
2009-08-25, 00:12:20
Mein erster Gedanke beim coden der Klasse war das ich eine Hashtable dafür nehmen könnte, einfach nur aus dem Grund das ich nich 50+ Prepared Statements als globale Variablen deklarieren muss. Eben um das übersichtlicher zu halten. Momentan tendiere ich aber auch eher zu der Variante mit den globalen Variablen, weil ich dann die String-Literals nich mehr brauche.
Und was spricht dagegen die PreparedStatements einfach zu dem Zeitpunkt im Code zu nutzen (also Erzeugen und nach Benutzung schließen) an der du sie brauchst? Bei guter Programmierung sollte das nicht groß unübersichtlich sein und normal ist ein SQL-Statement auch nichts was man ständig ändert (daher auch eigtl. nicht in einem Configfile oder ähnlichem). Performancetechnisch ist es normal auch kein Problem. Sollte es tatsächlich nötig sein kannst du das PreparedStatement ja auch als Funktionsparameter übergeben.
Wenn ich abstrahiere würde ich das über etwas nach dem "Data Access Object"-Pattern machen: http://java.sun.com/blueprints/corej2eepatterns/Patterns/DataAccessObject.html
Was meinst du mit Caching? die Hashtable?
Naja ich ging davon aus, dass du in der Hashtable nicht den String sondern schon das fertige PreparedStatement-Objekt speicherst (ansonsten würde das ja überhaupt keinen Sinn machen). Das kann natürlich vorteilhaft sein wenn man sehr oft an verschiedensten Stellen im Code dasselbe ausführt. Denn das Parsen der Query und Erstellen des Ausführungsplans kostet eben Rechenzeit (im Übrigen auch wenn man nicht "normale" nicht-Prepared Statements nutzt). Allerdings ist das auch gefährlich weil man
a) möglicherweise auf die Synchronisierung achten muss wenn man mehrere Threads hat
b) man sicherstellen sollte dass das Statement auch geschlosen wird
c) die Datenbank unter Umständen das preparedStatement nicht unendlich lange speichert (zumindest bei serverseitigen preparedStatements werden hier ja Ressourcen auf dem Server belkegt die dieser unter Umständne irgednwann freiräumt) und es dann zu Fehlern kommt
Daher sollte das meiner Meinung nach wiklich nur nahc guter Überlegung an absolut performancekritischen Stellen genutzt werden.

Gast
2009-08-26, 10:46:15
Mit Caching meint er hier, dass du Instanz für jedes Prepared Statement schon als Klassenattribut anlegst - also zur Instanziierung des Objekts initialisierst. Im Prinzip könntest du das ja auch in jedem Methodenaufruf machen - da wo die Statements halt gebraucht werden.

Mir fällt es gerade schwer zu glauben, dass du mehr als 50 prepared Statements brauchst - die auch nicht voneinander ableitbar sind, und im beliebigen Kontext stehen können.

Wenn du einen typisierten Zugriff auf etwas brauchst was eventuell auch konfigurierbar ist, ist das möglicherweise auch ein Fall für Resourcen/Settings. Bin aber gerade unsicher, wie das in Java mittlerweile konkret umgesetzt wird, bzw. das kommt dann auch auf die Entwicklungsumgebung an.

Kannst du nicht noch ein Beispiel machen, wo du tatsächlich zwei, drei Statements verwendest?

Eigentlich würde ich bei einem Objekt das vom Typ "Datenbank" ist annehmen, dass diese Objekt alle Informationen trägt die zu einer spezifischen Datenbank gehören. Sprich: ich würde schonmal erwarten, dass die URI zur Datenbank im Konstruktor gefordert wird.

Bin grade dabei die Datenbank zu coden, die 50+ hab ich einfach mal am Anfang geschätzt, mittlerweile siehts eher so aus das ich etwa 35 brauche.
Die URI ist momentan hard coded im privaten Konstruktor, später soll die URI aus einer Konfigurationsdatei ausgelesen werden. Beispiel folgt weiter unten.


Eigentlich macht man solche Dinge mit Wrapper. Du beziehst deine Connection aus einer DataSource, das PreparedStatement aus der Connection etc. Und immer gibst du einen Wrapper zurück. Als Key für den Cache (in einer Connection) für PreparedStatements nimmst du den SQL-String selber. Dann kannst du auf prepareStatement() für ein identisches Statement ein entsprechendes PreparedStatement aus dem Cache nehmen.

Damit du die ganze Wrapperei nicht selber machen musst, würde ich commons-dbcp (http://commons.apache.org/dbcp/) empfehlen.

Es kommt stark auf den JDBC-Treiber und die Statements an, ob das überhaupt etwas bringt. Einige Treiber machen temporäre Stored Procedures aus Prepared Statements, d.h. sie haben den ganzen Ausführungsplan etc. bereits parat liegen, was der Performance zugute kommt. Bei einfachen Statements hingegen kann es wiederum gut sein, dass auch das absolut nichts bringt. Solche Dinge musst du in der Doku des JDBC-Treibers nachlesen.

Das Prepared Statements teilweise bei einfachen Anfragen keinen Geschwindigkeitsvorteil bringen ist für mich einleuchtend, auch das einige Treiber diese nicht korrekt unterstützen und deswegen generell eher keinen Perfomanceschub bringen. Das mit dem Wrappern muss ich mir mal genauer anschauen, höre das grad zum ersten Mal.

Und was spricht dagegen die PreparedStatements einfach zu dem Zeitpunkt im Code zu nutzen (also Erzeugen und nach Benutzung schließen) an der du sie brauchst? Bei guter Programmierung sollte das nicht groß unübersichtlich sein und normal ist ein SQL-Statement auch nichts was man ständig ändert (daher auch eigtl. nicht in einem Configfile oder ähnlichem). Performancetechnisch ist es normal auch kein Problem. Sollte es tatsächlich nötig sein kannst du das PreparedStatement ja auch als Funktionsparameter übergeben.
Wenn ich abstrahiere würde ich das über etwas nach dem "Data Access Object"-Pattern machen: http://java.sun.com/blueprints/corej2eepatterns/Patterns/DataAccessObject.html

Naja ich ging davon aus, dass du in der Hashtable nicht den String sondern schon das fertige PreparedStatement-Objekt speicherst (ansonsten würde das ja überhaupt keinen Sinn machen). Das kann natürlich vorteilhaft sein wenn man sehr oft an verschiedensten Stellen im Code dasselbe ausführt. Denn das Parsen der Query und Erstellen des Ausführungsplans kostet eben Rechenzeit (im Übrigen auch wenn man nicht "normale" nicht-Prepared Statements nutzt). Allerdings ist das auch gefährlich weil man
a) möglicherweise auf die Synchronisierung achten muss wenn man mehrere Threads hat
b) man sicherstellen sollte dass das Statement auch geschlosen wird
c) die Datenbank unter Umständen das preparedStatement nicht unendlich lange speichert (zumindest bei serverseitigen preparedStatements werden hier ja Ressourcen auf dem Server belkegt die dieser unter Umständne irgednwann freiräumt) und es dann zu Fehlern kommt
Daher sollte das meiner Meinung nach wiklich nur nahc guter Überlegung an absolut performancekritischen Stellen genutzt werden.

Am besten ich mach hier nochmal mal eben ein Beispiel:



public class Database {
private static Database instance = new Database();
private static Connection conn = null;

// folgende Zeile nur bei Version Hashtable
Hashtable<String, PreparedStatement> prepStatements = new Hashtable<String, PreparedStatement>();

// folgende Zeile nur bei Version globale Variablen
PreparedStatement banUserPrep;

public static Database getInstance() {
return Database.instance;
}

private Database() {


String connURL = "";
String username = "";
String pass = "";

try {
conn = DriverManager.getConnection(connURL, username, pass);

} catch (SQLException ex) {
// handle any errors
System.out.println("SQLException: " + ex.getMessage());
System.out.println("SQLState: " + ex.getSQLState());
System.out.println("VendorError: " + ex.getErrorCode());
}

// alle preparedstatements zur db senden und ebenso in der hashtabel speichern um darauf zugreifen zu können
try {
// folgende Zeile nur bei Version Hashtable prepStatements.put("banUserPrep",conn.prepareStatement("INSERT INTO ..."));

// folgende Zeile nur bei Version globale Variablen
banUserPrep = conn.prepareStatement("INSERT INTO ...");

} catch (SQLException e) {}

}

public boolean banUser(int userid, int duration, String desc) {
// folgende Zeile nur bei Version Hashtable
PreparedStatement tempState = prepStatements.get("banUserPrep");

// folgende Zeile nur bei Version globale Variablen
PreparedStatement tempState = banUserPrep;

// get actual time in milliseconds
long banstart = java.util.Calendar.getInstance().getTimeInMillis();

long banend = banstart + duration;

int changedRows = 0;

try {
tempState.setInt(1, userid);
tempState.setLong(2, banstart);
tempState.setLong(3, banend);
tempState.setString(4, desc);

changedRows = tempState.executeUpdate();

} catch (SQLException e) {}

if(changedRows == 1){
// es wurde eine zeile geaendert -> erfolg beim schreiben
return true;
} else{
// keine zeile geaendert
return false;
}
}
}


Zugriff auf die Klasse aus anderen Paketen ist dann mit Database.getInstance()._methode_(); möglich. Hintergrund des ganzen ist ein Projekt an der Uni mit dem wir in einer Gruppe arbeiten, deswegen soltle außerhalb dieser Klasse auch keiner mit SQL hantieren müssen.

zu c) Du meinst also PS nur an einigen wenigen Stellen benutzen und sonst normale Statements? Sind PS nicht aber auch generell sicherer?

Monger
2009-08-26, 11:13:35
Wird denn ein und das selbe PS jemals von mehreren Methoden genutzt? Wenn nicht, sehe ich keinen vernünftigen Grund, weshalb du dafür irgendwelche Attribute (egal ob HashTable oder eben einzeln) brauchst.

Warum also den Aufwand, sie erst mühsam zu sammeln und dann wieder auseinander zu friemeln, wenn du sie auch direkt lokal in der jeweiligen Methode deklarieren kannst?

Gast
2009-08-26, 17:26:36
Im Grunde werden die PS immer nur von einer Methode benutzt, aber der Geschwindigkeitsvorteil von PS entsteht nur wenn ich sie vorher, also nich immer lokal, deklariere, oder?

Monger
2009-08-26, 23:32:48
Solange du nicht ernsthafte Hinweise darauf hast dass dort dein Flaschenhals sitzt, mach dir mal über Performance keine Gedanken.

Sowohl der Compiler als auch die Datenbank optimieren da ohnehin.