Avatar billede dsj Nybegynder
31. januar 2003 - 14:45 Der er 37 kommentarer og
2 løsninger

Problem med PreparedStatement og transaktioner

Jeg har et problem med mine transaktioner, når jeg anvender PreparedStatements. Mine kode er som følger:

  con.setAutoCommit(false);
  String sql = "INSERT INTO event (event_id,type,spm_id) VALUES(?, ?, ?);";
  PreparedStatement prepStmt = con.prepareStatement(sql);
  prepStmt = dbHandler.prepare(sql);
  prepStmt.setLong(1, time);
  prepStmt.setString(2, "nqu");
  prepStmt.setInt(3, spmID);
  prepStmt.executeUpdate();
  ...
  // flere prepared statements køres
  ...
  con.commit();
  con.setAutoCommit(true);

Mit problem er så, at linien: con.commit(); smider en SQLException: Can't call commit when autocommit=true

Hvad sker der her? Jeg har jo lige sat autoCommit til false, og kører jeg con.getAutoCommit(); lige inden con.commit(); returnerer den false som den jo er sat til...
Avatar billede dsj Nybegynder
31. januar 2003 - 14:53 #1
Det skal lige tilføjes, at hvis jeg kører sql på den "normale" måder der slet ingen problemer, altså med:

Statement stat = con.createStatement();
stat.executeUpdate(sql);
Avatar billede erikjacobsen Ekspert
31. januar 2003 - 14:58 #2
Du bruger også en dbhandler ??
Avatar billede erikjacobsen Ekspert
31. januar 2003 - 14:59 #3
Hvad sker der hvis du sletter linien
prepStmt = dbHandler.prepare(sql);
Avatar billede dsj Nybegynder
31. januar 2003 - 15:03 #4
Ja, jeg har egentlig pakket al håndtering af databasen væk i en klasse for sig. Jeg kan se at jeg har skrevet lidt forkert for den er der heller ikke, jeg laver altså ikke to preparedStatements...
Avatar billede erikjacobsen Ekspert
31. januar 2003 - 15:05 #5
Ok - det var bare lidt mystisk ;)
Avatar billede dsj Nybegynder
31. januar 2003 - 15:06 #6
Det går bare let galt, når man skal vise sine kode lidt overskueligt og hiver den ud fra flere forskellige klasser :)
Avatar billede arne_v Ekspert
31. januar 2003 - 15:09 #7
Hvilken database og hvilken JDBC driver ?

Kan du lave et lille 30 liniers program i en klasse, som kan
genskabe problemet ?
Avatar billede dsj Nybegynder
31. januar 2003 - 15:31 #8
Jeg anvender MySQL standard 4.0.9 og alle tabeller er InnoDB. Driveren er den sidste nye 3-et-eller-andet developer-version. Koden:

package expnote;

import java.sql.*;

public class Test {
  private static final int MAX_RECONNECTS = 3;
  public Connection con = null;

  public Test() {
    open();
    run(1, "dsj", "Hvordan gør man?", "Programmering : Java", "60", System.currentTimeMillis());
  }

  public boolean open() {
    boolean success = false;
    try {
      if(con == null) {
        Class.forName("org.gjt.mm.mysql.Driver").newInstance();
        con = DriverManager.getConnection("jdbc:mysql://10.0.0.9/expnote2:3306"
                                        +"?user=root&password=280780"
                                        +"&autoReconnect=true"
                                        +"&maxReconnects="+MAX_RECONNECTS);
        con.setTransactionIsolation(con.TRANSACTION_READ_COMMITTED);
        success = true;
      }
    } catch (Exception exc) {
      exc.printStackTrace();
    }
    return success;
  }

  public void run(int spmID, String creator, String title, String category, String points, long time) {
    try {
      String sql = "INSERT INTO question (spm_id,creator,title,category,points) VALUES(?, ?, ?, ?, ?);";
      PreparedStatement prepStmt = con.prepareStatement(sql);
      con.setAutoCommit(false);
      prepStmt.setInt(1, spmID);
      prepStmt.setString(2, creator);
      prepStmt.setString(3, title);
      prepStmt.setString(4, category);
      prepStmt.setString(5, points);
      try {
        prepStmt.executeUpdate();
      } catch (Exception sqle) {
        sqle.printStackTrace();
      }
      sql = "INSERT INTO event (event_id,type,spm_id) VALUES(?, ?, ?);";
      prepStmt = con.prepareStatement(sql);
      prepStmt.setLong(1, time);
      prepStmt.setString(2, "nqu");
      prepStmt.setInt(3, spmID);
      try {
        prepStmt.executeUpdate();
      } catch (Exception sqle) {
        sqle.printStackTrace();
      }
      con.commit();
      con.setAutoCommit(true);
    } catch (Exception exc) {
      exc.printStackTrace();
    }
  }
}

Jeg kan bare ikke genskabe den nævnte SQLException, men når prepStmt.executeUpdate(); er kørt på den første bliver den sendt til databasen, hvilket den jo ikke skal. Begge statements skulle gerne sendes til database ved con.commit(); og ikke ved prepStmt.executeUpdate();
Avatar billede arne_v Ekspert
31. januar 2003 - 15:37 #9
Et par forslag:

1)  Flyt setAutoCommit(false) op før prepareStatement

2)  Put begge executeUpdate i samme try/catch og lav en rollback
Avatar billede arne_v Ekspert
31. januar 2003 - 15:38 #10
Altså lav en rollback i catch.
Avatar billede arne_v Ekspert
31. januar 2003 - 15:39 #11
Og du er naturligvis klar over, at når man kører med bleeding edge
developer versioner, så kan der være små fejl i koden.
Avatar billede dsj Nybegynder
31. januar 2003 - 15:44 #12
1. ændrer intet
2. Nytter ikke noget, begge handlinger skal udføres på commit(); IKKE hver for sig. Fjerner jeg con.commit(); bliver de jo stadig udført. Hvordan får jeg de her PreparedStatements til at vente med at blive udført til commit(); ?

Ja, jeg er klar over der kan være fejl i developer-versioner
Avatar billede arne_v Ekspert
31. januar 2003 - 15:44 #13
Har du prøvet:
  con.getMetaData().supportsTransctions()
og:
  con.getMetaData().supportsTransctionIsolationLevel(con.TRANSACTION_READ_COMMITTED)
?
Avatar billede arne_v Ekspert
31. januar 2003 - 15:46 #14
Jeg ved godt at begge handliner skal udføres med sammecommit, men
jeg vil bare have dem i samme try/catch:

try {
  executeUpdate
  executeUpdate
  commit
} catch {
  rollback
}
Avatar billede dsj Nybegynder
31. januar 2003 - 15:51 #15
Gør jeg således, virker det perfekt!

con.setAutoCommit(false);
Statement stat = con.createStatement();
stat.executeUpdate(sql);
Statement stat1 = con.createStatement();
stat1.executeUpdate(sql1);
Statement stat2 = con.createStatement();
stat2.executeUpdate(sql2);
con.commit();
con.setAutoCommit(true);

Gør jeg således:

try {
  executeUpdate
  executeUpdate
  commit
} catch {
  rollback
}

Kommer der en exception i den første executeUpdate(), ide der i forvejen findes et en tuppel i databasen med den primærnøglen '1'. Gør jeg som du forslår bliver det sådan noget applications-styret transaktions-halløj, jeg vil havde databasen til at styre det.

Og jo, MySQL understøtter transaktioner og jeg har sat con.TRANSACTION_READ_COMMITTED
Avatar billede arne_v Ekspert
31. januar 2003 - 15:53 #16
Og så undrer det mig at du loader:
  org.gjt.mm.mysql.Driver
jeg troede at nyeste MySQL JDBC drivere hed:
  com.mysql.jdbc.Driver
efter at MySQL selv har overtaget JDBC driveren !?
Avatar billede dsj Nybegynder
31. januar 2003 - 15:53 #17
Jeg kunne selvfølgelig lade være med at bruge PreparedStatements, men så er jeg tilbage til problemet med at nogle af attributterne indeholder tegnet \' hvilket ødelægger SQL-sætningen !
Avatar billede dsj Nybegynder
31. januar 2003 - 15:57 #18
Ok, jeg var ikke klar over at den nye driver hedder "com.mysql.jdbc.Driver", det ændrer imidlertid ikke ved noget :)
Avatar billede arne_v Ekspert
31. januar 2003 - 15:58 #19
PreparedStatement er det eneste rigtige.

1) Det løser quote problemet.
2) der forbedrer performance.
Avatar billede arne_v Ekspert
31. januar 2003 - 16:00 #20
Jeg har set at du sætter TRANSACTION_READ_COMMITTED, men har du spurgt om
JDBC driveren og MySQL understøttr det ?

[ikke alle databaser understøtter alle isolation levels]
Avatar billede arne_v Ekspert
31. januar 2003 - 16:03 #21
OK - du vil have den sidste committed selvom den første fejler -
men ikke den første committed hvis den anden fejler ?
Avatar billede dsj Nybegynder
31. januar 2003 - 16:10 #22
supportsTransactionIsolationLevel - true
supportsTransactionIsolationLevel(con.TRANSACTION_READ_COMMITTED) - true
supportsDataDefinitionAndDataManipulationTransactions - false

Før jeg konverterede til InnoDB, fik jeg en exception ved con.setAutoCommit(false);
Avatar billede dsj Nybegynder
31. januar 2003 - 16:17 #23
Ja, jeg vil have den sidste commited selvom den første fejler. Ideen er jo netop, at databasen så finder ud af, at den første fejler og ikke udfører den anden! Rollback burde heller ikke være nødvendigt. DBMS'en skulle meget gerne selv finde ud af, at noget gik galt og ikke udføre noget som helst af transaktionen.

Bruger jeg almindelige statements og udfører en test med 4 sql-sætninger i en transaktion, hvor den sidste går galt med vilje, kan jeg se i databasen, at de tre første insert's ikke er udført (uden rollback i Java), præcis som det skal være, dette er blot ikke tilfældet med PreparedStatements.
Avatar billede dsj Nybegynder
31. januar 2003 - 16:23 #24
ehe fik lige talt over mig, selvfølgelig skal der også rollback til, havde bare overset at jeg faktisk havde skrevet det :)
Avatar billede arne_v Ekspert
31. januar 2003 - 16:31 #25
Der er forskel på:

try {
  executeUpdate
  executeUpdate
  commit
} catch {
  rollback
}

og:

try {
  excecuteUpdate
} catch {
}
try {
  excecuteUpdate
} catch {
}
commit

og:

try {
  excecuteUpdate
} catch {
}
try {
  excecuteUpdate
  commit
} catch {
  rollback
}
Avatar billede arne_v Ekspert
31. januar 2003 - 16:33 #26
De giver henholdsvis:

ok ok => commit
ok fail => rollback
fail ok => rollback
fail fail => rollback

og:

ok ok => commit
ok fail => commit
fail ok => commit
fail fail => commit (af ingenting)

og:

ok ok => commit
ok fail => rollback
fail ok => commit
fail fail => rollback
Avatar billede arne_v Ekspert
31. januar 2003 - 16:34 #27
Nå men det løser jo ikke dit problem - det var bare for at forklare hvorfor jeg hvade foreslået en try-catch.
Avatar billede arne_v Ekspert
31. januar 2003 - 16:37 #28
Hvis du skal videre med dit problem, så  tror jeg at det var interessant at
prøve den samme kode mod en anden database med en anden JDBC driver.

Virker det der, så er der dømt MySQL developer version problem.

Virker det ikke der, så må der jo være et eller andeti koden.

Jeg har en pæn samling database derhjemme og jeg kunn egodt lave
en lille test i weekenden.
Avatar billede dsj Nybegynder
31. januar 2003 - 16:40 #29
Ja det har du ret i.

Der er noget riv-ravende galt i min kode, jeg gennemgår den nu fra bunden og må vende tilbage senere.

Denne:
try {
  executeUpdate
  executeUpdate
  commit
} catch {
  rollback
}

er selvfølgelig den rigtige - jeg har prøvet den før, men med fejl, sikkert fordi der i min kode er noget der er helt galt.
Avatar billede soelvpil Nybegynder
01. februar 2003 - 22:56 #30
Når jeg kigger på koden i dit oprindelige spørgsmål får jeg en mistanke om, at din dbhandler (er det mon disky's???) har sin egen connection inde i sig.

dvs du sætter autocommit til false på en connection, men den connection der giver dig din preparedstatement er en helt anden, hvor autocommit stadig er sat til true.

Giver det mening??
Avatar billede soelvpil Nybegynder
01. februar 2003 - 23:01 #31
Måske du bare sku slette linjen

prepStmt = dbHandler.prepare(sql);
Avatar billede arne_v Ekspert
01. februar 2003 - 23:05 #32
soelvpil>

Har du læst indlæggene 14:59-15:06 ?
Avatar billede dsj Nybegynder
01. februar 2003 - 23:41 #33
Engang var det disky's dbhandler, men den er da alt for simpel til andet end at starte med, dengang man ikke vidste så meget :)
Avatar billede arne_v Ekspert
02. februar 2003 - 09:45 #34
Sådan som jeg læser de kommentarer har han ikke både dbhandler og direkte JDBC,
men kom kun til at copy-paste lidt blandet.

Jeg har aldrig set pointen i diskys dbhandler - stort set erstatter den
1 java.sql metode med 1 disky-metode.
Avatar billede dsj Nybegynder
02. februar 2003 - 14:44 #35
Ja, jeg kom til at copy/paste noget blandet.

Det er princippet omkring klassen jeg godt kan lide: at jeg har et objekt der repræsenterer databasen. Indvendig ligner den slet ikke disky's. F.eks. har jeg etableret en connection-pool samt mekanismer til reconnect, hvis forbindelserne ryger, debugging metoder/udskrifter mm., som i klassen er pakket pænt væk.

Indtil videre har jeg også kunnet bruge klassen til at synkronisere adgangen til databasen, så ikke alle tråde kunne gøre som de ville. Men nu er tiden kommet til at komme videre med transaktions-styring.

Jeg har desuden fået skidtet til at virke efter at have lavet det hele fra bunden. Det er noget kode-rod, som resulterede i problemerne.
Avatar billede dsj Nybegynder
02. februar 2003 - 14:45 #36
arne hvis du dumper et svar kan du få nogle point for ulejligheden.
Avatar billede arne_v Ekspert
02. februar 2003 - 14:49 #37
svar
Avatar billede arne_v Ekspert
02. februar 2003 - 14:50 #38
Det kan skam være udmærket at lave en database klasse, hvis
den leverer noget ekstra funktionalitet i forhold til
java.sql (og det lydet det det som om din gør).
Avatar billede soelvpil Nybegynder
02. februar 2003 - 16:14 #39
arne_v: næ, jeg fik ikke læst hele tråden så grundigt igennem, og når man ikke gør det, snakker man som man har forstand til.

Mine 10 points burde vist have tilfaldet Erik i stedet (men har mistanke om at han er ret ligeglad med points, når han konsekvent laver kommentarer i.s.f. svar).
Avatar billede Ny bruger Nybegynder

Din løsning...

Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.

Loading billede Opret Preview
Kategori
Kurser inden for grundlæggende programmering

Log ind eller opret profil

Hov!

For at kunne deltage på Computerworld Eksperten skal du være logget ind.

Det er heldigvis nemt at oprette en bruger: Det tager to minutter og du kan vælge at bruge enten e-mail, Facebook eller Google som login.

Du kan også logge ind via nedenstående tjenester