当前位置: 首页 > article >正文

【GO基础学习】Go操作数据库MySQL

文章目录

  • 建立测试表
  • 使用模版
  • insert操作
  • update操作
  • delete操作
  • select操作
  • 小结
  • SQL预处理
  • 事务
  • sqlx
    • 增删改成操作
    • 事务
    • sqlx.In 的使用


建立测试表

mysql数据库里面新建测试表person、place 表,方便后面测试:

CREATE TABLE `person` (
    `user_id` int(11) NOT NULL AUTO_INCREMENT,
    `username` varchar(260) DEFAULT NULL,
    `sex` varchar(260) DEFAULT NULL,
    `email` varchar(260) DEFAULT NULL,
    PRIMARY KEY (`user_id`)
  ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

CREATE TABLE place (
    country varchar(200),
    city varchar(200),
    telcode int
)ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

使用模版

先上怎么使用,然后解读每一步的操作,最后上一些详细的操作。

简单的操作MySQL的模版

package main

import (
	"database/sql"
	"fmt"
	"time"
	"log"

	_ "github.com/go-sql-driver/mysql"
)

// 定义一个全局对象db
var db *sql.DB

// 定义一个初始化数据库的函数
func initMySQLDB() (err error) {
	// DSN:Data Source Name
	dsn := "user:password@tcp(127.0.0.1:3306)/sql_test?charset=utf8mb4&parseTime=True"
	// 不会校验账号密码是否正确
	// 注意!!!这里不要使用:=,我们是给全局变量赋值,然后在main函数中使用全局变量db
	// 打开数据库连接(但不校验)
	db, err = sql.Open("mysql", dsn)
	if err != nil {
		return fmt.Errorf("sql.Open failed: %v", err)
	}

	// 校验数据库连接
	if err = db.Ping(); err != nil {
		return fmt.Errorf("db.Ping failed: %v", err)
	}

	// 设置数据库连接池参数
	db.SetMaxOpenConns(10)           // 最大打开连接数
	db.SetMaxIdleConns(5)            // 最大空闲连接数
	db.SetConnMaxLifetime(time.Hour) // 每个连接的生命周期

	fmt.Println("Database connection initialized successfully.")
	return nil
}


func main() {
	// 初始化数据库
	if err := initMySQLDB(); err != nil {
		log.Fatalf("initMySQLDB failed: %v\n", err)
	}
	defer db.Close() // 程序退出时关闭数据库连接

	// 使用db进行数据库的增删改查操作...

}

  1. database/sql 是 Go 的标准库,提供了与 SQL 数据库交互的接口,它以高度抽象的方式操作数据库,但需要具体的驱动来完成底层实现,如 go-sql-driver/mysql
  2. github.com/go-sql-driver/mysql是 MySQL 的驱动程序,它实现了 database/sql 的接口,并通过 MySQL 的 C/S 协议与数据库通信。

可以通过go get -u github.com/go-sql-driver/mysql或者go mod tidy下载库:
在这里插入图片描述

go-sql-driver/mysql 的导入中,使用匿名导入-,作用是调用驱动的 init 函数:

func init() {
	sql.Register("mysql", &MySQLDriver{})
}

sql.Register

  • 注册驱动,建立 driverName 和驱动实现的映射关系。
  • 当调用 sql.Open(“mysql”, …) 时,通过这个映射找到对应的驱动。

MySQLDriver 实现了 driver.Driver 接口:

  • Open(name string) (driver.Conn, error):建立与数据库的连接。
  1. 数据库对象

(1)sql.DB

  • 代表与数据库的连接池(不是单个连接)。
  • 提供线程安全的操作,管理连接复用、超时、连接关闭等。
  • 常用方法:
    • Open(driverName, dataSourceName string): 打开数据库连接。
    • Ping(): 测试连接是否可用。
    • Query()Exec():执行查询和非查询操作。

(2)sql.Tx

  • 代表事务,提供对事务操作的封装。

(3)sql.Rows

  • 代表查询返回的多行结果。

(4)sql.Row

  • 代表查询返回的单行结果。
  1. db, err = sql.Open("mysql", dns)

(1)sql.Open

  • 检查驱动是否注册。
  • 初始化 sql.DB 对象(不建立实际连接,仅配置连接信息)。
  • 返回一个连接池对象,连接池会在需要时与数据库建立连接。

(2)数据源名称(DSN)的解析

  • DSN 是连接信息的字符串形式:
 user:password@protocol(address)/dbname?param1=value1&param2=value2
  • 驱动负责解析这些信息,构建与数据库通信的上下文。

(3)连接建立

  • 当执行 db.Ping() 或第一次 Query 时,sql.DB 会通过驱动的 Open 方法与数据库建立物理连接。
  • go-sql-driver/mysql 解析 DSN 后,通过 TCP 或 Unix 套接字与 MySQL 服务器通信。
  1. 数据库连接池的管理
    sql.DB 管理了一个连接池,提供连接复用、超时管理等功能:
  • 最大连接数

    • SetMaxOpenConns(n int):设置最大打开连接数。如果n大于0且小于最大闲置连接数,会将最大闲置连接数减小到匹配最大开启连接数的限制。 如果n<=0,不会限制最大开启连接数,默认为0(无限制)。
  • 最大空闲连接数

    • SetMaxIdleConns(n int):设置连接池中的最大空闲连接数。如果n大于最大开启连接数,则新的最大闲置连接数会减小到匹配最大开启连接数的限制。 如果n<=0,不会保留闲置连接。
  • 连接生命周期

    • SetConnMaxLifetime(d time.Duration):设置连接的最大生存时间。

insert操作

插入、更新和删除操作都使用Exec方法。

func main() {
	// 初始化数据库
	if err := initMySQLDB(); err != nil {
		log.Fatalf("initMySQLDB failed: %v\n", err)
	}
	defer db.Close() // 程序退出时关闭数据库连接

	// 执行数据库操作示例
	insertRowDemo()
}

// insertRowDemo 插入数据示例
func insertRowDemo() {
	sqlStr := "INSERT INTO person(username, sex, email) VALUES (?, ?, ?)"
	result, err := db.Exec(sqlStr, "王五", "男", "XXX@qq.com")
	if err != nil {
		log.Printf("Insert failed: %v\n", err)
		return
	}

	// 获取新插入数据的 ID
	lastInsertID, err := result.LastInsertId()
	if err != nil {
		log.Printf("Get LastInsertId failed: %v\n", err)
		return
	}
	fmt.Printf("Insert success, new ID is %d.\n", lastInsertID)
}

在这里插入图片描述


update操作

func main() {
	// 初始化数据库
	if err := initMySQLDB(); err != nil {
		log.Fatalf("initMySQLDB failed: %v\n", err)
	}
	defer db.Close() // 程序退出时关闭数据库连接

	// 执行数据库操作示例
	updateRow(2)
}

// 更新数据
func updateRow(userID int) {
	sqlStr := "UPDATE person SET username = ?, email = ? WHERE user_id = ?"
	result, err := db.Exec(sqlStr, "李四", "XXX@162.com", userID)
	if err != nil {
		log.Printf("Update failed: %v\n", err)
		return
	}

	rowsAffected, err := result.RowsAffected()
	if err != nil {
		log.Printf("Get RowsAffected failed: %v\n", err)
		return
	}
	fmt.Printf("Update success, %d rows affected.\n", rowsAffected)
}

在这里插入图片描述


delete操作

func main() {
	// 初始化数据库
	if err := initMySQLDB(); err != nil {
		log.Fatalf("initMySQLDB failed: %v\n", err)
	}
	defer db.Close() // 程序退出时关闭数据库连接

	// 执行数据库操作示例
	deleteRow(2)
}


// 删除数据
func deleteRow(userID int) {
	sqlStr := "DELETE FROM person WHERE user_id = ?"
	result, err := db.Exec(sqlStr, userID)
	if err != nil {
		log.Printf("Delete failed: %v\n", err)
		return
	}

	rowsAffected, err := result.RowsAffected()
	if err != nil {
		log.Printf("Get RowsAffected failed: %v\n", err)
		return
	}
	fmt.Printf("Delete success, %d rows affected.\n", rowsAffected)
}

在这里插入图片描述


select操作

  1. 单条记录查询

// Person 定义用户信息结构体
type Person struct {
	UserID   int    `db:"user_id"`
	Username string `db:"username"`
	Sex      string `db:"sex"`
	Email    string `db:"email"`
}
// main 主函数
func main() {
	// 初始化数据库
	if err := initMySQLDB(); err != nil {
		log.Fatalf("initMySQLDB failed: %v\n", err)
	}
	defer db.Close() // 程序退出时关闭数据库连接

	// 执行数据库操作示例
	queryOneRow(3)
}

// 查询单条数据
func queryOneRow(userID int) {
	sqlStr := "SELECT user_id, username, sex, email FROM person WHERE user_id = ?"
	var person Person
	err := db.QueryRow(sqlStr, userID).Scan(&person.UserID, &person.Username, &person.Sex, &person.Email)
	if err != nil {
		if err == sql.ErrNoRows {
			log.Printf("No record found for userId: %d\n", userID)
			return
		}
		log.Printf("Query failed: %v\n", err)
		return
	}
	fmt.Printf("Query result: %+v\n", person)
}

Scan方法,已经关闭rows释放持有的数据库链接,不需要我们自己关闭了:
在这里插入图片描述
运行结果:

在这里插入图片描述

  1. 多条记录查询:
// 查询多条数据
func queryMultipleRows() {
	sqlStr := "SELECT userId, username, sex, email FROM person"
	rows, err := db.Query(sqlStr)
	if err != nil {
		log.Printf("Query failed: %v\n", err)
		return
	}
	defer rows.Close()

	for rows.Next() {
		var person Person
		err := rows.Scan(&person.UserID, &person.Username, &person.Sex, &person.Email)
		if err != nil {
			log.Printf("Row scan failed: %v\n", err)
			continue
		}
		fmt.Printf("Row: %+v\n", person)
	}

	// 检查遍历时是否遇到错误
	if err := rows.Err(); err != nil {
		log.Printf("Rows iteration error: %v\n", err)
	}
}

在这里插入图片描述


小结

  1. 插入、更新和删除操作都使用Exec方法,传入sql语句,以及sql里面需要的参数:
result, err := db.Exec(sql, args...)
  1. 单条记录查询:db.QueryRow(sql, args...).Scan(),使用了scan方法,会自动关闭连接,不需要手动关闭。
  2. 多条记录查询:rows, err := db.Query(sql),在使用rows.Next()遍历所有记录前,需要defer rows.Close(),防止遍历时中断,不能关闭所有链接。

SQL预处理

上面的示例中所有 SQL 参数均使用占位符 (?),避免了 SQL 注入的风险。

不正确的操作,sql注入示例:

// sql注入示例
func sqlInjectDemo(name string) {
	sqlStr := fmt.Sprintf("select id, name, age from user where name='%s'", name)
	fmt.Printf("SQL:%s\n", sqlStr)
	var u user
	err := db.QueryRow(sqlStr).Scan(&u.id, &u.name, &u.age)
	if err != nil {
		fmt.Printf("exec failed, err:%v\n", err)
		return
	}
	fmt.Printf("user:%#v\n", u)
}

传入下面字段都会引发sql注入:

sqlInjectDemo("xxx' or 1=1#")
sqlInjectDemo("xxx' union select * from user #")
sqlInjectDemo("xxx' and (select count(*) from user) <10 #")

关于预防sql注入,除了采用直接在 db.Execdb.Query 中使用占位符:

sqlStr := "SELECT * FROM person WHERE username = ? AND email = ?"
rows, err := db.Query(sqlStr, "Alice", "alice@example.com")

还可以采用db.Prepare,它是为多次执行相似语句设计的,它可以提前编译和缓存 SQL 语句:

stmt, err := db.Prepare("INSERT INTO person(username, sex, email) VALUES (?, ?, ?)")
if err != nil {
    log.Fatalf("Prepare failed: %v", err)
}
defer stmt.Close()

// 多次执行相似的 SQL 语句
_, err = stmt.Exec("Alice", "Female", "alice@example.com")
_, err = stmt.Exec("Bob", "Male", "bob@example.com")

关于这两种的选择:

单次操作: 如果只执行一次 SQL 语句,直接使用占位符(?)更方便。
多次操作: 如果需要多次执行相似的 SQL 语句(如批量插入或更新),使用 Prepare 可以提高性能。
安全性: 无论使用哪种方式,参数绑定的本质是由数据库驱动安全地处理的,因此都可以防止 SQL 注入。


事务

在数据库操作中,事务(Transaction)是一组操作的集合,这些操作要么全部成功,要么全部失败,确保数据库处于一致性状态。

事务的特性 (ACID)

  1. 原子性 (Atomicity):

    • 事务中的所有操作要么全部完成,要么全部不执行。
  2. 一致性 (Consistency):

    • 事务完成后,数据库状态从一个一致状态转变为另一个一致状态。
  3. 隔离性 (Isolation):

    • 一个事务的执行不能被其他事务干扰,事务之间互不影响。
  4. 持久性 (Durability):

    • 事务完成后,其对数据库的更改是永久性的。

Go 中的事务操作

在 Go 的 database/sql 包中,可以通过以下方式使用事务:

  1. 开启事务: db.Begin()
  2. 提交事务: tx.Commit()
  3. 回滚事务: tx.Rollback()

事务操作的基本流程

  1. 开启事务: 使用 db.Begin() 创建一个事务对象。

  2. 执行事务中的多条 SQL 操作: 使用事务对象 tx 的方法执行语句。

  3. 提交事务或回滚事务:

    • 如果所有操作成功,调用 tx.Commit() 提交事务。
    • 如果任何操作失败,调用 tx.Rollback() 回滚事务。

完整示例代码:

package main

import (
	"database/sql"
	"fmt"
	"log"

	_ "github.com/go-sql-driver/mysql"
)

// 定义一个全局对象 db
var db *sql.DB

// 初始化数据库连接
func initMySQLDB() error {
	var err error
	dsn := "user:password@tcp(127.0.0.1:3306)/bank?charset=utf8mb4&parseTime=True"
	db, err = sql.Open("mysql", dsn)
	if err != nil {
		return fmt.Errorf("sql.Open failed: %v", err)
	}

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

	fmt.Println("Database connected successfully.")
	return nil
}

// 转账操作
func transferMoney(fromAccountID, toAccountID int, amount float64) error {
	// 开启事务
	tx, err := db.Begin()
	if err != nil {
		return fmt.Errorf("begin transaction failed: %v", err)
	}

	// 执行操作:扣减转出账户余额
	sqlStr1 := "UPDATE accounts SET balance = balance - ? WHERE id = ? AND balance >= ?"
	_, err = tx.Exec(sqlStr1, amount, fromAccountID, amount)
	if err != nil {
		tx.Rollback() // 回滚事务
		return fmt.Errorf("deduct from account failed: %v", err)
	}

	// 执行操作:增加转入账户余额
	sqlStr2 := "UPDATE accounts SET balance = balance + ? WHERE id = ?"
	_, err = tx.Exec(sqlStr2, amount, toAccountID)
	if err != nil {
		tx.Rollback() // 回滚事务
		return fmt.Errorf("add to account failed: %v", err)
	}

	// 提交事务
	err = tx.Commit()
	if err != nil {
		return fmt.Errorf("commit transaction failed: %v", err)
	}

	fmt.Println("Transfer successful.")
	return nil
}

func main() {
	// 初始化数据库
	if err := initMySQLDB(); err != nil {
		log.Fatalf("Database initialization failed: %v", err)
	}
	defer db.Close()

	// 转账操作
	err := transferMoney(1, 2, 100.00)
	if err != nil {
		log.Printf("Transfer failed: %v\n", err)
	} else {
		fmt.Println("Transfer completed.")
	}
}


sqlx

sqlx 是 Go 社区流行的一个库,基于 Go 的标准库 database/sql 封装而成。sqlx 提供了许多增强功能,例如结构体绑定、便捷的查询操作等,使开发者更容易操作数据库。

sqlx 的优势

  1. 简化查询结果映射:

    • 支持将查询结果直接映射到结构体,省去手动 rows.Scan 的繁琐操作。
  2. Named Queries:

    • 支持命名参数,避免了大量占位符的混乱。
  3. 事务支持 :

    • 简化事务处理,与 database/sql 一致。
  4. 预加载/预处理:

    • 提供便捷的批量插入和动态查询构建。

增删改成操作

可以通过go get github.com/jmoiron/sqlx或者go mod tidy进行下载。

示例代码:

package main

import (
	"fmt"
	"log"

	_ "github.com/go-sql-driver/mysql"
	"github.com/jmoiron/sqlx"
)

var db *sqlx.DB

// 初始化数据库连接
func initMySQLDB() error {
	dsn := "user:password@tcp(127.0.0.1:3306)/testdb?charset=utf8mb4&parseTime=True"
	var err error
	db, err = sqlx.Connect("mysql", dsn) // 使用 sqlx.Connect
	if err != nil {
		return fmt.Errorf("failed to connect to database: %w", err)
	}

	// 设置连接池参数
	db.SetMaxOpenConns(10)
	db.SetMaxIdleConns(5)
	return nil
}

func main() {
	err := initMySQLDB()
	if err != nil {
		log.Fatalf("Database connection failed: %v", err)
	}
	defer db.Close()

	fmt.Println("Database connected successfully.")
}

插入、更新、删除操作上,基本和前面的相同,都使用Exec方法,传入sql语句,以及sql里面需要的参数:

func insertPerson(username, sex, email string) error {
	_, err := db.Exec("INSERT INTO person (username, sex, email) VALUES (?, ?, ?)", username, sex, email)
	if err != nil {
		return fmt.Errorf("insert failed: %w", err)
	}
	return nil
}

func updatePersonEmail(id int, email string) error {
	_, err := db.Exec("UPDATE person SET email = ? WHERE user_id = ?", email, id)
	if err != nil {
		return fmt.Errorf("update failed: %w", err)
	}
	return nil
}

func deletePerson(id int) error {
	_, err := db.Exec("DELETE FROM person WHERE user_id = ?", id)
	if err != nil {
		return fmt.Errorf("delete failed: %w", err)
	}
	return nil
}

查询上面,可以直接通过结构体进行映射,无需自己通过Scan函数一个一个映射,与前面调用QueryRowQuery和不同的是,这里是GetSelect方法:

type Person struct {
	UserID       int    `db:"user_id"`
	Username string `db:"username"`
	Sex      string `db:"sex"`
	Email    string `db:"email"`
}

// 单条记录
func getPersonByID(id int) (*Person, error) {
	var person Person
	err := db.Get(&person, "SELECT * FROM person WHERE user_id = ?", id)
	if err != nil {
		return nil, fmt.Errorf("query failed: %w", err)
	}
	return &person, nil
}
// 多条记录
func getAllPersons() ([]Person, error) {
	var persons []Person
	err := db.Select(&persons, "SELECT * FROM person")
	if err != nil {
		return nil, fmt.Errorf("query failed: %w", err)
	}
	return persons, nil
}

func main() {
	person, err := getPersonByID(1)
	if err != nil {
		log.Fatalf("Failed to get person: %v", err)
	}
	fmt.Printf("Person: %+v\n", person)

	persons, err2 := getAllPersons()
	if err2 != nil {
		log.Fatalf("Failed to get persons: %v", err2)
	}
	for _, pers := range persons {
		fmt.Printf("Person: %+v\n", pers)
	}
}


另外一个优势是:sqlx 支持命名参数(Named Queries),使代码更加可读:

(1)NamedExec方法用来绑定SQL语句与结构体或map中的同名字段。

func insertPersonNamed(username, sex, email string) error {
	sqlStr := "INSERT INTO person (username, sex, email) VALUES (:username, :sex, :email)"
	_, err := db.NamedExec(sqlStr, map[string]interface{}{
		"username": username,
		"sex":      sex,
		"email":    email,
	})
	if err != nil {
		return fmt.Errorf("insert failed: %w", err)
	}
	return nil
}

(2)NamedQuery 可以将查询的参数以结构体或映射(map[string]interface{})的形式传递。

type Person struct {
	UserID       int    `db:"user_id"`
	Username string `db:"username"`
	Sex      string `db:"sex"`
	Email    string `db:"email"`
}

// 使用命名参数查询单条记录
func getPersonByUsername(username string) (*Person, error) {
	sqlStr := "SELECT * FROM person WHERE username = :username"
	rows, err := db.NamedQuery(sqlStr, map[string]interface{}{
		"username": username,
	})
	if err != nil {
		return nil, fmt.Errorf("query failed: %w", err)
	}
	defer rows.Close()

	var person Person
	if rows.Next() {
		err = rows.StructScan(&person)
		if err != nil {
			return nil, fmt.Errorf("scan failed: %w", err)
		}
	}
	return &person, nil
}

func main() {
	person, err := getPersonByUsername("张三")
	if err != nil {
		log.Fatalf("Failed to get person: %v", err)
	}
	fmt.Printf("Person: %+v\n", person)
}

参数为结构体: 你也可以直接使用结构体传递参数(字段需要对应 SQL 中的命名参数):

func getPersonUsingStruct(p *Person) (*Person, error) {
	sqlStr := "SELECT * FROM person WHERE username = :username AND email = :email"
	rows, err := db.NamedQuery(sqlStr, p)
	if err != nil {
		return nil, fmt.Errorf("query failed: %w", err)
	}
	defer rows.Close()

	var person Person
	if rows.Next() {
		err = rows.StructScan(&person)
		if err != nil {
			return nil, fmt.Errorf("scan failed: %w", err)
		}
	}
	return &person, nil
}

func main() {
	input := &Person{Username: "张三", Email: "zhangsan@example.com"}
	person, err := getPersonUsingStruct(input)
	if err != nil {
		log.Fatalf("Failed to get person: %v", err)
	}
	fmt.Printf("Person: %+v\n", person)
}

事务

事务操作与 database/sql 基本一样:

func transferMoney(tx *sqlx.Tx, fromID, toID int, amount float64) error {
	// 扣减账户余额
	_, err := tx.Exec("UPDATE accounts SET balance = balance - ? WHERE id = ?", amount, fromID)
	if err != nil {
		return fmt.Errorf("deduct failed: %w", err)
	}

	// 增加账户余额
	_, err = tx.Exec("UPDATE accounts SET balance = balance + ? WHERE id = ?", amount, toID)
	if err != nil {
		return fmt.Errorf("add failed: %w", err)
	}
	return nil
}

func main() {
	tx, err := db.Beginx() // 开启事务
	if err != nil {
		log.Fatalf("Failed to begin transaction: %v", err)
	}

	err = transferMoney(tx, 1, 2, 100.00)
	if err != nil {
		tx.Rollback()
		log.Fatalf("Transaction failed: %v", err)
	}

	err = tx.Commit() // 提交事务
	if err != nil {
		log.Fatalf("Failed to commit transaction: %v", err)
	}

	fmt.Println("Transaction completed successfully.")
}

sqlx.In 的使用

sqlx.Insqlx 提供的一个工具函数,解决了 SQL 查询中 IN 子句传递动态参数的问题。标准库 database/sql 不支持直接传递切片作为 IN 子句的参数,而 sqlx.In 自动将切片展开为占位符并处理参数绑定。

(1)使用 sqlx.In 查询多条记录【需求: 查询多个用户的信息,用户 ID 动态传入】

func getPersonsByIDs(ids []int) ([]Person, error) {
	// 使用 sqlx.In 构建 SQL 语句
	query, args, err := sqlx.In("SELECT * FROM person WHERE user_id IN (?)", ids)
	if err != nil {
		return nil, fmt.Errorf("sqlx.In failed: %w", err)
	}

	// 将查询转换为数据库驱动支持的语句
	query = db.Rebind(query)

	var persons []Person
	err = db.Select(&persons, query, args...)
	if err != nil {
		return nil, fmt.Errorf("query failed: %w", err)
	}
	return persons, nil
}

func main() {
	ids := []int{1, 2, 3}
	persons, err := getPersonsByIDs(ids)
	if err != nil {
		log.Fatalf("Failed to get persons: %v", err)
	}
	for _, person := range persons {
		fmt.Printf("Person: %+v\n", person)
	}
}

(2)sqlx.In 插入数据【需求: 批量插入用户数据,使用动态参数。】

func insertPersons(persons []Person) error {
	// 构建批量插入语句和参数
	values := []interface{}{}
	for _, p := range persons {
		values = append(values, p.Username, p.Sex, p.Email)
	}

	query, args, err := sqlx.In("INSERT INTO person (username, sex, email) VALUES (?, ?, ?)", values...)
	if err != nil {
		return fmt.Errorf("sqlx.In failed: %w", err)
	}

	// Rebind 将 SQL 语句转换为当前数据库驱动支持的语法
	query = db.Rebind(query)

	// 执行插入
	_, err = db.Exec(query, args...)
	if err != nil {
		return fmt.Errorf("insert failed: %w", err)
	}
	return nil
}

func main() {
	persons := []Person{
		{Username: "Alice", Sex: "女", Email: "alice@example.com"},
		{Username: "Bob", Sex: "男", Email: "bob@example.com"},
	}
	err := insertPersons(persons)
	if err != nil {
		log.Fatalf("Failed to insert persons: %v", err)
	}
	fmt.Println("Batch insert successful!")
}



http://www.kler.cn/a/463476.html

相关文章:

  • 把vue项目或者vue组件发布成npm包或者打包成lib库文件本地使用
  • 设计心得——流程图和数据流图绘制
  • RP2K:一个面向细粒度图像的大规模零售商品数据集
  • SpringCloudAlibaba实战入门之路由网关Gateway过滤器(十三)
  • PostgreSQL 表达式
  • ASA第六天笔记
  • webpack打包node后端项目
  • 3维场景测试自动化
  • 18.2、网络安全评测技术与攻击
  • excel怎么删除右边无限列(亲测有效)
  • 青少年编程与数学 02-005 移动Web编程基础 15课题、移动应用开发
  • CentOS Stream 9 安装 JDK
  • 三分钟在你的react项目中引入tailwindcss
  • Vue.js组件开发-刷新当前页面方法
  • pyQT + OpenCV小练习
  • PyCharm专项训练5 最短路径算法
  • 【kubernetes组件合集】深入解析Kubernetes组件之三:client-go
  • 中国香港阿里云200M不限流量轻量云主机测试报告
  • 怎样用 Excel 做数据分析?
  • python-leetcode-删除有序数组中的重复项 II
  • SOME/IP 协议详解——远程过程调用(RPC)
  • python3GUI--网络流量分析系统 By:PyQt5
  • 电话男 AI 语音,用于变声器和文本
  • 从 Elastic 迁移到 Easysearch 指引
  • 探索Docker:解锁容器化的神奇世界
  • Quartz任务调度框架实现任务动态执行