前言
学了很多Golang的基础语法和零碎知识,总感觉无法把Golang像Python一样灵活运用到实际项目开发之中。
Gin框架源码解析
Gin框架路由详解
Gin框架中间详解
Go操作MySQL数据库
1.database/sql操作MySQL
在golang内置了1个database/sql包,database/sql提供了连接数据库的泛用的接口,但并不提供具体数据库驱动。
所以使用database/sqx包时必须注入(至少)一个数据库驱动。
如果我们需要连接不同的数据就需要基于database/sqlx的数据库驱动,下载不同的数据驱动。
我们常用的数据库基本上都有完整的第三方实现。例如:MySQL驱动。
公共的接口+不同的实现插件,这就是1个接口思想。
2.mysql数据库和表创建
MariaDB [(none)]> create database web default charset=utf8;
创建user表
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;
3.下载MySQL驱动
go get -u github.com/go-sql-driver/mysql
3.sql.Open()使用驱动
sql.Open会返回1个db对象,db对象维护了1个数据库连接池,并发安全。
func UserDriver()(err error){ rds:="zhanggen:123.com@tcp(192.168.56.18:3306)/web" db,err= sql.Open("mysql", rds) //做完错误检查之后再进行defer db.close() if err!=nil{ return } //确保sql.Open()返回的不是空指针! defer db.Close() return }
4.db.Ping()测试数据库连接
sql.Open()不会去真正连接数据库,我们可以使用db.ping尝试与数据建立连接。
//全局连接池对象 var db *sql.DB func InitDB() (err error) { dns := "zhanggen:123.com@tcp(192.168.56.18:3306)/web" //Open函数只是校验其参数格式是否正确?实际并不会创建数据库连接! db, err = sql.Open("mysql", dns) if err != nil { return } //db有可能返回Nil //做完了错误检查之后,再defer,db.close释放掉数据库连接资源,确保db不为nill //defer db.Close() //尝试与数据库建立连接 err = db.Ping() if err!=nil{ fmt.Printf("连接数据库失败%v\n",err) return } //连接最长的时间 db.SetConnMaxLifetime(time.Second*10) //最大连接数 db.SetMaxOpenConns(200) //最大闲置连接 db.SetMaxIdleConns(10) return }
5.db.QueryRow查询单条记录
注意在我查询的数据的使用了QueryRow就相当于在连接池中获取了1个数据库连接,要使用Scan方法对当前进行释放。
package main import ( "database/sql" "fmt" _ "github.com/go-sql-driver/mysql" //自动初始化执行mysql包中的init() "time" ) //全局连接池对象 var db *sql.DB func InitDB() (err error) { dns := "zhanggen:123.com@tcp(192.168.56.18:3306)/web" //Open函数只是校验其参数格式是否正确?实际并不会创建数据库连接! db, err = sql.Open("mysql", dns) if err != nil { return } //db有可能返回Nil //做完了错误检查之后,再defer,db.close释放掉数据库连接资源,确保db不为nill //defer db.Close() //尝试与数据库建立连接 err = db.Ping() if err!=nil{ fmt.Printf("连接数据库失败%v\n",err) return } //连接最长的时间 db.SetConnMaxLifetime(time.Second*10) //最大连接数 db.SetMaxOpenConns(200) //最大闲置连接 db.SetMaxIdleConns(10) return } type user struct { id int name string age int } func main() { if err := InitDB(); err != nil { fmt.Printf("connect to db faild,err:%v\n", err) } defer db.Close() sqlStr:="select id,name,age from user where id=?" var u user //注意:每1次QueryRow之后都要scan把当前连接释放掉!否则会一直占用单前连接! row:=db.QueryRow(sqlStr,1) //相当于直接传了3个变量,如果不直接传1个结构体字段名无需大写! err := row.Scan(&u.id, &u.name, &u.age) if err!=nil{ fmt.Println(err) return } fmt.Printf("id:%d name:%s age:%d\n",u.id,u.name,u.age) }
5.db.Query()查询多条记录
//查询多条记录 func querymultiple() { sqlString := "select id,name,age from user limit 10;" rows, err := db.Query(sqlString) if err != nil { fmt.Println(err) return } var userList []user //防止for循环过程出错!提前注册关闭! defer rows.Close() //循环获取结果集中的数据,知道Rows.Next()=flase for rows.Next() { var u user err := rows.Scan(&u.id, &u.name, &u.age) if err != nil { return } userList = append(userList, u) } fmt.Println(userList) }
7.db.Exec()
db.Exec()可以对数据库进行insert、update、delete操作。
insert
插入操作可以通过db.Exec()执行之后返回的结果描述中获取LastInsertId
func insertRow() { sqlString := "insert into user(name,age)values(?,?)" ret, err := db.Exec(sqlString, "张根", 25) if err != nil { fmt.Println(err) return } id, err := ret.LastInsertId() if err != nil { fmt.Println("获取最后一条插入记录的ID失败!", err) } fmt.Printf("插入成功,最新记录的id为%d\n", id) }
update
我们在使用db.Exec()执行update操作时,会返回1个RowsAffected,也就是受影响的行数。
func updateRow(){ sqlSring:="update user set age=? where id=?;" ret,err := db.Exec(sqlSring,27,3) if err!=nil{ fmt.Println("更新数据失败",err) return } //受影响的行数 n,err:=ret.RowsAffected() if err!=nil{ fmt.Println(err) } if n==0{ fmt.Println("未更新!") return } fmt.Printf("更新%d条数据\n",n) }
delete
我们在使用db.Exec()执行delete操作时,会返回RowsAffected(受影响的行数),仅SQL语句不同。
func deleteRow() { sqlString := "delete from user where id=?;" ret, err := db.Exec(sqlString, 4) if err != nil { fmt.Println("执行删除失败", err) return } //受影响的行数!! var affectedRow int64 affectedRow, err = ret.RowsAffected() if err != nil { fmt.Println(err) return } if affectedRow == 0 { fmt.Println("删除未成功!", affectedRow) return } fmt.Printf("删除%d数据\n", affectedRow) }
8.db.Prepare(sql)SQL预处理
SQL预处理有2大好处
1.避免SQL注入问题。
func prepareQuery(){ sql:="select id,name,age from user where name=?" //1.把SQL先发送到MySQL-server端去预处理 prepared,err := db.Prepare(sql) if err!=nil{ fmt.Printf("SQL预处理失败%v",err) } //3.最后记得关闭连接 defer prepared.Close() //2.把sql参数发送到MySQL-server端进行拼接 row:= prepared.QueryRow("Tom") u:=new(user) row.Scan(&u.id,&u.name,&u.age) fmt.Println(u) }
2.SQL预处理是优化MySQL服务器频繁执行重复SQL的方法,可以提升服务器性能,提前让服务器编译,服务端一次编译多次执行,节省后续编译的成本。
我们在做监控系统时,数据的写入不能延迟,所以无法使用批量插入,可以使用SQL预编译来进行优化。
func prepareInsert() { sqlString := "insert into user(name,age)values(?,?);" preparedStatement, err := db.Prepare(sqlString) if err != nil { fmt.Println("sql预处理失败", err) return } defer preparedStatement.Close() //实时插入 preparedStatement.Exec("Amy", 19) preparedStatement.Exec("Jack", 89) preparedStatement.Exec("Foina", 29) }
9.事务操作
Go语言中使用以下三个方法实现MySQL中的事务操作。
开始事务
begin, err := db.Begin()
提交事务
begin.Commit()
回滚事务
begin.Rollback()
代码
func transactionDemo() { //1.开启事务 begin, err := db.Begin() if err != nil { if begin != nil { //事务回滚 begin.Rollback() } fmt.Println("事务操作开始失败", err) return } sql1 := "update user set age=59 where id=?" _, err = begin.Exec(sql1, 1) if err != nil { //事务回滚 begin.Rollback() fmt.Println("事务操作时执行SQL1失败!", err) return } sql2 := "update user set age=60 where id=?" _, err = begin.Exec(sql2, 2) if err != nil { //事务回滚 begin.Rollback() fmt.Println("事务操作时执行SQL2失败", err) return } //2.提交事务 err = begin.Commit() if err != nil { //事务回滚 begin.Rollback() fmt.Println("执行事务操作提交事务时失败", err) return } }
使用sqlx操作数据
sqlx是基于内置database/sql包扩展出来的超集,它的功能更加强大,更加易用。官网。
1.sqlx.Connect()
连接数据库:sqlx是基于database/sql进行的扩展,它的connect()就是对sql.Open()和sql.Ping()的封装。
func initDB() (err error) { //parseTime=True解析时间类型 dsn := "zhanggen:123.com@tcp(192.168.56.18:3306)/web?charset=utf8mb4&parseTime=True" //Connect()=Open()+Ping() db, err = sqlx.Connect("mysql", dsn) if err != nil { fmt.Println("数据库连接失败", err) return } //设置连接池参数 db.SetConnMaxLifetime(time.Second) db.SetMaxIdleConns(10) return }
2.db.Get(&u, sqlString,参数)
查询单条数据
func queryOne(){ sqlString:="select id,name,age from user where id=?;" var u user //&u:查询结果要赋值到的结构体,sql语句,sql的参数 err := db.Get(&u, sqlString,1) if err!=nil{ fmt.Println("查询数据失败",err) return } fmt.Println(u) }
3.db.Select(&users, sqlString, 10)
查询多条数据
func queryMutil(){ sqlString:="select id,name,age from user limit ?" var users []user //不需要for rows.Next()逐一进行Scan获取了! err := db.Select(&users, sqlString, 10) if err!=nil{ fmt.Println(err) return } fmt.Printf("%#v\n",users) }
4.db.Exec()
我们可以使用db.Exec()完成对数据库的insert/update/delete操作。
增
func InsertOne() { sqlStr := "insert into user(name,age)values(?,?);" ret, err := db.Exec(sqlStr, "郑爽", 28) if err != nil { fmt.Printf("数据插入失败%v\n", err) return } var id int64 id, err = ret.LastInsertId() if err != nil { fmt.Println(err) return } fmt.Println(id) }