отложенный порядок, когда переменная сохраняется - PullRequest
0 голосов
/ 03 февраля 2020

Я видел несколько вопросов по Go defer, но ничего похожего на реализацию, которую я пытаюсь сделать.

Тестовый пример

Приложение Я пишу довольно много дБ транзакций, поэтому у меня есть функция

func getCursor() *sql.Tx {
    psqlInfo := fmt.Sprintf("host=%s port=%d user=%s "+
        "password=%s dbname=%s sslmode=disable",
        host, port, user, password, dbname)
    db, err := sql.Open("postgres", psqlInfo)
    if err != nil {
        panic(err)
    }
    defer db.Close()
    err = db.Ping()
    if err != nil {
        panic(err)
    }
    tx, err := db.Begin()
    handleErr(err, tx)
    return tx
}

, которая возвращает мне транзакцию, и defer db.Close(), поэтому я не заполняю базу данных pool.

Другие функции, которые используют это:

// addPerson lets you add a person using a transaction that's passed as the first argument.
func addPerson(tx *sql.Tx, firstName string, lastName string, phoneNumber string) sql.Result {
    statement := "INSERT INTO public.persons (first_name, last_name, phone_number, data) VALUES ($1, $2, $3, '{}')"
    res, err := tx.Exec(statement, firstName, lastName, phoneNumber)
    handleErr(err, tx)
    return res
}

// wraps addPerson in a transaction to be used as standalong
func AddPerson(firstName string, lastName string, phoneNumber string) int64 {
    tx := getCursor()
    defer tx.Rollback()
    err := tx.Commit()
    res := addPerson(tx, firstName, lastName, phoneNumber)
    handleErr(err, tx)
    affected, err := res.RowsAffected()
    handleErr(err, tx)
    return affected
}

из https://blog.golang.org/defer-panic-and-recover

Отложенные вызовы функций выполняются в порядке «последний пришел - первый вышел» после возврата окружающей функции.

Поэтому, если tx.Commit() завершится неудачно, транзакция будет откатана.

Моя путаница вокруг defer db.Close(). Если это выполняется после того, как getCursor сделано, как я все еще могу сделать INSERT в базу данных? (Код в вопросе работает, меня просто смущает, почему он работает). Любая помощь в разъяснении очень ценится.

1 Ответ

1 голос
/ 03 февраля 2020

Во-первых, БД инициализируется один раз для каждого основного приложения. Итак, если вы хотите вызвать db.Close, включите его в основную функцию. и вам нужно рефакторинг вашего кода. Вот некоторая идея о том, как вы должны это реализовать. Надеюсь, это полезно.

func main() {
    db, err := createDBConn()
    if err != nil {
        panic(err)
    }
    defer db.Close()

    tx, err := beginTx(db)
    if err != nil {
        panic(err)
    }

    AddPerson("test", "test", "test")

}

func addPerson(tx *sql.Tx, firstName string, lastName string, phoneNumber string) sql.Result {
    statement := "INSERT INTO public.persons (first_name, last_name, phone_number, data) VALUES ($1, $2, $3, '{}')"
    res, err := tx.Exec(statement, firstName, lastName, phoneNumber)
    handleErr(err, tx)
    return res
}

// wraps addPerson in a transaction to be used as standalong
func AddPerson(tx *sql.Tx, firstName string, lastName string, phoneNumber string) int64 {
    defer tx.Rollback()
    err := tx.Commit()
    res := addPerson(tx, firstName, lastName, phoneNumber)
    handleErr(err, tx)
    affected, err := res.RowsAffected()
    handleErr(err, tx)
    return affected
}

func beginTx(db *sql.DB) (*sql.Tx, error) {
    tx, err := db.Begin()
    if err != nil {
        return nil, fmt.Errorf("create tx: %w", err)
    }
    return tx, nil
}

func createDBConn() (*sql.DB, error) {
    psqlInfo := fmt.Sprintf("host=%s port=%d user=%s "+
        "password=%s dbname=%s sslmode=disable",
        host, port, user, password, dbname)
    db, err := sql.Open("postgres", psqlInfo)
    if err != nil {
        return nil, fmt.Errorf("failed connect to db: %w", err)
    }

    err = db.Ping()
    if err != nil {
        return nil, fmt.Errorf("failed ping db: %w", err)
    }

    return db, nil
}

Почему вы все еще можете сделать запрос. Годо c https://golang.org/pkg/database/sql/#Conn. Закрыть . Close returns the connection to the connection pool. All operations after a Close will return with ErrConnDone. Close is safe to call concurrently with other operations and will block until all other operations finish. It may be useful to first cancel any used context and then call close directly after. будет блокироваться до тех пор, пока все остальные операции не будут завершены sh.

А https://golang.org/src/database/sql/sql.go линия 1930

...