2013-04-24 12 views
21

Korzystanie z bazy danych/sql i pakietów sterowników i Tx, nie jest możliwe, aby wykryć, czy transakcja została zatwierdzona lub wycofana bez próby kolejnego i otrzymania błędu jako wynik, a następnie przeanalizowanie błędu w celu określenia typu błędu. Chciałbym móc określić z obiektu Tx, czy został popełniony, czy nie. Oczywiście, mogę zdefiniować i ustawić inną zmienną w funkcji, która używa Tx, ale mam ich całkiem sporo i za każdym razem jest to 2 razy (zmienna i przypisanie). Mam również funkcję odroczoną, aby wykonać Cofanie, jeśli jest to konieczne, i musi zostać przekazana zmienna bool.
Czy można zaakceptować ustawienie zmiennej Tx na zero po zatwierdzeniu lub wycofaniu i czy GC odzyska jakąkolwiek pamięć, czy jest to nie-nie, czy istnieje lepsza alternatywa?database/sql Tx - wykrywanie Commit lub Rollback

+1

Nie jestem pewien, czy rozumiem problemu. Musisz zakończyć transakcję za pomocą Commit lub Rollback, aby wiedzieć, co zrobiłeś, ale nie chcesz tego pamiętać w dodatkowej zmiennej? Możesz owinąć Tx i bool we własny RememberingTx, co zmniejszy nieco liczbę linii. W odniesieniu do pytania GC: Nie ma znaczenia, czy ustawiłeś zero, czy nie: pamięć zostanie odzyskana, gdy nie pozostanie do niej żadne odniesienie. Tak: Tak, możesz mieć 'var tx * Tx; fantastyczna okazja; if cond {tx.Commit; tx = nil} else {tx.Rollback}; fantastyczna okazja; if tx == nil {was commited} else {was rollbacked} 'ale czuje się brzydko. – Volker

+0

To o to chodzi, ale istnieje odroczony func, który wykonuje rollback, jeśli Tx nie jest zerowe. Po zatwierdzeniu transakcji, Tx i tak nie może być użyty, więc planuję ustawić go na zero. To nie jest ładne, jednak próba wycofania i testowanie komunikatu o błędzie nie jest ładna. Problem polega na tym, że AFAIK nie ma możliwości sprawdzenia, czy transakcja jest "wykonana" od Tx. Nie jestem pewien, dlaczego tak się stało, być może wydajność. –

Odpowiedz

73

Dlaczego miałbyś to zrobić? Funkcja wywołująca Begin() powinna również wywoływać Commit() lub Rollback() i zwracać odpowiedni błąd.

Na przykład ten kod robi commit lub rollback w zależności od tego, czy zwracany jest błąd:

func (s Service) DoSomething() (err error) { 
    tx, err := s.db.Begin() 
    if err != nil { 
     return 
    } 
    defer func() { 
     if err != nil { 
      tx.Rollback() 
      return 
     } 
     err = tx.Commit() 
    }() 
    if _, err = tx.Exec(...); err != nil { 
     return 
    } 
    if _, err = tx.Exec(...); err != nil { 
     return 
    } 
    // ... 
    return 
} 

zauważyć, jak ja sprawdzanie error, aby zobaczyć, czy nie powinienem popełnić lub wycofania. Powyższy przykład nie obsługuje jednak paniki.

Nie lubię robić logiki commit/rollback w każdej procedurze bazy danych, więc zwykle pakuję je w procedurę obsługi transakcji. Coś wzdłuż linii to:

func Transact(db *sql.DB, txFunc func(*sql.Tx) error) (err error) { 
    tx, err := db.Begin() 
    if err != nil { 
     return 
    } 
    defer func() { 
     if p := recover(); p != nil { 
      tx.Rollback() 
      panic(p) // re-throw panic after Rollback 
     } else if err != nil { 
      tx.Rollback() 
     } else { 
      err = tx.Commit() 
     } 
    }() 
    err = txFunc(tx) 
    return err 
} 

Pozwala mi to zrobić w zamian:

func (s Service) DoSomething() error { 
    return Transact(s.db, func (tx *sql.Tx) error { 
     if _, err := tx.Exec(...); err != nil { 
      return err 
     } 
     if _, err := tx.Exec(...); err != nil { 
      return err 
     } 
    }) 
} 

Zauważ, że jeśli coś w moim transakcji panikuje ona automatycznie obsługiwane przez procedurę obsługi transakcji.

W mojej aktualnej implementacji przekazuję interfejs zamiast * sql.Tx, aby zapobiec niechcianym połączeniom do Commit() lub Rollback().

Oto prosty fragment wykazać jak defer prace (Wydruki 4, a nie 5):

package main 

func test() (i int) { 
    defer func() { 
     i = 4 
    }() 
    return 5 
} 

func main() { 
    println(test()) 
} 

http://play.golang.org/p/0OinYDWFlx

+0

dobra odpowiedź! Myślę, że brakowało ci "powrotu zero" pod koniec twojej drugiej implementacji doSomething(). – splinter123

+0

Luke, jak i kiedy błędy są oceniane? Zgodnie z dokumentacją, "err" powinien uzyskać wartość, gdy zostanie po raz pierwszy zadeklarowany w wywołaniu odroczonym.Tak więc jest to dla mnie nieco mylące, ponieważ wartość "err" użyta w odroczeniu wydaje się zmieniać. – mirage

+0

err jest zadeklarowane przed odroczeniem przez: = (dwukropek równy). Anon func przechwytuje to. Defer jest wywoływany tuż przed zwróceniem wartości. To pozwala na ustawienie. Kiedy pojawia się panika, jest odzyskiwana, zamieniana na błąd, a następnie zwracana. Jeśli błąd wystąpi w jakikolwiek sposób, nastąpi wycofanie. W końcu następuje zatwierdzenie, jeśli nie ma błędów, a wartość err (obecnie zero) jest ustawiona na wartość zwracaną Commit w przypadku wystąpienia błędów. – Luke

Powiązane problemy