[TOC]
Go语言编程快速入门之go-sql-driver操作MySQL数据库 快速了解 MySQL 数据库 MySQL 是目前主流关系型的数据库,它的胞胎兄弟 MariaDB (MySQL 的一个分支),除此之外使用最多的就是 Oracle 和 PostgreSQL 数据库。
SQL 语言类型:
DDL : 主要是操作数据库
DML : 主要进行表的增删改查
DCL : 主要进行用户和权限操作
MySQL 至此插件式的存储引擎,其常见存储引擎MyISAM 和 InnoDB: MyISAM 特点:
InnoDB 特点:
事务的特点即我们常说的ACID:
A(Atomicity)- 原子性 (多个语句要么全成功,要么即失败,将不会更改数据库的数据)
C(Consistence) - 一致性 (在每次提交或回滚之后以及正在进行的事务处理期间,数据库始终保持一致状态,要么全部旧值要么全部新值)
I(Isolation) - 隔离性 (事务之间的相互隔离的)
D(Durability) - 持久性 (事务操作的结果是不会丢失的)
1.MySQL驱动下载 描述: Go语言中的database/sql
包提供了保证SQL或类SQL数据库的泛用接口
, 并不提供具体的数据库驱动, 所以使用database/sql
包时必须注入(至少)一个数据库驱动。
Go语言中我们常用的数据库操作, 基本上都有完整的第三方实现,例如本节的MySQL驱动(https://github.com/go-sql-driver/mysql )
1 2 3 4 5 6 7 8 9 10 11 12 ➜ go get -u github.com/go-sql-driver/mysql go: downloading github.com/go-sql-driver/mysql v1.6.0 ➜ weiyigeek.top go get github.com/go-sql-driver/mysql ➜ weiyigeek.top pwd /home/weiyigeek/app/program/project/go/src/weiyigeek.top ➜ go-sql-driv pwd /home/weiyigeek/app/program/project/go/pkg/mod/github.com/go-sql-driv
2.MySQL驱动格式 描述: 使用MySQL驱动格式函数原型如下所示:
func Open(driverName, dataSourceName string) (*DB, error)
: Open方法是打开一个dirverName指定的数据库,dataSourceName指定数据源
,一般至少包括数据库文件名和其它连接必要的信息。
func (db *DB) SetMaxOpenConns(n int)
: SetMaxOpenConns方法是设置与数据库建立连接的最大数目。
如果n大于0且小于最大闲置连接数,会将最大闲置连接数减小到匹配最大开启连接数的限制。
如果n<=0,不会限制最大开启连接数,默认为0(无限制)。
func (db *DB) SetMaxIdleConns(n int)
: SetMaxIdleConns方法是设置连接池中的最大闲置连接数。
如果n大于最大开启连接数,则新的最大闲置连接数会减小到匹配最大开启连接数的限制。
如果n<=0,不会保留闲置连接。
基础示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 import ( "database/sql" "fmt" _ "github.com/go-sql-driver/mysql" ) func main () { dsn := "root:WwW.weiyigeek.top@tcp(10.20.172.248:3306)/test?charset=utf8&parseTime=True" db, err := sql.Open("mysql" , dsn) if err != nil { fmt.Printf("DSN : %s Format failed, Error: %v \n" , dsn, err) panic (err) } defer db.Close() err = db.Ping() if err != nil { fmt.Printf("Connection %s Failed, Error: %v \n" , dsn, err) return } fmt.Println("数据库连接成功!" ) }
Tips: 为什么上面代码中的defer db.Close()语句不应该写在if err != nil的前面呢?
3.MySQL初始化连接 描述: 上面的例子可以看到Open函数可能只是验证其参数格式是否正确,实际上并不创建与数据库的连接,此时我们如果要检查数据源的名称是否真实有效,应该调用Ping方法。
下述代码中sql.DB
是表示连接的数据库对象(结构体实例),它保存了连接数据库相关的所有信息。它内部维护着一个具有零到多个底层连接的连接池,它可以安全地被多个goroutine同时使用。
MySQL 用户密码更改:1 2 ALTER USER `root` @`%` IDENTIFIED BY 'weiyigeek.top' ;
初始化示例:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 package mainimport ( "database/sql" "fmt" _ "github.com/go-sql-driver/mysql" ) var db *sql.DBfunc initDB () (err error) { dsn := "root:weiyigeek.top@tcp(10.20.172.248:3306)/test?charset=utf8&parseTime=True" db, err = sql.Open("mysql" , dsn) if err != nil { fmt.Printf("DSN : %s Format failed\n %v \n" , dsn, err) return err } err = db.Ping() if err != nil { fmt.Printf("Connection %s Failed,\n%v \n" , dsn, err) return err } db.SetMaxOpenConns(1024 ) db.SetMaxIdleConns(0 ) fmt.Println("数据库初始化连接成功!" ) return nil } func main () { err := initDB() defer db.Close() if err != nil { fmt.Println("Database Init failed!" ) return } }
执行结果: 1 2 3 4 5 6 7 数据库初始化连接成功! Connection root:www.weiyigeek.top@tcp(10.20.172.248:3306)/test ?charset=utf8&parseTime=True Failed, Error 1045: Access denied for user 'root' @'10.20.172.108' (using password: YES) Database Init failed!
4.MySQL的CRUD操作 库表准备 我们首先需要在MySQL(8.x)数据库中创建一个名为test数据库
和一个user表
,SQL语句如下所示:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 CREATE DATABASE test ;USE test ;CREATE TABLE `user` ( `id` BIGINT (20 ) NOT NULL AUTO_INCREMENT, `name` VARCHAR (20 ) DEFAULT '' , `age` INT (11 ) DEFAULT '0' , PRIMARY KEY (`id` ) )ENGINE =InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET =utf8mb4; INSERT INTO `test` .`user` (`uid` , `name` , `age` ) VALUES (1 , 'WeiyiGeek' , 20 );INSERT INTO `test` .`user` (`uid` , `name` , `age` ) VALUES (2 , 'Elastic' , 18 );INSERT INTO `test` .`user` (`uid` , `name` , `age` ) VALUES (3 , 'Logstash' , 20 );INSERT INTO `test` .`user` (`uid` , `name` , `age` ) VALUES (4 , 'Beats' , 10 );INSERT INTO `test` .`user` (`uid` , `name` , `age` ) VALUES (5 , 'Kibana' , 19 );INSERT INTO `test` .`user` (`uid` , `name` , `age` ) VALUES (6 , 'C' , 25 );INSERT INTO `test` .`user` (`uid` , `name` , `age` ) VALUES (7 , 'C++' , 25 );INSERT INTO `test` .`user` (`uid` , `name` , `age` ) VALUES (8 , 'Python' , 26 );
示例结构体声明:1 2 3 4 5 type user struct { id int age int name string }
单行查询 函数原型: func (db *DB) QueryRow(query string, args ...interface{}) *Row
函数说明: 单行查询db.QueryRow()
执行一次查询,并期望返回最多一行结果(即Row)。 Tips: QueryRow总是返回非nil的值,直到返回值的Scan方法被调用时,才会返回被延迟的错误。(如:未找到结果)
简单示例:1 2 3 4 5 6 7 8 9 10 11 12 func queryRowDemo () { var u user sqlStr := "select id, name, age from user where id=?" err := db.QueryRow(sqlStr, 1 ).Scan(&u.id, &u.name, &u.age) if err != nil { fmt.Printf("scan failed, err:%v\n" , err) return } fmt.Printf("id:%d name:%s age:%d\n" , u.id, u.name, u.age) }
多行查询 函数原型: func (db *DB) Query(query string, args ...interface{}) (*Rows, error)
函数说明: 多行查询db.Query()
执行一次查询,返回多行结果(即 Rows)
, 一般用于执行select命令, 参数args表示 query中的占位参数(空接口)。
简单示例:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 func queryMultiRowDemo () { sqlStr := "select id, name, age from user where id > ?" rows, err := db.Query(sqlStr, 0 ) if err != nil { fmt.Printf("query failed, err:%v\n" , err) return } defer rows.Close() for rows.Next() { var u user err := rows.Scan(&u.id, &u.name, &u.age) if err != nil { fmt.Printf("scan failed, err:%v\n" , err) return } fmt.Printf("id:%d name:%s age:%d\n" , u.id, u.name, u.age) } }
插入/更新/删除数据 函数原型: func (db *DB) Exec(query string, args ...interface{}) (Result, error)
函数说明: Exec执行一次命令(包括查询、删除、更新、插入等),返回的Result是对已执行的SQL命令的总结。参数args表示query中的占位参数。
具体插入数据示例代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 func insertRowDemo () { sqlStr := "insert into user(name, age) values (?,?)" ret, err := db.Exec(sqlStr, "王五" , 38 ) if err != nil { fmt.Printf("insert failed, err:%v\n" , err) return } theID, err := ret.LastInsertId() if err != nil { fmt.Printf("get lastinsert ID failed, err:%v\n" , err) return } fmt.Printf("insert success, the id is %d.\n" , theID) }
具体更新数据示例代码如下:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func updateRowDemo () { sqlStr := "update user set age=? where id = ?" ret, err := db.Exec(sqlStr, 39 , 3 ) if err != nil { fmt.Printf("update failed, err:%v\n" , err) return } n, err := ret.RowsAffected() if err != nil { fmt.Printf("get RowsAffected failed, err:%v\n" , err) return } fmt.Printf("update success, affected rows:%d\n" , n) }
具体删除数据的示例代码如下:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func deleteRowDemo () { sqlStr := "delete from user where id = ?" ret, err := db.Exec(sqlStr, 3 ) if err != nil { fmt.Printf("delete failed, err:%v\n" , err) return } n, err := ret.RowsAffected() if err != nil { fmt.Printf("get RowsAffected failed, err:%v\n" , err) return } fmt.Printf("delete success, affected rows:%d\n" , n) }
综合实践 下述代码简单实现利用Go语言操作MySQL数据库的增、删、改、查等。
数据库连接封装:weiyigeek.top/studygo/Day09/MySQL/mypkg1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 package mypkgimport ( "database/sql" "fmt" _ "github.com/go-sql-driver/mysql" ) type MysqlObj struct { Mysql_host string Mysql_port uint16 Mysql_user, Mysql_pass string Database string Db *sql.DB } type Person struct { Uid int Name string Age int } func (conn *MysqlObj) InitDB () (err error) { dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True" , conn.Mysql_user, conn.Mysql_pass, conn.Mysql_host, conn.Mysql_port, conn.Database) conn.Db, err = sql.Open("mysql" , dsn) if err != nil { fmt.Printf("DSN : %s Format failed\n%v \n" , dsn, err) return err } err = conn.Db.Ping() if err != nil { fmt.Printf("Connection %s Failed,\n%v \n" , dsn, err) return err } conn.Db.SetMaxOpenConns(1024 ) conn.Db.SetMaxIdleConns(0 ) return nil }
实践 main 入口函数:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 package mainimport ( "database/sql" "fmt" db "weiyigeek.top/studygo/Day09/MySQL/mypkg" ) func queryPersonOne (conn *sql.DB, Uid int ) (res db.Person) { sqlStr := `select Uid,name,age from test.user where Uid=?;` rowObj := conn.QueryRow(sqlStr, Uid) rowObj.Scan(&res.Uid, &res.Name, &res.Age) return res } func queryPersonMore (conn *sql.DB, id int ) { sqlStr := `select Uid,name,age from test.user where Uid > ?;` rows, err := conn.Query(sqlStr, id) if err != nil { fmt.Printf("Exec %s query failed!,err : %v \n" , sqlStr, err) return } defer rows.Close() for rows.Next() { var u db.Person err := rows.Scan(&u.Uid, &u.Name, &u.Age) if err != nil { fmt.Printf("scan failed, err:%v\n" , err) return } fmt.Printf("Uid:%d name:%s age:%d\n" , u.Uid, u.Name, u.Age) } } func insertPerson (conn *sql.DB) { sqlStr := `insert into user(name,age) values("Go语言",15)` ret, err := conn.Exec(sqlStr) if err != nil { fmt.Printf("Insert Failed, err : %v \n" , err) return } Uid, err := ret.LastInsertId() if err != nil { fmt.Printf("Get Id Failed, err : %v \n" , err) return } fmt.Println("插入语句Uid值: " , Uid) } func updatePerson (conn *sql.DB, age, Uid int ) { sqlStr := `update user set age=? where Uid = ?` ret, err := conn.Exec(sqlStr, age, Uid) if err != nil { fmt.Printf("Update Failed, err : %v \n" , err) return } count, err := ret.RowsAffected() if err != nil { fmt.Printf("Get Id Failed, err : %v \n" , err) return } fmt.Println("更新数据影响的行数: " , count) } func deletePerson (conn *sql.DB, Uid int ) { sqlStr := `delete from user where Uid > ?` ret, err := conn.Exec(sqlStr, Uid) if err != nil { fmt.Printf("Delete Failed, err : %v \n" , err) return } count, err := ret.RowsAffected() if err != nil { fmt.Printf("Get Id Failed, err : %v \n" , err) return } fmt.Println("删除数据影响的行数: " , count) } func main () { conn := &db.MysqlObj{ Mysql_host: "10.20.172.248" , Mysql_port: 3306 , Mysql_user: "root" , Mysql_pass: "weiyigeek.top" , Database: "test" , } err := conn.InitDB() if err != nil { panic (err) } else { fmt.Println("数据库初始化连接成功!" ) } defer conn.Db.Close() res := queryPersonOne(conn.Db, 1 ) fmt.Printf("单行查询: %#v\n" , res) fmt.Println("多行查询" ) queryPersonMore(conn.Db, 6 ) fmt.Println("插入数据" ) insertPerson(conn.Db) fmt.Println("更新数据" ) updatePerson(conn.Db, 16 , 9 ) fmt.Println("删除数据" ) deletePerson(conn.Db, 10 ) }
执行结果&数据库查询结果: 1 2 3 4 5 6 7 8 9 10 11 12 数据库初始化连接成功! 单行查询: main.person{uid:1, name:"WeiyiGeek" , age:20} 多行查询 uid:7 name:C++ age:25 uid:8 name:Python age:26 uid:9 name:Golang age:15 插入数据 插入语句uid值: 10 更新数据 更新数据影响的行数: 1 删除数据 删除数据影响的行数: 1
weiyigeek.top-Go语言针对于MySQL数据库的CRUD操作
5.MySQL预处理 基础介绍 什么是预处理?
普通SQL语句执行过程:
客户端对SQL语句进行占位符替换得到完整的SQL语句。
客户端发送完整SQL语句到MySQL服务端
MySQL服务端执行完整的SQL语句并将结果返回给客户端。
预处理执行过程:
把SQL语句分成两部分,命令部分与数据部分。
先把命令部分发送给MySQL服务端,MySQL服务端进行SQL预处理。
然后把数据部分发送给MySQL服务端,MySQL服务端对SQL语句进行占位符替换。
MySQL服务端执行完整的SQL语句并将结果返回给客户端。
为什么要预处理?
优化MySQL服务器重复执行SQL的方法,可以提升服务器性能,提前让服务器编译,一次编译多次执行,节省后续编译的成本。
避免SQL注入问题。
SQL注入 描述: 非常注意, 我们任何时候都不应该自己拼接SQL语句, 可能会导致SQL注入的问题。
此处演示一个自行拼接SQL语句的示例,编写一个根据name字段
查询user表
的函数如下:1 2 3 4 5 6 7 8 9 10 11 12 func sqlInjectDemo (name string ) { var u user sqlStr := fmt.Sprintf("select id, name, age from user where name='%s'" , name) fmt.Printf("SQL:%s\n" , sqlStr) 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) }
当name变量输入以下字符串时便会引发SQL注入问题:1 2 3 sqlInjectDemo("xxx' or 1=1#" ) sqlInjectDemo("xxx' union select * from user #" ) sqlInjectDemo("xxx' and (select count(*) from user) <10 #" )
示例演示 Go是如何实现MySQL预处理 描述: database/sql
中使用下面的Prepare
方法来实现预处理操作。 函数原型: func (db *DB) Prepare(query string) (*Stmt, error)
函数说明: Prepare方法会先将sql语句发送给MySQL服务端,返回一个准备好的状态用于之后的查询和命令。返回值可以同时执行多个查询和命令。
示例演示: 描述: 此处引用上面封装的结构体成员以及方法,进行数据库的初始化操作。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 package mainimport ( "database/sql" "fmt" db "weiyigeek.top/studygo/Day09/MySQL/mypkg" ) func prepareQuery (conn *sql.DB, id int ) { sqlStr := "select uid,name,age from user where uid > ?;" stmt, err := conn.Prepare(sqlStr) if err != nil { fmt.Printf("prepare failed, err:%v\n" , err) return } defer stmt.Close() rows, err := stmt.Query(id) if err != nil { fmt.Printf("query failed, err:%v\n" , err) return } defer rows.Close() res := make (map [int ]db.Person, 5 ) for rows.Next() { var u db.Person err := rows.Scan(&u.Uid, &u.Name, &u.Age) if err != nil { fmt.Printf("scan failed, err:%v\n" , err) return } _, ok := res[u.Uid] if !ok { res[u.Uid] = u } fmt.Printf("id:%d name:%s age:%d\n" , u.Uid, u.Name, u.Age) } fmt.Printf("%#v\n" , res) } func prepareInsert (conn *sql.DB) { sqlStr := "insert into user(name,age) values (?,?)" stmt, err := conn.Prepare(sqlStr) if err != nil { fmt.Printf("prepare failed, err:%v\n" , err) return } defer stmt.Close() _, err = stmt.Exec("WeiyiGeek" , 18 ) if err != nil { fmt.Printf("insert failed, err:%v\n" , err) return } _, err = stmt.Exec("插入示例" , 82 ) if err != nil { fmt.Printf("insert failed, err:%v\n" , err) return } fmt.Println("insert success." ) } func main () { conn := &db.MysqlObj{ Mysql_host: "10.20.172.248" , Mysql_port: 3306 , Mysql_user: "root" , Mysql_pass: "weiyigeek.top" , Database: "test" , } err := conn.InitDB() if err != nil { panic (err) } else { fmt.Println("[INFO] - 已成功连接到数据库!" ) } defer conn.Db.Close() fmt.Println("预处理查询示例函数 prepareQuery:" ) prepareQuery(conn.Db, 5 ) fmt.Println("预处理插入示例函数 prepareInsert:" ) prepareInsert(conn.Db) }
执行结果:1 2 3 4 5 6 7 8 9 10 11 12 13 [INFO] - 已成功连接到数据库! id:6 name:C age:25 id:7 name:C++ age:25 id:8 name:Python age:26 id:9 name:Golang age:16 id:12 name:WeiyiGeek age:18 id:13 name:插入示例 age:82 map[int]mypkg.Person{6:mypkg.Person{Uid:6, Name:"C", Age:25}, 7:mypkg.Person{Uid:7, Name:"C++", Age:25}, 8:mypkg.Person{Uid:8, Name:"Python", Age:26}, 9:mypkg.Person{Uid:9, Name:"Golang", Age:16}, 12:mypkg.Person{Uid:12, Name:"WeiyiGeek", Age:18}, 13:mypkg.Person{Uid:13, Name:"插入示例", Age:18}} insert success.
Tips:不同的数据库中,SQL语句使用的占位符语法不尽相同,例如下表所示。
数据库
占位符语法
MySQL
?
PostgreSQL
$1
, $2
等
SQLite
?
和$1
Oracle
:name
6.MySQL事务处理 什么是事务?
事务:一个最小的不可再分的工作单元;通常一个事务对应一个完整的业务(例如银行账户转账业务,该业务就是一个最小的工作单元),同时这个完整的业务需要执行多次的DML(insert、update、delete)语句共同联合完成。A转账给B,这里面就需要执行两次update操作。
在MySQL中只有使用了Innodb数据库引擎的数据库或表才支持事务
, 事务处理可以用来维护数据库的完整性
,保证成批的SQL语句要么全部执行,要么全部不执行。
事务特性复习 ACID
描述: 通常事务必须满足4个条件(ACID):原子性(Atomicity,或称不可分割性)、一致性(Consistency)、隔离性(Isolation,又称独立性)、持久性(Durability)。
原子性
: 一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
一致性
: 在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。
隔离性
: 数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted)
、读提交(read committed)
、可重复读(repeatable read)
和串行化(Serializable)
。
持久性
: 事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
事务方法原型 描述:Go语言中使用以下三个方法实现MySQL中的事务操作。
func (db *DB) Begin() (*Tx, error)
: 开始事务
func (tx *Tx) Commit() error
: 提交事务
func (tx *Tx) Rollback() error
: 回滚事务
实践示例 描述: 下面的代码演示了一个简单的事务操作,该事物操作能够确保两次更新操作要么同时成功要么同时失败,不会存在中间状态。 例如: A 转账给 B 50 RMB,即从A账号余额-50,B账号余额+50。
数据库表创建:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 create table `money` ( `id` BIGINT (20 ) NOT NULL AUTO_INCREMENT, `name` VARCHAR (20 ) DEFAULT '' , `balance` INT (16 ) DEFAULT '0' , PRIMARY KEY (`id` ) )ENGINE =InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET =utf8mb4; insert into `test` .`money` (`name` ,`balance` ) values ("WeiyiGeek" ,1200 );insert into `test` .`money` (`name` ,`balance` ) values ("辛勤的小蜜蜂" ,3650 );SELECT * from money;1 WeiyiGeek 1200 2 辛勤的小蜜蜂 3650
示例代码:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 package mainimport ( "database/sql" "fmt" "weiyigeek.top/studygo/Day09/MySQL/mypkg" ) func transactionDemo (conn *sql.DB, money int ) { tx, err := conn.Begin() if err != nil { if tx != nil { tx.Rollback() } fmt.Printf("begin trans failed, err:%v\n" , err) return } sqlStr1 := "UPDATE `money` SET balance=balance-? WHERE id=?;" ret1, err := tx.Exec(sqlStr1, money, 1 ) if err != nil { tx.Rollback() fmt.Printf("exec sql1 failed, err:%v\n" , err) return } affRow1, err := ret1.RowsAffected() if err != nil { tx.Rollback() fmt.Printf("exec ret1.RowsAffected() failed, err:%v\n" , err) return } sqlStr2 := "UPDATE `money` SET balance=balance+? WHERE id=?;" ret2, err := tx.Exec(sqlStr2, money, 2 ) if err != nil { tx.Rollback() fmt.Printf("exec sql2 failed, err:%v\n" , err) return } affRow2, err := ret2.RowsAffected() if err != nil { tx.Rollback() fmt.Printf("exec ret1.RowsAffected() failed, err:%v\n" , err) return } fmt.Println("事务处理影响行数判断是否修改成功: " , affRow1, affRow2) if affRow1 == 1 && affRow2 == 1 { fmt.Println("事务正在提交啦..." ) tx.Commit() } else { tx.Rollback() fmt.Println("事务回滚啦..." ) } fmt.Println("[INFO] - 事务完成了 ,exec trans success!" ) } func main () { conn := &mypkg.MysqlObj{ Mysql_host: "10.20.172.248" , Mysql_port: 3306 , Mysql_user: "root" , Mysql_pass: "weiyigeek.top" , Database: "test" , } err := conn.InitDB() if err != nil { panic (err) } else { fmt.Println("[INFO] - 已成功连接到数据库!" ) } defer conn.Db.Close() transactionDemo(conn.Db, 50 ) }
执行结果:1 2 3 4 5 6 7 8 [INFO] - 已成功连接到数据库! 事务处理影响行数判断是否修改成功: 1 1 事务正在提交啦... [INFO] - 事务完成了 ,exec trans success! 1 WeiyiGeek 1150 2 辛勤的小蜜蜂 3700
0x01 Go语言编程快速入门之第三方sqlx库操作MySQL数据库 描述: 在项目中我们通常可能会使用database/sql
(原生库)连接MySQL数据库,而我们可以采用sqlx
来替代它, 它可以简化代码量、从而使查询更加方便。
你可以认为sqlx
是Go语言内置database/sql
的超集,它在优秀的内置database/sql
基础上提供了一组扩展。 例如: 常用来查询的 Get(dest interface{}, ...) error
和 Select(dest interface{}, ...) error
外还有很多其他强大的功能。
本文借助使用sqlx实现批量插入数据的例子,介绍了sqlx中可能被你忽视了的sqlx.In和DB.NamedExec方法。
第三方sqlx库主页: http://jmoiron.github.io/sqlx/
sqlx 安装&语法 描述: 在shell或者cmd终端中执行如下命令进行sqlx的安装:1 go get github.com/jmoiron/sqlx
语法原型:
func (db *DB) Get(dest interface{}, query string, args ...interface{}) error
: 执行SQL并绑定单行结果查询到指定类型变量中, 占位符参数都将替换为提供的参数,如果结果集为空则返回错误。
func (db *DB) Select(dest interface{}, query string, args ...interface{}) error
: 执行SQL并绑定多行结果查询到指定类型变量中。
func (db *DB) Exec(query string, args ...interface{}) (Result, error)
: Exec 执行查询时不返回任何行但可以获取影响的行数, 支持插入、更新、删除等SQL语句
sqlx 数据库初始化 描述: 我们可以利用下述示例看到sqlx与sql之间的小小区别.1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 package mypkgimport ( "fmt" "github.com/jmoiron/sqlx" ) type SqlObj struct { Mysql_host string Mysql_port uint16 Mysql_user, Mysql_pass string Database string DB *sqlx.DB } type User struct { Uid int Name string Age int } func (conn *SqlObj) InitDB () (err error) { dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True" , conn.Mysql_user, conn.Mysql_pass, conn.Mysql_host, conn.Mysql_port, conn.Database) conn.DB, err = sqlx.Connect("mysql" , dsn) if err != nil { fmt.Printf("Connect %s DB Failed\n%v \n" , dsn, err) return err } conn.DB.SetMaxOpenConns(1024 ) conn.DB.SetMaxIdleConns(10 ) return nil }
sqlx CRUD操作 描述: 在测试使用sqlx针对MySQL数据库进行CRUD操作时,我们需要准备库表准备 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 CREATE DATABASE test ;USE test ;CREATE TABLE `user` ( `id` BIGINT (20 ) NOT NULL AUTO_INCREMENT, `name` VARCHAR (20 ) DEFAULT '' , `age` INT (11 ) DEFAULT '0' , PRIMARY KEY (`id` ) )ENGINE =InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET =utf8mb4; INSERT INTO `test` .`user` (`uid` , `name` , `age` ) VALUES (1 , 'WeiyiGeek' , 20 );INSERT INTO `test` .`user` (`uid` , `name` , `age` ) VALUES (2 , 'Elastic' , 18 );INSERT INTO `test` .`user` (`uid` , `name` , `age` ) VALUES (3 , 'Logstash' , 20 );INSERT INTO `test` .`user` (`uid` , `name` , `age` ) VALUES (4 , 'Beats' , 10 );INSERT INTO `test` .`user` (`uid` , `name` , `age` ) VALUES (5 , 'Kibana' , 19 );INSERT INTO `test` .`user` (`uid` , `name` , `age` ) VALUES (6 , 'C' , 25 );INSERT INTO `test` .`user` (`uid` , `name` , `age` ) VALUES (7 , 'C++' , 25 );INSERT INTO `test` .`user` (`uid` , `name` , `age` ) VALUES (8 , 'Python' , 26 );
单行查询结果 1 2 3 4 5 6 7 8 9 10 11 12 13 func queryRow (db *sqlx.DB) { var u mypkg.User sqlStr := "SELECT uid,name,age FROM user WHERE uid=?" err := db.Get(&u, sqlStr, 1 ) if err != nil { fmt.Printf("get failed, err:%v\n" , err) return } fmt.Printf("id:%d name:%s age:%d\n" , u.Uid, u.Name, u.Age) }
多行查询结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 func queryMultiRow (db *sqlx.DB) { var u []mypkg.User sqlStr := "select uid, name, age from user where uid > ?" err := db.Select(&u, sqlStr, 8 ) if err != nil { fmt.Printf("query failed, err:%v\n" , err) return } fmt.Printf("users:%#v\n" , u) }
插入数据示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 func insertRow (db *sqlx.DB) { sqlStr := "insert into user(name, age) values (?,?)" ret, err := db.Exec(sqlStr, "我爱学Go" , 19 ) if err != nil { fmt.Printf("insert failed, err:%v\n" , err) return } theID, err := ret.LastInsertId() if err != nil { fmt.Printf("get lastinsert ID failed, err:%v\n" , err) return } fmt.Printf("insert success, the id is %d.\n" , theID) }
更新数据示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 func updateRow (db *sqlx.DB) { sqlStr := "update user set age=? where uid = ?" ret, err := db.Exec(sqlStr, 39 , 8 ) if err != nil { fmt.Printf("update failed, err:%v\n" , err) return } n, err := ret.RowsAffected() if err != nil { fmt.Printf("get RowsAffected failed, err:%v\n" , err) return } fmt.Printf("update success, affected rows:%d\n" , n) }
删除数据示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func deleteRow (db *sqlx.DB) { sqlStr := "delete from user where uid = ?" ret, err := db.Exec(sqlStr, 16 ) if err != nil { fmt.Printf("delete failed, err:%v\n" , err) return } n, err := ret.RowsAffected() if err != nil { fmt.Printf("get RowsAffected failed, err:%v\n" , err) return } fmt.Printf("delete success, affected rows:%d\n" , n) }
主函数数据库初始化和调用示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 func main () { conn := &mypkg.SqlObj{ Mysql_host: "10.20.172.248" , Mysql_port: 3306 , Mysql_user: "root" , Mysql_pass: "weiyigeek.top" , Database: "test" , } err := conn.InitDB() if err != nil { panic (err) } else { fmt.Println("[INFO] - 数据库已连接成功!" ) } defer conn.DB.Close() fmt.Println("单行数据查询结果:" ) queryRow(conn.DB) fmt.Println("多行数据查询结果:" ) queryMultiRow(conn.DB) fmt.Println("输入数据操作:" ) insertRow(conn.DB) fmt.Println("更新数据操作: " ) updateRow(conn.DB) fmt.Println("删除数据操作: " ) deleteRow(conn.DB) }
执行结果: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 [INFO] - 数据库已连接成功! 单行数据查询结果: id:1 name:WeiyiGeek age:20 多行数据查询结果: users:[]mypkg.User{mypkg.User{Uid:16, Name:"我爱学Go" , Age:19}} 输入数据操作: insert success, the id is 17. 更新数据操作: update success, affected rows:1 删除数据操作: delete success, affected rows:1 select uid,name,age from `test `.`user` 1 WeiyiGeek 20 2 Elastic 18 3 Logstash 20 4 Beats 10 5 Kibana 19 6 C 25 7 C++ 25 8 Python 39 17 我爱学Go 19
sqlx 绑定SQL语句到同名字段 我们可以使用 DB.NamedExec
和 DB.NamedQuery
方法用来绑定SQL语句与结构体或map中的同名字段,来分别进行操作字段里面的值或者将查询的结果赋予这些字段。
函数原型: 1 2 func (db *DB) NamedQuery (query string , arg interface {}) (*Rows, error) - 执行查询语句返回*rows 类型的数据。func (db *DB) NamedExec (query string , arg interface {}) (sql.Result, error) - 执行操作语句单行sql .Result 结果集。
示例演示: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 func namedQuery () { sqlStr := "SELECT * FROM user WHERE name=:name" rows, err := db.NamedQuery(sqlStr, map [string ]interface {}{"name" : "WeiyiGeek" }) if err != nil { fmt.Printf("db.NamedQuery failed, err:%v\n" , err) return } defer rows.Close() for rows.Next(){ var u user err := rows.StructScan(&u) if err != nil { fmt.Printf("scan failed, err:%v\n" , err) continue } fmt.Printf("user:%#v\n" , u) } u := user{ Name: "WeiyiGeek" , } rows, err = db.NamedQuery(sqlStr, u) if err != nil { fmt.Printf("db.NamedQuery failed, err:%v\n" , err) return } defer rows.Close() for rows.Next(){ var u user err := rows.StructScan(&u) if err != nil { fmt.Printf("scan failed, err:%v\n" , err) continue } fmt.Printf("user:%#v\n" , u) } } func insertUserDemo () (err error) { sqlStr := "INSERT INTO user (name,age) VALUES (:name,:age)" _, err = db.NamedExec(sqlStr, map [string ]interface {}{ "name" : "WeiyiGeek" , "age" : 28 , }) return }
sqlx 事务处理 描述: 对于事务操作sqlx中为我们提供了db.Beginx()
和tx.Exec()
等方法。
函数原型: 1 2 func (db *DB) Beginx () (*Tx, error)
测试库表: 1 2 3 INSERT INTO `test` .`money` (`id` , `name` , `balance` ) VALUES (1 , 'WeiyiGeek' , 1100 ); INSERT INTO `test` .`money` (`id` , `name` , `balance` ) VALUES (2 , '辛勤的小蜜蜂' , 3800 );
实际案例: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 func transactionSqlx (db *sqlx.DB) (err error) { tx, err := db.Beginx() if err != nil { fmt.Printf("begin trans failed, err:%v\n" , err) return err } defer func () { if p := recover (); p != nil { tx.Rollback() panic (p) } else if err != nil { fmt.Println("rollback" ) tx.Rollback() } else { err = tx.Commit() fmt.Println("commit" ) } }() sqlStr1 := "UPDATE `money` SET balance=balance-50 WHERE id=?" rs, err := tx.Exec(sqlStr1, 1 ) if err != nil { return err } n, err := rs.RowsAffected() if err != nil { return err } if n != 1 { return errors.New("exec sqlStr1 failed" ) } sqlStr2 := "UPDATE `money` SET balance=balance+50 WHERE id=?;" rs, err = tx.Exec(sqlStr2, 2 ) if err != nil { return err } n, err = rs.RowsAffected() if err != nil { return err } if n != 1 { return errors.New("exec sqlStr1 failed" ) } return err }
执行后结果:1 2 3 4 5 6 commit 1 WeiyiGeek 1050 2 辛勤的小蜜蜂 3850
sqlx 批量执行 描述: sqlx 为我们提供了一个非常方便的函数sqlx.In
使得我们可以批量插入,使用的函数原型格式如下:
查询占位符-bindvars
描述: 例如此处查询占位符?
在内部称为bindvars(查询占位符)
它非常重要, 由于通过字符串格式 database/sql
不尝试对查询文本进行任何验证, 而利用查询占位符进行预处理,可以极大的防止SQL注入攻击。
MySQL 中使用?
PostgreSQL 中使用枚举的$1、$2
SQLite 中?和$1的语法都支持
Oracle 中使用:name的语法
Tips: 非常注意bindvars
的一个常见误解是,它们用来在sql语句中插入值,它们其实仅用于参数化,不允许更改SQL语句的结构。
例如,使用bindvars尝试参数化列名或表名
将不起作用:1 2 3 4 5 db.Query("SELECT * FROM ?" , "mytable" ) db.Query("SELECT ?, ? FROM people" , "name" , "location" )
测试表库 1 2 3 4 5 6 7 8 9 10 11 12 13 CREATE TABLE `user` ( `id` BIGINT (20 ) NOT NULL AUTO_INCREMENT, `name` VARCHAR (20 ) DEFAULT '' , `age` INT (11 ) DEFAULT '0' , PRIMARY KEY (`id` ) )ENGINE =InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET =utf8mb4; type User struct { Name string `db:"name"` Age int `db:"age"` }
自定义批量插入 描述: 通常拼接语句实现批量插入, 方法可能比较笨但是很好理解,就是有多少个User就拼接多少个(?, ?)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 func BatchInsertUsers1 (users []*User, db *sqlx.DB) error { valueStrings := make ([]string , 0 , len (users)) valueArgs := make ([]interface {}, 0 , len (users)*2 ) for _, u := range users { valueStrings = append (valueStrings, "(?, ?)" ) valueArgs = append (valueArgs, u.Name) valueArgs = append (valueArgs, u.Age) } fmt.Printf("%#v\n%#v\n" , valueStrings, valueArgs) stmt := fmt.Sprintf("INSERT INTO user (name, age) VALUES %s" , strings.Join(valueStrings, "," )) fmt.Println(stmt) res, err := db.Exec(stmt, valueArgs...) if err != nil { fmt.Printf("Exec Batch Insert Users SQL Failed, %v\n" , err) return err } count, err := res.RowsAffected() if err != nil { fmt.Printf("Get Rows Affected Failed, %v\n" , err) return err } else { fmt.Println("Insert Rows Affected :" , count) return nil } } func main () { conn := &mypkg.SqlObj{ Mysql_host: "10.20.172.248" , Mysql_port: 3306 , Mysql_user: "root" , Mysql_pass: "weiyigeek.top" , Database: "test" , } err := conn.InitDB() if err != nil { panic (err) } else { fmt.Println("[INFO] - 数据库已连接成功!" ) } defer conn.DB.Close() userInsert := make ([]*User, 0 ) userInsert = append (userInsert, &User{Name: "WeiyiGeek-20" , Age: 20 }) userInsert = append (userInsert, &User{Name: "WeiyiGeek-21" , Age: 21 }) userInsert = append (userInsert, &User{Name: "WeiyiGeek-22" , Age: 22 }) err = BatchInsertUsers1(userInsert, conn.DB) if err != nil { panic (err) } else { fmt.Println("批量插入执行完毕!" ) } }
执行结果: 1 2 3 4 5 6 7 8 9 10 11 12 [INFO] - 数据库已连接成功! 插入占位符: []string{"(?, ?)" , "(?, ?)" , "(?, ?)" } []interface {}{"WeiyiGeek-20" , 20, "WeiyiGeek-21" , 21, "WeiyiGeek-22" , 22} INSERT INTO user (name, age) VALUES (?, ?),(?, ?),(?, ?) Insert Rows Affected : 3 批量插入执行完毕! 19 WeiyiGeek-20 20 20 WeiyiGeek-21 21 21 WeiyiGeek-22 22
使用 sqlx.In 实现批量插入 描述: 我们除了使用自定义的还可以使用sqlx.In
方法与NamedExec
方法实现批量插入,下面我们来实践sqlx.In的批量插入。
步骤01.插入实例前提是需要我们的结构体实现driver.Valuer
接口(类似于Java中的重写), 此处将字段值包装为空接口进返回。1 2 3 func (u User) Value () (driver.Value, error) { return []interface {}{u.Name, u.Age}, nil }
步骤02.使用sqlx.In
实现批量插入代码如下:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 func BatchInsertUsers2 (users []interface {}, db *sqlx.DB) error { query, args, _ := sqlx.In( "INSERT INTO user (name, age) VALUES (?), (?), (?)" , users..., ) fmt.Println(query) fmt.Println(args) res, err := db.Exec(query, args...) if err != nil { fmt.Printf("Exec Batch Insert Users SQL Failed, %v\n" , err) return err } count, err := res.RowsAffected() if err != nil { fmt.Printf("Get Rows Affected Failed, %v\n" , err) return err } else { fmt.Println("Insert Rows Affected :" , count) return nil } } ...... userInsert := make ([]interface {}, 0 ) userInsert = append (userInsert, &User{Name: "Gooo-20" , Age: 20 }) userInsert = append (userInsert, &User{Name: "R-21" , Age: 21 }) userInsert = append (userInsert, &User{Name: "Javascript-22" , Age: 22 }) err = BatchInsertUsers2(userInsert, conn.DB) if err != nil { panic (err) } else { fmt.Println("sqlx.In - 批量插入执行完毕!" ) }
执行结果: 1 2 3 4 5 6 7 8 9 10 [INFO] - 数据库已连接成功! INSERT INTO user (name, age) VALUES (?, ?), (?, ?), (?, ?) [Go-20 20 R-21 21 Javascript-22 22] Insert Rows Affected : 3 sqlx.In - 批量插入执行完毕! 25 Go-20 20 26 R-21 21 27 Javascript-22 22
扩展学习之 sqlx.In 的查询示例 在sqlx查询语句中实现In查询
和 FIND_IN_SET函数, 即实现 SELECT * 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查询: IN 操作符允许我们在 WHERE 子句中规定多个值1 2 3 4 5 6 7 8 9 10 11 12 func QueryByIDs (ids []int ) (users []User, err error) { query, args, err := sqlx.In("SELECT name, age FROM user WHERE id IN (?)" , ids) if err != nil { return } query = DB.Rebind(query) err = DB.Select(&users, query, args...) return }
in 查询
和 FIND_IN_SET 查询
: 查询id在给定id集合的数据并维持给定id集合的顺序。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 func QueryAndOrderByIDs (ids []int ) (users []User, err error) { strIDs := make ([]string , 0 , len (ids)) for _, id := range ids { strIDs = append (strIDs, fmt.Sprintf("%d" , id)) } query, args, err := sqlx.In("SELECT name, age FROM user WHERE id IN (?) ORDER BY FIND_IN_SET(id, ?)" , ids, strings.Join(strIDs, "," )) if err != nil { return } query = DB.Rebind(query) err = DB.Select(&users, query, args...) return }
当然,在这个例子里面你也可以先使用IN查询,然后通过代码按给定的ids对查询结果进行排序。
Tips: 上述SQL执行结果以及IN 关键字 与 FIND_IN_SET 区别如下:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 SELECT uid, name , age FROM user WHERE uid IN (1 ,2 )SELECT uid, name , age FROM user WHERE 8 IN (uid)SELECT uid, name , age FROM user WHERE 1 IN (2 ,3 ,4 )SELECT FIND_IN_SET (5 , '1,5,6,18' ) as 'Index' ; SELECT uid, name , age FROM user WHERE FIND_IN_SET (1 ,uid);SELECT uid, name , age FROM user WHERE uid IN (1 ,5 ,6 ,18 ) ORDER BY FIND_IN_SET (1 ,uid);
使用 NamedExec 实现批量插入 注意:该功能需1.3.1
版本以上并在1.3.1版本目前还有点问题sql语句最后不能有空格和, 不过当前版本 v1.3.4 中已解决;
使用NamedExec实现批量插入示例如下:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 func BatchInsertUsers3 (users []*User, db *sqlx.DB) error { res, err := db.NamedExec("INSERT INTO user (name, age) VALUES (:name, :age)" , users) if err != nil { fmt.Printf("Exec Batch Insert Users SQL Failed, %v\n" , err) return err } count, err := res.RowsAffected() if err != nil { fmt.Printf("Get Rows Affected Failed, %v\n" , err) return err } else { fmt.Println("Insert Rows Affected :" , count) return nil } } userInsert := make ([]*User, 0 ) userInsert = append (userInsert, &User{Name: "小红" , Age: 20 }) userInsert = append (userInsert, &User{Name: "小南" , Age: 21 }) userInsert = append (userInsert, &User{Name: "小白" , Age: 22 }) err = BatchInsertUsers3(userInsert, conn.DB) if err != nil { fmt.Printf("[Error] - %v\n" , err) } else { fmt.Println("NamedExec - 批量插入执行完毕!" ) }
执行结果: 1 2 3 4 5 6 7 8 [INFO] - 数据库已连接成功! Insert Rows Affected : 3 NamedExec - 批量插入执行完毕! 28 小红 20 29 小南 21 30 小白 22
此处将上面三种方法综合起来试一下:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 func main () { err := initDB() if err != nil { panic (err) } defer DB.Close() u1 := User{Name: "WeiyiGeek" , Age: 18 } u2 := User{Name: "weiy_" , Age: 28 } u3 := User{Name: "weiyi" , Age: 38 } users := []*User{&u1, &u2, &u3} err = BatchInsertUsers(users) if err != nil { fmt.Printf("BatchInsertUsers failed, err:%v\n" , err) } users2 := []interface {}{u1, u2, u3} err = BatchInsertUsers2(users2) if err != nil { fmt.Printf("BatchInsertUsers2 failed, err:%v\n" , err) } users3 := []*User{&u1, &u2, &u3} err = BatchInsertUsers3(users3) if err != nil { fmt.Printf("BatchInsertUsers3 failed, err:%v\n" , err) } }