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/sqlxgo 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。
// 查询单条数据示例 结构体,Getfunc getDemo() {sqlStr := "select * from user where id=?"var u Usererr := 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 []Usererr := 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 stringCity sql.NullStringTelCode int}// if you have null fields and use SELECT *, you must use sql.Null* in your structplaces := []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 stringvar age interr = 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 Usererr = 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 intvar name stringvar age interr :=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 Usererr = 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 语句来获取。
// Execfunc 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 &persontx.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 databaserows, 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) {// 动态填充idstrIDs := 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.htmquery, 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 mainimport ("database/sql/driver""fmt"_ "github.com/go-sql-driver/mysql" // 注册MySQL驱动"github.com/jmoiron/sqlx""strings")var db *sqlx.DBfunc initDB() (err error) {dsn := "root:Abcdef@123456@tcp(127.0.0.1:3306)/ueumd_test?charset=utf8mb4&parseTime=True"// Connect包含了open和ping方法,也可以使用MustConnect连接不成功就panicdb, 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用来获取结果切片。// 查询单条数据示例 结构体,Getfunc getDemo() {sqlStr := "select * from user where id=?"var u Usererr := 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 []Usererr := db.Select(&users, sqlStr, 0)if err != nil {fmt.Printf("get failed err:%v\n", err)return}fmt.Printf("users:%#v\n", users)}// Execfunc 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 stringvar age interr = 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 Usererr = 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 intvar name stringvar age interr :=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 Usererr = 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.Infunc 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) {// 动态填充idstrIDs := 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.htmquery, 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()}
参考连接
