Golang中的数据库CRUD操作(f附实例)

151 阅读5分钟

这个例子将CRUD操作分为两个部分,即 "写 "和 "读"。写 "部分包括INSERT,UPDATEDELETE 查询。读 "部分涵盖了SELECT 查询。我们也在使用context.WithTimeout 来终止长期运行的查询。请注意SELECT 查询,因为它们依赖于一个叫做MAX_EXECUTION_TIME 的东西。

插入/更新/删除

预备(安全选项)

如果你不能100%确定查询参数是没有SQL注入的,那么首选这个。

这是一个缓慢的操作,因为它是网络往返次数的三倍 (Prepare,ExecuteClose)。该查询使用? 参数占位符。

下面两个例子的工作方式完全相同,所以我建议使用第二个例子,因为它更干净。另外,无论查询参数是动态的还是静态的,它仍然是网络往返次数的三倍。

func Create(args ...interface{}) error {
	ctx, cancel := context.WithTimeout(context.Background(), 3 * time.Second)
	defer cancel()

	stmt, err := db.PrepareContext(ctx, `
INSERT INTO leagues
(uuid, name, address, int_rank, is_active, founded_at, created_at)
VALUES
(?, ?, ?, ?, ?, ?, UTC_TIMESTAMP())
`)
	if err != nil {
		return err
	}
	defer stmt.Close()

	res, err := stmt.ExecContext(ctx, args...)
	if err != nil {
		return err
	}

	tot, err := res.RowsAffected()
	if err != nil {
		return err
	}

	if tot != 1 {
		return errors.New("no rows were affected")
	}

	return nil
}
func Create(args ...interface{}) error {
	ctx, cancel := context.WithTimeout(context.Background(), 3 * time.Second)
	defer cancel()

	q := `
INSERT INTO leagues
(uuid, name, address, int_rank, is_active, founded_at, created_at)
VALUES
(?, ?, ?, ?, ?, ?, UTC_TIMESTAMP())
`

	res, err := db.ExecContext(ctx, q, args...)
	if err != nil {
		return err
	}

	tot, err := res.RowsAffected()
	if err != nil {
		return err
	}

	if tot != 1 {
		return errors.New("no rows were affected")
	}

	return nil
}
2020-03-13T13:14:36.035537Z 9 Prepare INSERT INTO leagues
(uuid, name, address, int_rank, is_active, founded_at, created_at)
VALUES
(?, ?, ?, ?, ?, ?, UTC_TIMESTAMP())

2020-03-13T13:14:36.038073Z 9 Execute INSERT INTO leagues
(uuid, name, address, int_rank, is_active, founded_at, created_at)
VALUES
('6a980454-0727-4260-a757-ce619e79af83', 'League 1', 'Address', 2, 0, '2001-01-01', UTC_TIMESTAMP())

2020-03-13T13:14:36.052590Z 9 Close stmt

格式化(不安全选项)

如果你100%确定查询参数不存在SQL注入问题,那么请选择这个选项。

这是一个快速的操作,因为它只发出一个网络往返(Query)。使用适当的参数格式化器,用fmt.Sprintf 函数来格式化查询。

func Create(args ...interface{}) error {
	ctx, cancel := context.WithTimeout(context.Background(), 3 * time.Second)
	defer cancel()

    q := fmt.Sprintf(`
INSERT INTO leagues
(uuid, name, address, int_rank, is_active, founded_at, created_at)
VALUES
('%s', '%s', '%s', %d, %t, '%s', UTC_TIMESTAMP())
`,
		args...,
	)

	res, err := db.ExecContext(ctx, q)
	if err != nil {
		return err
	}

	tot, err := res.RowsAffected()
	if err != nil {
		return err
	}

	if tot != 1 {
		return errors.New("no rows were affected")
	}

	return nil
}
2020-03-13T13:30:26.812083Z 15 Query INSERT INTO leagues
(uuid, name, address, int_rank, is_active, founded_at, created_at)
VALUES
('2250b0d9-365a-4655-a116-1b092f6d6f20', 'League 1', 'Address', 2, false, '2001-01-01', UTC_TIMESTAMP())

注意

尽管我们使用context.WithTimeout 来取消长期运行的查询,但由于其协议性质,MySQL实际上不会终止基础查询。取消只发生在Go级别。然而,你可以通过使用MySQL的MAX_EXECUTION_TIME查询提示来解决SELECT 查询的这个问题,以便告诉MySQL服务器,如果它运行的时间超过了给定的时间限制,就终止执行。如果是这样,它将产生Error 3024: Query execution was interrupted, maximum statement execution time exceeded 错误。

选择一

预备(安全选项)

如果你不能100%确定查询参数是没有SQL注入的。

这是一个缓慢的操作,因为它是网络往返次数的三倍(Prepare,ExecuteClose )。该查询使用? 参数占位符。

无论查询参数是动态的还是静态的,它仍然会使网络往返次数增加三倍。

type League struct {
	ID        sql.NullInt64
	UUID      sql.NullString
	Name      sql.NullString
	Address   sql.NullString
	IntRank   sql.NullInt64
	IsActive  sql.NullBool
	FoundedAt mysql.NullTime
	CreatedAt mysql.NullTime
	DeletedAt mysql.NullTime
}
func Read(uuid string) (League, bool, error) {
	ctx, cancel := context.WithTimeout(context.Background(), 3 * time.Second)
	defer cancel()

	row := db.QueryRowContext(ctx, `
SELECT
/*+ MAX_EXECUTION_TIME(3000) */
id, uuid, name, int_rank, address, is_active, founded_at, created_at, deleted_at
FROM leagues
WHERE
uuid = ?
LIMIT 1`,
		uuid,
	)

	var model League

	err := row.Scan(
		&model.ID,
		&model.UUID,
		&model.Name,
		&model.IntRank,
		&model.Address,
		&model.IsActive,
		&model.FoundedAt,
		&model.CreatedAt,
		&model.DeletedAt,
	)

	switch {
	case err == sql.ErrNoRows:
		return model, false, nil // 404
	case err != nil:
		return model, false, err // 500
	default:
		return model, true, nil  // 200
	}
}
2020-03-13T16:00:11.624465Z 16 Prepare SELECT
id, uuid, name, int_rank, address, is_active, founded_at, created_at, deleted_at
FROM leagues
WHERE
uuid = ?
LIMIT 1

2020-03-13T16:00:11.643215Z 16 Execute SELECT
id, uuid, name, int_rank, address, is_active, founded_at, created_at, deleted_at
FROM leagues
WHERE
uuid = '2250b0d9-365a-4655-a116-1b092f6d6f20'
LIMIT 1

2020-03-13T16:00:11.674942Z 16 Close stmt

格式化(不安全选项)

如果你100%确定查询参数是没有SQL注入的。

这是一个快速的操作,因为它只发出一个网络往返(Query)。使用适当的参数格式化器,用fmt.Sprintf 函数对查询进行格式化。

func Read(uuid string) (League, bool, error) {
	ctx, cancel := context.WithTimeout(context.Background(), 3 * time.Second)
	defer cancel()

	q := fmt.Sprintf(`
SELECT
/*+ MAX_EXECUTION_TIME(3000) */
id, uuid, name, int_rank, address, is_active, founded_at, created_at, deleted_at
FROM leagues
WHERE
uuid = '%s'
LIMIT 1
`,
		uuid,
	)

	row := db.QueryRowContext(ctx, q)

	var model League

	err := row.Scan(
		&model.ID,
		&model.UUID,
		&model.Name,
		&model.IntRank,
		&model.Address,
		&model.IsActive,
		&model.FoundedAt,
		&model.CreatedAt,
		&model.DeletedAt,
	)

	switch {
	case err == sql.ErrNoRows:
		return model, false, nil // 404
	case err != nil:
		return model, false, err // 500
	default:
		return model, true, nil  // 200
	}
}
2020-03-13T16:06:49.526538Z 18 Query SELECT
id, uuid, name, int_rank, address, is_active, founded_at, created_at, deleted_at
FROM leagues
WHERE
uuid = '2250b0d9-365a-4655-a116-1b092f6d6f20'
LIMIT 1

选择 "大量"(SELECT MANY)

预备(安全选项)

如果你不能100%确定查询参数是没有SQL注入的。

这是一个缓慢的操作,因为它是网络往返次数的三倍(Prepare,ExecuteClose)。该查询使用? 参数占位符。

无论查询参数是动态的还是静态的,它仍然会使网络往返次数增加三倍。

type League struct {
	ID        sql.NullInt64
	UUID      sql.NullString
	Name      sql.NullString
	Address   sql.NullString
	IntRank   sql.NullInt64
	IsActive  sql.NullBool
	FoundedAt mysql.NullTime
	CreatedAt mysql.NullTime
	DeletedAt mysql.NullTime
}
func Read(limit, offset int) ([]League, error) {
	ctx, cancel := context.WithTimeout(context.Background(), 3 * time.Second)
	defer cancel()

	rows, err := db.QueryContext(ctx, `
SELECT
/*+ MAX_EXECUTION_TIME(3000) */
id, uuid, name, int_rank, address, is_active, founded_at, created_at, deleted_at
FROM leagues
LIMIT ?
OFFSET ?`,
		limit,
		offset,
	)

	var models []League

	if err != nil {
		return models, err // 500
	}
	defer rows.Close()

	for rows.Next() {
		var model League

		err := rows.Scan(
			&model.ID,
			&model.UUID,
			&model.Name,
			&model.IntRank,
			&model.Address,
			&model.IsActive,
			&model.FoundedAt,
			&model.CreatedAt,
			&model.DeletedAt,
		)
		if err != nil {
			return models, err // 500
		}

		models = append(models, model)
	}
	if err = rows.Err(); err != nil {
		return models, err // 500
	}

	return models, nil
}
2020-03-13T16:35:51.216389Z 22 Prepare SELECT
id, uuid, name, int_rank, address, is_active, founded_at, created_at, deleted_at
FROM leagues
LIMIT ?
OFFSET ?

2020-03-13T16:35:51.223332Z 22 Execute SELECT
id, uuid, name, int_rank, address, is_active, founded_at, created_at, deleted_at
FROM leagues
LIMIT 10
OFFSET 0

2020-03-13T16:35:51.245423Z 22 Close stmt	

格式化(不安全选项)

如果你100%确定查询参数是没有SQL注入的。

这是一个快速的操作,因为它只发出一个网络往返(Query)。使用适当的参数格式化器,用fmt.Sprintf 函数对查询进行格式化。

func Read(limit, offset int) ([]League, error) {
	ctx, cancel := context.WithTimeout(context.Background(), 3 * time.Second)
	defer cancel()

	q := fmt.Sprintf(`
SELECT
/*+ MAX_EXECUTION_TIME(3000) */
id, uuid, name, int_rank, address, is_active, founded_at, created_at, deleted_at
FROM leagues
LIMIT %d
OFFSET %d
`,
		limit,
		offset,
	)

	rows, err := db.QueryContext(ctx, q)

	var models []League

	if err != nil {
		return models, err // 500
	}
	defer rows.Close()

	for rows.Next() {
		var model League

		err := rows.Scan(
			&model.ID,
			&model.UUID,
			&model.Name,
			&model.IntRank,
			&model.Address,
			&model.IsActive,
			&model.FoundedAt,
			&model.CreatedAt,
			&model.DeletedAt,
		)
		if err != nil {
			return models, err // 500
		}

		models = append(models, model)
	}
	if err = rows.Err(); err != nil {
		return models, err // 500
	}

	return models, nil
}
2020-03-13T16:39:42.933817Z 23 Query SELECT
id, uuid, name, int_rank, address, is_active, founded_at, created_at, deleted_at
FROM leagues
LIMIT 10
OFFSET 0