PreparedStatement vs. StoredProcedure vs. executeBatch

Hallo zusammen!

Disclaimer erstmal: habe diese Frage schon hier gestellt, leider noch keine Resonanz.
Hier zur Übersicht nochmal der Text:

Ich habe eine grundlegende Frage bezüglich Speichern in einer MySQL DB

Nehmen wir diese Klasse:

public class DummyObj 
{
public int intvalue;
public String stringvalue;

public DummyObj()
{
	intvalue=123456;
	stringvalue="awdawd";
}
}

und füllen eine Liste mit Objekten der Klasse

int i;
dummyliste = new ArrayList<DummyObj>();
for (i=0; i<100000; i++)
{
	DummyObj dummy = new DummyObj();
	dummyliste.add(dummy);
}

Nun möchte ich diese Liste in diese MysqlTabelle speichern:
[SQL]
CREATE TABLE testtable (
idtesttable int(11) NOT NULL AUTO_INCREMENT,
stringvalue varchar(45) DEFAULT NULL,
intvalue int(11) DEFAULT NULL,
PRIMARY KEY (idtesttable)
)
[/SQL]

Folgende Möglichkeiten ergeben sich mir:

Methode 1 - mit Stored Procedure:
ich habe diese StoredProcedure auf meinem MySQLServer
[SQL]PROCEDURE inserttest(
IN v_teststring VARCHAR(45),
IN v_testint INT
)
BEGIN
INSERT INTO testtable
SET stringvalue=v_teststring, intvalue=v_testint;
END
[/SQL]
und rufe sie in meinem JavaCode auf

Connection con = DriverManager.getConnection(connectionString);
int j;
CallableStatement cstmt = con.prepareCall("call inserttest(?, ?)");
for (j=0; j<dummyliste.size();j++)
{				
	cstmt.setString(1, dummyliste.get(j).stringvalue);
	cstmt.setInt(2, dummyliste.get(j).intvalue);
	cstmt.execute();	
}

Methode 2 - Prepared Statement

Connection con = DriverManager.getConnection(connectionString);
int j;
PreparedStatement pstmt = con.prepareStatement("INSERT INTO testtable SET stringvalue=?, intvalue=?;");
for (j=0; j<dummyliste.size();j++)
{				
	pstmt.setString(1, dummyliste.get(j).stringvalue);
	pstmt.setInt(2, dummyliste.get(j).intvalue);
	pstmt.executeUpdate();
}

Methode 3 - Statement mit Batch

Connection con = DriverManager.getConnection(connectionString);
int j;
Statement stmt = con.createStatement();
for (j=0; j<dummyliste.size();j++)
{
	stmt.addBatch("INSERT INTO testtable SET stringvalue=\""+dummyliste.get(j).stringvalue+"\", intvalue="+dummyliste.get(j).intvalue+";");
}
stmt.executeBatch();

Ich habe es gerade seperat mit allein 3 Methode getestet und konnte keine bedeutsamen Geschwindigkeitsunterschiede festestellen, Methode 2 und 3 waren sogar identisch
Gibt es sonstige Gründe, warum man die eine Methode der anderen vorziehen sollte oder eben nicht?

Grüße
Stefan

Methode 3
Statements solltest du, wenn du sie zusammenfrickelst und das meine ich an dieser Stelle “wertend” überhaupt nicht machen.
Ruckzuck hast du eine SQL-Injection, wenn du keine gute Validierung machst.
Also auch beim Batch das PreparedStatement nutzen.

Eine Sache die du noch garnicht verraten hast ist, wie du deine Transaktionen handhabst.

Eine weitere Geschichte die du machen kannst mit StoredProcedures sind Loops.
Du könntest also auch den Loop in der StoredProcedure laufen lassen anstatt in Java. Das würde mit Sicherheit einen Unterschied machen. Da dann zwischen DB und Java weniger kommuniziert werden müsste.

Aus genau diesem Grund (SQL-Injection) habe ich mich für das PreparedStatement entschieden - wenn ich hier auch die Möglichkeit eines BatchUpdates habe, umso besser, wieder was gelernt :slight_smile:
Sprich:

Connection con = DriverManager.getConnection(connectionString);
int j;
PreparedStatement pstmt = con.prepareStatement("INSERT INTO testtable SET stringvalue=?, intvalue=?;");
for (j=0; j<dummyliste.size();j++)
{              
    pstmt.setString(1, dummyliste.get(j).stringvalue);
    pstmt.setInt(2, dummyliste.get(j).intvalue);
    pstmt.addBatch();
}
pstmt.executeBatch();

wäre hier die favorisierte Lösung

Transaktion: Ich würde nach dem executeBatch() das commit() setzen, rollback() im catch-Block (vorher con.setAutoCommit(false) natürlich)
Loop: Verstehe ich technisch, aber ich sehe gerade nicht wie ich den Loop in der SP laufen lassen soll - es sei denn es gibt eine Möglichkeit eine ArrayList direkt an die SP zu geben?

Vielen Dank!

Man könnte z.B. auf das erstellen der Liste in Java verzichten und die Anzahl der zu erstellenden Objekte übergeben. Dann könnte man in einem Loop in einer SP die Datensätze erstellen.

Im Grunde stellt sich eigentlich nur die Frage ob SP oder PrepaparedStatement.

Das Beispiel, das hier gewählt wurde ist mMn. noch zu einfach um da wirklich Vorteile zu sehen.

Angenommen du möchtest in der Tabelle testtable auch noch zwei weitere Spalten haben. Und zwar createdAt und updatedAt bei dem ein Datum hinterlegt ist.

Jetzt ist es zwar möglich das ganze in einem Insert-Statement zu setzen in dem man auf dem Prep.St. setDate aufruft und das aktuelle Datum setzt.
Allerdings ist das etwas lästig und sorgt für unnötigen Java-Code.
Also könnte man im Statement auch eine SP aufrufen wie “NOW()”.
Und jetzt fängt der Spaß an, weil das ganze im SQLServer dann “GETDATE()” heißt.

Und verständlicher wird das ganze damit auch nicht wenn da ein zusätzlicher Parameter hinzukommt.

Also kann man sich für die verwendete Datenbank eine StoredProcedure anlegen, die dann intern das Datum der Erstellung setzt.

Andererseits könnte man sich auch einen Trigger anlegen, der das Datum nach dem erstellen einer neuen Zeile setzt. Allerdings wird es dann schwierig einen Not Null Constraint auf diese Spalte zu setzen, da diese ja erst im Nachhinein befüllt wird.

Der Nachteil an SP ist mMn. der, dass jemand der mit Datenbanken arbeitet in der Regel auch Querys schreiben kann und eine SP schnell mal ausufern kann, wenn man dann so Tabellen mit 70 Spalten hat, bei denen dann noch viele NULL Werte vorkommen.
Eine SP hätte dann sehr viele Parameter. Eine Query könnte man dynamisch zusammenbauen.