SQLX 库
sqlx
是 Go 的软件包,它在出色的内置 database/sql
软件包的基础上提供了一组扩展。
该库兼容sql原生包,同时又提供了更为强大的、优雅的查询、插入函数。
该库提供四个处理类型,分别是:
- sqlx.DB – 类似原生的 sql.DB;
- sqlx.Tx – 类似原生的 sql.Tx;
- sqlx.Stmt – 类似原生的 sql.Stmt, 准备 SQL 语句操作;
- sqlx.NamedStmt – 对特定参数命名并绑定生成 SQL 语句操作。
提供两个游标类型,分别是:
- sqlx.Rows – 类似原生的 sql.Rows, 从 Queryx 返回;
sqlx.Row – 类似原生的 sql.Row, 从 QueryRowx 返回。
可以将Rows内容解析至struct(支持内嵌)、map、slice
- 命名参数支持
- Get/Select可以快速将查询结果转为为struct/slice
handle类型
sqlx最大可能去实现database/sql一样的功能。有4中主要handle类型:
- sqlx.DB - 相当于database/sql中的sql.DB,代表一个数据库。
- sqlx.Tx - 相当于database/sql中的sql.Tx,代表一个事务。
- sqlx.Stmt = 相当于database/sql中的sql.Stmt,代表一条要执行的语句。
- sqlx.NamedStmt - 代表一条有参数的执行语句。
所有handle类型内嵌实现了对应database/sql中handle。也就是说,当你在代码中调用sqlx.DB.Query的时候,同时也会调用执行到sql.DB.Query对应的代码。
除此之外,还有两种指针类型:
- sqlx.Rows - 相当于sql.Rows, 从Queryx返回的指针;
- sqlx.Row - 相当于sql.Row, 从QueryRowx返回的结果;
跟handle类型一样,sqlx.Rows内嵌了sql.Rows。犹豫sql.Row的底层实现未公开,sqlx.Row只是实现了sql.Row的部分标准接口。
bindvars
查询占位符?在内部称为bindvars(查询占位符),它非常重要。你应该始终使用它们向数据库发送值,因为它们可以防止SQL注入攻击。database/sql不尝试对查询文本进行任何验证;它与编码的参数一起按原样发送到服务器。除非驱动程序实现一个特殊的接口,否则在执行之前,查询是在服务器上准备的。因此bindvars是特定于数据库的:
- MySQL中使用 ?
- PostgreSQL使用枚举的$1、$2等bindvar语法
- SQLite中?和$1的语法都支持
- Oracle中使用:name的语法
bindvars的一个常见误解是,它们用来在sql语句中插入值。它们其实仅用于参数化,不允许更改SQL语句的结构。例如,使用bindvars尝试参数化列或表名将不起作用:
// ?不能用来插入表名(做SQL语句中表名的占位符)
db.Query("SELECT * FROM ?", "mytable")
// ?也不能用来插入列名(做SQL语句中列名的占位符)
db.Query("SELECT ?, ? FROM people", "name", "location")
安装与使用
安装 SQLX 库
go get github.com/jmoiron/sqlx
go get github.com/go-sql-driver/mysql # mysql驱动包
使用操作
Open/Connect
- Open可能仅校验参数,而没有与db间创建连接,要确认db是否可用,需要调用Ping
- Connect则相当于Open+Ping
使用如下:
db, err := sqlx.Open("postgres", "user=foo dbname=bar sslmode=disable")
if err != nil {
log.Fatalln(err)
}
db, err := sqlx.Connect("mysql", "user:password@host:port?database")
if err != nil {
log.Fatalln(err)
}
// 初始化数据库
func initMySQL() (err error) {
dsn := "root:password@tcp(127.0.0.1:3306)/database"
db, err = sqlx.Open("mysql", dsn)
if err != nil {
fmt.Printf("connect server failed, err:%v\n", err)
return
}
db.SetMaxOpenConns(200) // 最大连接数
db.SetMaxIdleConns(10) // 最大空闲数
return db.Ping()
}
Get/Select
Get和Select是一个非常省时的扩展,可直接将结果赋值给结构体,其内部封装了StructScan进行转化。
- Get用于查询单条数据
- Select则用于查询多条数据
需要注意的是方法中的dest必须满足要求,Get中不能为nil,Select中必须为slice。
// 查询单条数据示例 结构体,Get
func getDemo() {
sqlStr := "select * from user where id=?"
var u User
err := db.Get(&u, sqlStr, 2)
if err != nil {
fmt.Printf("get failed err:%v\n", err)
return
}
fmt.Println(u.ID, u.Name, u.Age)
}
// select查询
func selectDemo() {
sqlStr := "select * from user where id > ?"
var users []User
err := db.Select(&users, sqlStr, 0)
if err != nil {
fmt.Printf("get failed err:%v\n", err)
return
}
fmt.Printf("users:%#v\n", users)
}
tag注意
使用struct需要注意,sqlx默认解析的 tag
为 db
,未设置tag,默认情况下是直接将field名转换为小写,因此默认情况下不满足需求时,需要注意设置field的tag名,否则可能因为不匹配而导致数据处理失败。
type Person struct {
FirstName string `db:"first_name"`
LastName string `db:"last_name"`
Email string
}
people := []Person{}
db.Select(&people, "SELECT * FROM person ORDER BY first_name ASC")
jason, john := people[0], people[1]
jason = Person{}
err = db.Get(&jason, "SELECT * FROM person WHERE first_name=$1", "Jason")
fmt.Printf("%#v\n", jason)
如果存在null的情况,则field的类型必须设置为对应的sql.Null*类型(sql.NullBool、sql.NullInt64、sql.NullString等)
type Place struct {
Country string
City sql.NullString
TelCode int
}
// if you have null fields and use SELECT *, you must use sql.Null* in your struct
places := []Place{}
err = db.Select(&places, "SELECT * FROM place ORDER BY telcode ASC")
if err != nil {
fmt.Println(err)
return
}
usa, singsing, honkers := places[0], places[1], places[2]
fmt.Printf("%#v\n%#v\n%#v\n", usa, singsing, honkers)
Query
Query是database/sql中执行查询主要使用的方法,该方法返回row结果。
Query返回一个sql.Rows对象和一个error对象。
在使用的时候应该吧Rows当成一个游标而不是一系列的结果。尽管数据库驱动缓存的方法不一样,通过Next()迭代每次获取一列结果,对于查询结果非常巨大的情况下,可以有效的限制内存的使用,Scan()利用reflect把sql每一列结果映射到go语言的数据类型如string,[]byte等。如果你没有遍历完全部的rows结果,一定要记得在把connection返回到连接池之前调用rows.Close()。
Query返回的error有可能是在server准备查询的时候发生的,也有可能是在执行查询语句的时候发生的。例如可能从连接池中获取一个坏的连级(尽管数据库会尝试10次去发现或创建一个工作连接)。一般来说,错误主要由错误的sql语句,错误的类似匹配,错误的域名或表名等。
在大部分情况下,Rows.Scan()会把从驱动获取的数据进行拷贝,无论驱动如何使用缓存。特殊类型sql.RawBytes可以用来从驱动返回的数据总获取一个zero-copy的slice byte。当下一次调用Next的时候,这个值就不在有效了,因为它指向的内存已经被驱动重写了别的数据。
Query使用的connection在所有的rows通过Next()遍历完后或者调用rows.Close()后释放。
Queryx
Queryx和Query行为很相似,不过返回一个sqlx.Rows对象,支持扩展的scan行为,同时可将对数据进行结构体转换。
QueryRow和QueryRowx
QueryRow和QueryRowx都是从数据库中获取一条数据,但是QueryRowx提供scan扩展,可直接将结果转换为结构体。
// Query使用的connection在所有的rows通过Next()遍历完后或者调用rows.Close()后释放
func queryDemo() {
rows, err := db.Query("select name, age from user")
if err != nil {
fmt.Println("query failed, error: ", err)
return
}
// //循环结果
for rows.Next() {
var name string
var age int
err = rows.Scan(&name, &age)
fmt.Println(name, age)
}
}
// Queryx和Query行为很相似,不过返回一个sqlx.Rows对象
//支持扩展的scan行为,同时可将对数据进行结构体转换
func queryxDemo() {
rows, err := db.Queryx("select id, name, age from user")
if err != nil {
fmt.Println("queryx failed, error: ", err)
return
}
defer rows.Close()
// //循环结果
for rows.Next() {
var u User
err = rows.StructScan(&u)
fmt.Println(u)
}
}
//QueryRow和QueryRowx
//QueryRow和QueryRowx都是从数据库中获取一条数据,但是QueryRowx提供scan扩展,可直接将结果转换为结构体。
func queryRowAndQueryRowx() {
row := db.QueryRow("select id, name, age FROM user where id = ?",3) // QueryRow返回错误,错误通过Scan返回
var id int
var name string
var age int
err :=row.Scan(&id,&name,&age)
if err != nil{
fmt.Println(err)
}
fmt.Printf("this is QueryRow res:[%d:%s:%d]\n",id,name,age)
var u User
err = db.QueryRowx("select id, name, age FROM user where id = ?",2).StructScan(&u)
if err != nil{
fmt.Println("QueryRowx error :",err)
}else {
fmt.Printf("this is QueryRowx res:%v",u)
}
}
Exec
Exec 和MustExec 从数据库连接池获取一个连接,然后在服务器上执行对应的sql语句。对于不支持即时查询(ad-hoc query)的数据库驱动,系统会自动新建一个预定义语句(prepared statement)然后执行对应sql操作。结果返回以后立即释放连接到连接池。
schema := `CREATE TABLE place (
country text,
city text NULL,
telcode integer);`
// 直接执行sql操作
result, err := db.Exec(schema)
// 或者,通过MustExec执行,错误时抛出panic异常
cityState := `INSERT INTO place (country, telcode) VALUES (?, ?)`
countryCity := `INSERT INTO place (country, city, telcode) VALUES (?, ?, ?)`
db.MustExec(cityState, "Hong Kong", 852)
db.MustExec(cityState, "Singapore", 65)
db.MustExec(countryCity, "South Africa", "Johannesburg", 27)
Exec执行sql语句而不返回rows,主要用于insert、update、delete。
返回结果result依赖于不同的驱动,一般包括两部分数据:LastInsertedId() 或 RowsAffected()。
例如,
- MySQL中,可以通过 LastInsertedId() 在插入操作后直接获取自增以后的关键值;
- PostgreSQL中,只能通过标准的 RETURNING 语句来获取。
// Exec
func execInsert() {
sqlStr := "insert into user (name, age) values(?, ?)"
ret, err := db.Exec(sqlStr, "Jerry", 18)
if err != nil {
fmt.Printf("insert failed, err:%v\n", err)
return
}
theId, err := ret.LastInsertId()
if err != nil {
fmt.Println(err)
}
fmt.Println(theId)
}
func execUpdate() {
sqlStr := "update user set age=? where id = ?"
ret, err := db.Exec(sqlStr, 22, 1)
if err != nil {
fmt.Printf("update failed, err:%v\n", err)
return
}
// 影响的行数
count, err := ret.RowsAffected()
if err != nil {
fmt.Println(err)
}
fmt.Println(count)
}
func execDelete() {
sqlStr := "delete from user where id=?"
ret, err := db.Exec(sqlStr, 1)
if err != nil {
fmt.Println(err)
}
count, err := ret.RowsAffected()
if err != nil {
fmt.Println(err)
}
fmt.Println(count)
}
开启事务
对应database/sql有Beginx、Preparex、Stmtx,带有Must前缀的方法,若发生错误则会panic
tx := db.MustBegin()
tx.MustExec("INSERT INTO person (first_name, last_name, email) VALUES ($1, $2, $3)", "Jason", "Moiron", "jmoiron@jmoiron.net")
tx.MustExec("INSERT INTO person (first_name, last_name, email) VALUES ($1, $2, $3)", "John", "Doe", "johndoeDNE@gmail.net")
tx.MustExec("INSERT INTO place (country, city, telcode) VALUES ($1, $2, $3)", "United States", "New York", "1")
tx.MustExec("INSERT INTO place (country, telcode) VALUES ($1, $2)", "Hong Kong", "852")
tx.MustExec("INSERT INTO place (country, telcode) VALUES ($1, $2)", "Singapore", "65")
// Named queries can use structs, so if you have an existing struct (i.e. person := &Person{}) that you have populated, you can pass it in as &person
tx.NamedExec("INSERT INTO person (first_name, last_name, email) VALUES (:first_name, :last_name, :email)", &Person{"Jane", "Citizen", "jane.citzen@example.com"})
tx.Commit()
命名参数支持
支持对sql中的命名参数解析支持,命名参数的格式为:name,执行时会主动获取对应name的值。
// Named queries, using `:name` as the bindvar. Automatic bindvar support
// which takes into account the dbtype based on the driverName on sqlx.Open/Connect
_, err = db.NamedExec(`INSERT INTO person (first_name,last_name,email) VALUES (:first,:last,:email)`,
map[string]interface{}{
"first": "Bin",
"last": "Smuth",
"email": "bensmith@allblacks.nz",
})
// Selects Mr. Smith from the database
rows, err = db.NamedQuery(`SELECT * FROM person WHERE first_name=:fn`, map[string]interface{}{"fn": "Bin"})
// Named queries can also use structs. Their bind names follow the same rules
// as the name -> db mapping, so struct fields are lowercased and the `db` tag
// is taken into consideration.
rows, err = db.NamedQuery(`SELECT * FROM person WHERE first_name=:first_name`, jason)
sqlx.In
sqlx.In批量插入
// User 结构体实现driver.Valuer接口:
func (u User) Value() (driver.Value, error) {
return []interface{}{u.Name, u.Age}, nil
}
// BatchInsertUsers2 使用sqlx.In帮我们拼接语句和参数, 注意传入的参数是[]interface{}
func BatchInsertUsers2(users []interface{}) error {
query, args, _ := sqlx.In(
"INSERT INTO user (name, age) VALUES (?), (?), (?)",
users..., // 如果arg实现了 driver.Valuer, sqlx.In 会通过调用 Value()来展开它
)
fmt.Println(query) // 查看生成的querystring // INSERT INTO user (name, age) VALUES (?, ?), (?, ?), (?, ?)
fmt.Println(args) // 查看生成的args // [西瓜 18 yy 23 kk 24]
_, err := db.Exec(query, args...)
return err
}
func testBatchInsertUsers2() {
u1 := User{Name: "XX", Age: 18}
u2 := User{Name: "ZZ", Age: 23}
u3 := User{Name: "JJ", Age: 24}
users := []interface{}{u1, u2, u3}
err := BatchInsertUsers2(users)
if err != nil {
fmt.Printf("BatchInsertUsers2 failed, err:%v\n", err)
}
}
// BatchInsertUsers3 使用NamedExec实现批量插入
func BatchInsertUsers3(users []*User) error {
_, err := db.NamedExec("INSERT INTO user (name, age) VALUES (:name, :age)", users)
return err
}
func testBatchInsertUsers3() {
u1 := User{Name: "西瓜", Age: 18}
u2 := User{Name: "yy", Age: 23}
u3 := User{Name: "kk", Age: 24}
users := []*User{&u1, &u2, &u3}
err := BatchInsertUsers3(users)
if err != nil {
fmt.Printf("BatchInsertUsers3 failed, err:%v\n", err)
}
}
sqlx.In查询
sqlx查询语句中实现In查询和FIND_IN_SET函数。即实现
- SELEC * FROM user WHERE id in (3, 2, 1);
- SELECT * FROM user WHERE id in (3, 2, 1) ORDER BY FIND_IN_SET(id, ‘3,2,1’);
区别:
- in查询, 无法自定义顺序,默认id从小到大顺序
- FIND_IN_SET函数(给定顺序)
查询id在给定id集合的数据并维持给定id集合的顺序。
// 无法自定义顺序,默认id从小到大顺序
func queryByIDs(ids []int) (users []User, err error) {
query , args, err := sqlx.In("select * from user where id in (?)", ids)
if err != nil {
fmt.Println(err)
return
}
// sqlx.In 返回带 `?` bindvar的查询语句, 我们使用Rebind()重新绑定它
query = db.Rebind(query)
err = db.Select(&users, query, args...)
return
}
func testQueryByIDs() {
// 无法自定义顺序,默认id从小到大顺序
users, err := queryByIDs([]int {7, 3, 6, 2})
if err != nil {
fmt.Printf("QueryByIDs failed, err:%v\n", err)
return
}
for _, user := range users {
fmt.Printf("user:%#v\n", user)
}
/**
user:main.User{ID:2, Age:20, Name:"tom"}
user:main.User{ID:3, Age:19, Name:"jack"}
user:main.User{ID:6, Age:18, Name:"Jerry"}
user:main.User{ID:7, Age:18, Name:"Jerry"}
*/
}
// QueryAndOrderByIDs 按照指定id查询并维护顺序
func QueryAndOrderByIDs(ids []int) (users []User, err error) {
// 动态填充id
strIDs := make([]string, 0, len(ids))
for _, id := range ids {
strIDs = append(strIDs, fmt.Sprintf("%d", id))
}
// FIND_IN_SET维护顺序
// mysql中find_in_set()函数的使用及in()用法详解 https://www.jb51.net/article/143105.htm
query, args, err := sqlx.In("SELECT * FROM user WHERE id IN (?) ORDER BY FIND_IN_SET(id, ?)", ids, strings.Join(strIDs, ","))
if err != nil {
return
}
// sqlx.In 返回带 `?` bindvar的查询语句, 我们使用Rebind()重新绑定它
query = db.Rebind(query)
err = db.Select(&users, query, args...)
return
}
func testQueryAndOrderByIDs() {
// 1. 用代码去做排序
// 2. 让MySQL排序
fmt.Println("----")
users, err := QueryAndOrderByIDs([]int{2, 6, 3, 4}) // 维护id查询顺序
if err != nil {
fmt.Printf("QueryAndOrderByIDs failed, err:%v\n", err)
return
}
for _, user := range users {
fmt.Printf("user:%#v\n", user)
}
/**
user:main.User{ID:2, Age:20, Name:"tom"}
user:main.User{ID:6, Age:18, Name:"Jerry"}
user:main.User{ID:3, Age:19, Name:"jack"}
user:main.User{ID:4, Age:18, Name:"Jerry"}
*/
}
全部代码
package main
import (
"database/sql/driver"
"fmt"
_ "github.com/go-sql-driver/mysql" // 注册MySQL驱动
"github.com/jmoiron/sqlx"
"strings"
)
var db *sqlx.DB
func initDB() (err error) {
dsn := "root:Abcdef@123456@tcp(127.0.0.1:3306)/ueumd_test?charset=utf8mb4&parseTime=True"
// Connect包含了open和ping方法,也可以使用MustConnect连接不成功就panic
db, err = sqlx.Connect("mysql", dsn)
if err != nil {
fmt.Printf("connect DB failed, err:%v\n", err)
return
}
db.SetMaxOpenConns(200)
db.SetMaxIdleConns(10)
return db.Ping()
}
type User struct {
ID int `db:"id"`
Age int `db:"age"`
Name string `db:"name"`
}
//Get和Select是一个非常省时的扩展,可直接将结果赋值给结构体,其内部封装了StructScan进行转化。
//Get用于获取单个结果然后Scan,Select用来获取结果切片。
// 查询单条数据示例 结构体,Get
func getDemo() {
sqlStr := "select * from user where id=?"
var u User
err := db.Get(&u, sqlStr, 2)
if err != nil {
fmt.Printf("get failed err:%v\n", err)
return
}
fmt.Println(u.ID, u.Name, u.Age)
}
// select查询
func selectDemo() {
sqlStr := "select * from user where id > ?"
var users []User
err := db.Select(&users, sqlStr, 0)
if err != nil {
fmt.Printf("get failed err:%v\n", err)
return
}
fmt.Printf("users:%#v\n", users)
}
// Exec
func execInsert() {
sqlStr := "insert into user (name, age) values(?, ?)"
ret, err := db.Exec(sqlStr, "Jerry", 18)
if err != nil {
fmt.Printf("insert failed, err:%v\n", err)
return
}
theId, err := ret.LastInsertId()
if err != nil {
fmt.Println(err)
}
fmt.Println(theId)
}
func execUpdate() {
sqlStr := "update user set age=? where id = ?"
ret, err := db.Exec(sqlStr, 22, 1)
if err != nil {
fmt.Printf("update failed, err:%v\n", err)
return
}
// 影响的行数
count, err := ret.RowsAffected()
if err != nil {
fmt.Println(err)
}
fmt.Println(count)
}
func execDelete() {
sqlStr := "delete from user where id=?"
ret, err := db.Exec(sqlStr, 1)
if err != nil {
fmt.Println(err)
}
count, err := ret.RowsAffected()
if err != nil {
fmt.Println(err)
}
fmt.Println(count)
}
// Query使用的connection在所有的rows通过Next()遍历完后或者调用rows.Close()后释放
func queryDemo() {
rows, err := db.Query("select name, age from user")
if err != nil {
fmt.Println("query failed, error: ", err)
return
}
// //循环结果
for rows.Next() {
var name string
var age int
err = rows.Scan(&name, &age)
fmt.Println(name, age)
}
}
// Queryx和Query行为很相似,不过返回一个sqlx.Rows对象
//支持扩展的scan行为,同时可将对数据进行结构体转换
func queryxDemo() {
rows, err := db.Queryx("select id, name, age from user")
if err != nil {
fmt.Println("queryx failed, error: ", err)
return
}
defer rows.Close()
// //循环结果
for rows.Next() {
var u User
err = rows.StructScan(&u)
fmt.Println(u)
}
}
//QueryRow和QueryRowx
//QueryRow和QueryRowx都是从数据库中获取一条数据,但是QueryRowx提供scan扩展,可直接将结果转换为结构体。
func queryRowAndQueryRowx() {
row := db.QueryRow("select id, name, age FROM user where id = ?",3) // QueryRow返回错误,错误通过Scan返回
var id int
var name string
var age int
err :=row.Scan(&id,&name,&age)
if err != nil{
fmt.Println(err)
}
fmt.Printf("this is QueryRow res:[%d:%s:%d]\n",id,name,age)
var u User
err = db.QueryRowx("select id, name, age FROM user where id = ?",2).StructScan(&u)
if err != nil{
fmt.Println("QueryRowx error :",err)
}else {
fmt.Printf("this is QueryRowx res:%v",u)
}
}
// NamedQuery
//NamedQuery方法用来绑定SQL语句与结构体或map中的同名字段, 查询。
// Sqlx.In
func queryByIDs(ids []int) (users []User, err error) {
query , args, err := sqlx.In("select * from user where id in (?)", ids)
if err != nil {
fmt.Println(err)
return
}
// sqlx.In 返回带 `?` bindvar的查询语句, 我们使用Rebind()重新绑定它
query = db.Rebind(query)
err = db.Select(&users, query, args...)
return
}
func testQueryByIDs() {
// 无法自定义顺序,默认id从小到大顺序
users, err := queryByIDs([]int {7, 3, 6, 2})
if err != nil {
fmt.Printf("QueryByIDs failed, err:%v\n", err)
return
}
for _, user := range users {
fmt.Printf("user:%#v\n", user)
}
/**
user:main.User{ID:2, Age:20, Name:"tom"}
user:main.User{ID:3, Age:19, Name:"jack"}
user:main.User{ID:6, Age:18, Name:"Jerry"}
user:main.User{ID:7, Age:18, Name:"Jerry"}
*/
}
// QueryAndOrderByIDs 按照指定id查询并维护顺序
func QueryAndOrderByIDs(ids []int) (users []User, err error) {
// 动态填充id
strIDs := make([]string, 0, len(ids))
for _, id := range ids {
strIDs = append(strIDs, fmt.Sprintf("%d", id))
}
// FIND_IN_SET维护顺序
// mysql中find_in_set()函数的使用及in()用法详解 https://www.jb51.net/article/143105.htm
query, args, err := sqlx.In("SELECT * FROM user WHERE id IN (?) ORDER BY FIND_IN_SET(id, ?)", ids, strings.Join(strIDs, ","))
if err != nil {
return
}
// sqlx.In 返回带 `?` bindvar的查询语句, 我们使用Rebind()重新绑定它
query = db.Rebind(query)
err = db.Select(&users, query, args...)
return
}
func testQueryAndOrderByIDs() {
// 1. 用代码去做排序
// 2. 让MySQL排序
fmt.Println("----")
users, err := QueryAndOrderByIDs([]int{2, 6, 3, 4}) // 维护id查询顺序
if err != nil {
fmt.Printf("QueryAndOrderByIDs failed, err:%v\n", err)
return
}
for _, user := range users {
fmt.Printf("user:%#v\n", user)
}
}
// User 结构体实现driver.Valuer接口:
func (u User) Value() (driver.Value, error) {
return []interface{}{u.Name, u.Age}, nil
}
// BatchInsertUsers2 使用sqlx.In帮我们拼接语句和参数, 注意传入的参数是[]interface{}
func BatchInsertUsers2(users []interface{}) error {
query, args, _ := sqlx.In(
"INSERT INTO user (name, age) VALUES (?), (?), (?)",
users..., // 如果arg实现了 driver.Valuer, sqlx.In 会通过调用 Value()来展开它
)
fmt.Println(query) // 查看生成的querystring // INSERT INTO user (name, age) VALUES (?, ?), (?, ?), (?, ?)
fmt.Println(args) // 查看生成的args // [西瓜 18 yy 23 kk 24]
_, err := db.Exec(query, args...)
return err
}
func testBatchInsertUsers2() {
u1 := User{Name: "XX", Age: 18}
u2 := User{Name: "ZZ", Age: 23}
u3 := User{Name: "JJ", Age: 24}
users := []interface{}{u1, u2, u3}
err := BatchInsertUsers2(users)
if err != nil {
fmt.Printf("BatchInsertUsers2 failed, err:%v\n", err)
}
}
// BatchInsertUsers3 使用NamedExec实现批量插入
func BatchInsertUsers3(users []*User) error {
_, err := db.NamedExec("INSERT INTO user (name, age) VALUES (:name, :age)", users)
return err
}
func testBatchInsertUsers3() {
u1 := User{Name: "西瓜", Age: 18}
u2 := User{Name: "yy", Age: 23}
u3 := User{Name: "kk", Age: 24}
users := []*User{&u1, &u2, &u3}
err := BatchInsertUsers3(users)
if err != nil {
fmt.Printf("BatchInsertUsers3 failed, err:%v\n", err)
}
}
func main() {
if err := initDB(); err != nil {
fmt.Printf("init DB failed, err:%v\n", err)
return
}
fmt.Println("init DB success...")
//getDemo()
//selectDemo()
//execInsert()
//execUpdate()
//execDelete()
// queryDemo()
// queryxDemo()
//queryRowAndQueryRowx()
//testQueryByIDs()
//
//testQueryAndOrderByIDs()
testBatchInsertUsers2()
// testBatchInsertUsers3()
}
参考连接