2013-04-24 11 views
21

Utilizzando i pacchetti database/sql e driver e Tx, non è possibile che venga rilevato se una transazione è stata eseguita o rollback senza tentare un altro e ricevere un errore come risultato e quindi esaminare l'errore per determinare il tipo di errore. Mi piacerebbe essere in grado di determinare dall'oggetto Tx se commesso o meno. Certo, posso definire e impostare un'altra variabile nella funzione che utilizza Tx, ma ne ho un bel numero, ed è volta 2 ogni volta (variabile e assegnazione). Ho anche una funzione differita per fare un rollback, se necessario, e deve essere passato la variabile bool.

Sarebbe accettabile impostare la variabile Tx su nil dopo un commit o rollback, e il GC recupererà la memoria, o è un no-no, o c'è un'alternativa migliore?database/sql Tx - rilevamento Commit o rollback

+1

Non sono sicuro se ho ben capito il problema. Devi terminare una transazione con Commit o Rollback in modo da sapere cosa hai fatto ma non vuoi ricordarlo in una variabile extra? Potresti avvolgere Tx e il bool nel tuo RememberingTx, questo ridurrebbe un po 'il conteggio delle righe. Per quanto riguarda la domanda GC: Non importa se si imposta a zero o no: la memoria verrà recuperata una volta che non vi è più alcun riferimento. Quindi: Sì, puoi avere 'var tx * Tx; snip; se cond {tx.Commit; tx = nil} else {tx.Rollback}; snip; se tx == nil {era commited} else {was rollbacked} 'ma sembra brutto. – Volker

+0

Ecco di cosa si tratta, ma esiste una funzione differita che esegue un rollback se Tx non è nullo. Una volta che una transazione è stata commessa, il Tx non può essere comunque utilizzato, quindi ho intenzione di impostarlo su zero. Non è bello, tuttavia tentare un rollback e testare il messaggio di errore non è carino. Il problema è che AFAIK non ha modo di testare se la transazione è "conclusa" da Tx. Non sono sicuro del motivo per cui è stato fatto in quel modo, forse le prestazioni. –

risposta

73

Perché dovresti averne bisogno? La funzione che chiama Begin() dovrebbe anche chiamare Commit() o Rollback() e restituire un errore appropriato.

Ad esempio, questo codice fa un commit o rollback a seconda se viene restituito un errore:

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 
} 

Notate come sto controllando error per vedere se o non dovrei commit o rollback. L'esempio sopra non, tuttavia, gestisce il panico.

Non mi piace eseguire la logica di commit/rollback su ogni routine di database, quindi di solito li incorporo in un gestore di transazioni. Qualcosa sulla falsariga di questo:

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 
} 

Questo mi permette di fare questo, invece:

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 
     } 
    }) 
} 

Si noti che se qualcosa dentro la mia transazione panico è gestito automaticamente dal gestore delle transazioni.

Nella mia implementazione effettiva passaggio un'interfaccia invece di * sql.Tx per impedire chiamate indesiderate a Commit() o Rollback().

Ecco un semplice frammento di dimostrare come defer opere (stampe 4, non 5):

package main 

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

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

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

+0

bella risposta! Penso che ti sia mancato un "return nil" verso la fine della tua seconda implementazione doSomething(). – splinter123

+0

Luke, come e quando viene valutato err? Secondo la documentazione, "err" dovrebbe ottenere il suo valore quando viene dichiarato per la prima volta nella chiamata differita.Quindi questo è in realtà un po 'di confusione per me, dal momento che il valore di "err", come usato in differimento, sembra cambiare. – mirage

+0

err viene dichiarato prima del differimento tramite: = (due punti uguali). L'anon func lo cattura. Il rinvio è chiamato appena prima che il valore venga restituito. Questo gli permette di essere impostato. Quando si verifica un panico viene recuperato, trasformato in errore, quindi restituito. Se si verifica un errore in qualsiasi modo, si verifica un rollback. Finalmente un commit avviene se non ci sono errori e err (attualmente nil) è impostato su Commits valore di ritorno nel caso in cui errori. – Luke

Problemi correlati