var defaultTxOptions = &sql.TxOptions{} const defaultCurrency = "RUB" func CreateUserWithDefaultCurrency(ctx context.Context, db *sql.DB, email, passwordHash string) error { tx, err := db.BeginTx(ctx, defaultTxOptions) if err != nil { return err } defer func() { if err != nil{ tx.Rollback() } }() _, err = tx.Exec("INSERT INTO users (email, password) VALUES (?, ?)", email, passwordHash) if err != nil { return err } rows, err := tx.Query("SELECT id FROM users WHERE email = ?", email) if err != nil { return err } defer rows.Close() id := 0 for rows.Next() { err = rows.Scan(&id) if err != nil { return err } } _, err = tx.Exec("INSERT INTO balances (user_id, currency, current_balance) VALUES (?, ?, 0)", id, defaultCurrency) if err != nil { return err } return tx.Commit() }
Answer from AI
sobes.tech AI
В представленном коде есть несколько проблем, связанных с управлением транзакцией и обработкой ошибок:
-
Проблема с
deferи переменнойerr: Вdeferиспользуется переменнаяerr, которая может измениться после объявленияdefer, но в замыкании будет использоваться её значение на момент объявления. Это может привести к тому, чтоtx.Rollback()не вызовется при ошибках. -
Неявное получение ID пользователя: После вставки пользователя лучше использовать
LastInsertId()илиRETURNING id(в зависимости от СУБД), а не делать отдельный запрос. -
Отсутствие обработки ошибок при
tx.Commit().
Исправленный пример:
func CreateUserWithDefaultCurrency(ctx context.Context, db *sql.DB, email, passwordHash string) error {
tx, err := db.BeginTx(ctx, defaultTxOptions)
if err != nil {
return err
}
defer func() {
if p := recover(); p != nil {
tx.Rollback()
panic(p)
} else if err != nil {
tx.Rollback()
} else {
err = tx.Commit()
}
}()
res, err := tx.ExecContext(ctx, "INSERT INTO users (email, password) VALUES (?, ?)", email, passwordHash)
if err != nil {
return err
}
id64, err := res.LastInsertId()
if err != nil {
return err
}
id := int(id64)
_, err = tx.ExecContext(ctx, "INSERT INTO balances (user_id, currency, current_balance) VALUES (?, ?, 0)", id, defaultCurrency)
if err != nil {
return err
}
return err
}
Таким образом, транзакция корректно откатится при ошибках, а ID пользователя получаем напрямую после вставки, что эффективнее и безопаснее.