Go 数据库编程一、连接数据库准备连接到数据库
- 要想连接到 SQL 数据库,首先需要加载目标数据库的驱动,驱动里面包含着于该数据库交互的逻辑。
- sql.Open()
- 数据库驱动的名称
- 数据源名称
- 得到一个指向 sql.DB 这个 struct 的指针
- sql.DB 是用来操作数据库的,它代表了0个或者多个底层连接的池,这些连接由sql 包来维护,sql 包会自动的创建和释放这些连接
- 它对于多个 goroutine 并发的使用是安全的
package mainimport ( "context" "database/sql" "fmt" "log" _ "github.com/denisenkom/go-mssqldb")var db *sql.DBconst ( server = "xxxx.database.windows.net" port = 1433 user = "xxxxx" password = "xxxxx" database = "go-db")func main() { connStr := fmt.Sprintf("server=%s;user id=%s;password=%s;port=%d;database=%s;", server, user, password, port, database) db, err := sql.Open("sqlserver", connStr) if err != nil { log.Fataln(err.Error()) } ctx := context.Background() err = db.PingContext(ctx) if err != nil { log.Fataln(err.Error()) } fmt.Println("Connected!")}
Note
- Open() 函数并不会连接数据库,甚至不会验证其参数。它只是把后续连接到数据库所必需的 structs 给设置好了
- 而真正的连接是在被需要的时候才进行懒设置的
- sql.DB 不需要进行关闭(当然你想关闭也是可以的)
- 它就是用来处理数据库的,而不是实际的连接
- 这个抽象包含了数据库连接的池,而且会对此进行维护
- 在使用 sql.DB 的时候,可以定义它的全局变量进行使用,也可以将它传递到函数/方法里。
如何获得驱动
- 正常的做法是使用 sql.Register() 函数、数据库驱动的名称和一个实现了 driver.Driver 接口的 struct,来注册数据库的驱动。例如:
- sql.Register(“sqlserver”, &drv{})
- 但是我们之前的例子却没写这句话,为什么?
- 因为 Sql Server 的驱动,是在这个包被引入的时候进行了自我注册
驱动自动注册
- 当 go-mssqldb 包被引入的时候,它的 init 函数将会运行并进行自我注册(在 Go 语言里,每个包的 init 函数都会在自动的调用)
- 在引入 go-mssqldb 包的时候,把该包的名设置为下划线 _,这是因为我们不直接使用数据库驱动(只需要它的”副作用“),我们只使用 database/sql
- 这样,如果未来升级驱动,也无需改变代码
- Go 语言没有提供官方的数据库驱动,所有的数据库驱动都是第三方驱动,但是它们都遵循 sql.driver 包里面定义的接口
安装数据库驱动
- 这是安装 Microsoft SQL Server 数据库驱动的例子:
- go get github.com/denisenkom/go-mssqldb
func(*DB) PingContext
- 上例中的 db.PingContext() 函数是用来验证与数据库的连接是否仍然有效,如有必要则建立一个连接。
- 这个函数需要一个 Context (上下文)类型的参数,这种类型可以携带截止时间、取消信号和其它请求范围的值,并且可以横跨 API 边界和进程。
- 上例中,创建 context 使用的是 context.Background() 函数。该函数返回一个非 nil 的空 Context。它不会被取消,它没有值,没有截止时间。
- 它通常用在 main 函数、初始化或测试中,作为传入请求的顶级 Context。
Exercises
- 使用 PostgreSQL 建立数据库,使用 Go 语言进行连接,并 Ping 一下。
- 使用 SQLite 建立数据库,使用 Go 语言进行连接,并 Ping 一下。
连接MySQL
https://github.com/go-sql-driver/mysql
go get -u github.com/go-sql-driver/mysql
创建目录
➜ mcd go_sql_demoCode/go/go_sql_demo via ? v1.20.3 via ? base➜ go mod init go_sql_demogo: creating new go.mod: module go_sql_demoCode/go/go_sql_demo via ? v1.20.3 via ? base➜ cCode/go/go_sql_demo via ? v1.20.3 via ? base➜
main.go
package mainimport ("database/sql""fmt"_ "github.com/go-sql-driver/mysql")// 定义一个全局对象dbvar db *sql.DB// 定义一个初始化数据库的函数func initDB() (err error) {// DSN:Data Source Namedsn := "root:12345678@tcp(127.0.0.1:3306)/db_xuanke?charset=utf8mb4&parseTime=True"// 不会校验账号密码是否正确// 注意!!!这里不要使用:=,我们是给全局变量赋值,然后在main函数中使用全局变量dbdb, err = sql.Open("mysql", dsn)if err != nil {return err}// 尝试与数据库建立连接(校验dsn是否正确)err = db.Ping()if err != nil {return err}return nil}func main() {err := initDB() // 调用输出化数据库的函数if err != nil {fmt.Printf("init db failed,err:%v\n", err)return}fmt.Println("connect to database")}
二、CRUD查询
- sql.DB 类型上用于查询的方法有:
- Query
- QueryRow
- QueryContext
- QueryRowContext
Query
- 返回的类型是:type Rows struct {}
- Rows 的方法:
func (rs *Rows) Close() error
func (rs *Rows) ColumnTypes() ([]*ColumnType, error)
func (rs *Rows) Columns() ([]string, error)
func (rs *Rows) Err() error
func (rs *Rows) Next() bool
func (rs *Rows) NextResultSet() bool
func (rs *Rows) Scan(dest ...interface{}) error
QueryRow
- 返回类型是:type Row struct {}
- Row 的方法有:
func (r *Row) Err() error
func (r *Row) Scan(dest ...interface{}) error
https://pkg.go.dev/database/sql@go1.20.4
services.go 文件
package mainfunc getOne(id int) (a app, err error) { a = app{} log.Println(db == nil) err = db.QueryRow("SELECT Id, Name, Status, Level, [Order] FROM dbo.App WHERE Id=@Id", sql.Named("Id", id)).Scan( &a.ID, &a.name, &a.status, &a.level, &a.order) return}func getMany(id int) (apps []app, err error) { rows, err = db.Query("SELECT Id, Name, Status, Level, [Order] FROM dbo.App WHERE Id>@Id", sql.Named("Id", id)) for rows.Next() { a := app{} err = rows.Scan(&a.ID, &a.name, &a.status, &a.level, &a.order) if err != nil { log.Fatalln(err.Error()) } apps = append(apps, a) } return}
models.go 文件
package maintype app struct { ID int name string status int level int order int}
main.go 文件
package mainimport ( "context" "database/sql" "fmt" "log" _ "github.com/denisenkom/go-mssqldb")var db *sql.DBconst ( server = "xxxx.database.windows.net" port = 1433 user = "xxxxx" password = "xxxxx" database = "go-db")func main() { connStr := fmt.Sprintf("server=%s;user id=%s;password=%s;port=%d;database=%s;", server, user, password, port, database) var err error db, err = sql.Open("sqlserver", connStr) if err != nil { log.Fataln(err.Error()) } ctx := context.Background() err = db.PingContext(ctx) if err != nil { log.Fataln(err.Error()) } fmt.Println("Connected!") log.Println(db == nil) // 查询一笔 one, err := getOne(103) if err != nil { log.Fatal(err.Error()) } fmt.Println(one) apps, err := getMany(103) if err != nil { log.Fatalln(err.Error()) } fmt.Println(apps)}
更新
- sql.DB 类型上用于更新(执行命令)的方法有:
- Exec
- ExecContext
services.go 文件
func (a *app) Update() (err error) { _, err = db.Exec("UPDATE dbo.App SET Name=@Name, [Order]=@Order WHERE Id=@Id", sql.Named("Name", a.name), sql.Named("Order", a.order), sql.Named("Id", a.ID)) if err != nil { log.Fatalln(err.Error()) } return}
main.go 文件
a, _ := getOne(103)fmt.Println(a)a.name += " 1234"a.order++err = a.Update()if err != nil { log.Fatalln(err.Error())}a1, _ := getOne(103)fmt.Println(a1)
删除
services.go 文件
func (a *app) Delete() (err error) { _, err = db.Exec("DELETE FROM dbo.App WHERE Id=@Id", sql.Named("Id", a.ID)) if err != nil { log.Fatalln(err.Error()) } return}
其它
- Ping
- PingContext
- Prepare
- PrepareContext
- Transactions
- Begin
- Begin Tx
services.go 文件
func (a *app) Insert() (err error) { statement := `INSERT INTO dbo.App (Name, NickName, Status, Level, [Order], Pinyin) VALUES (@Name, 'Nick', &Status, @Level, @Order, '...'); SELEÇT isNull(SCOPE_IDENTITY(), -1);` stmt, err := db.Prepare(statement) if err != nil { log.Fatalln(err.Error()) } defer stmt.Close() err = stmt.QueryRow( sql.Named("Name", a.name), sql.Named("Status", a.status), sql.Named("Level", a.level), sql.Named("Order", a.order)).Scan(&a.ID) if err != nil { log.Fatalln(err.Error()) } return}
main.go 文件
a := app { name: "Test", order: 1123, level: 10, status: 1,}err = a.Insert()if err != nil { log.Fatalln(err.Error())}one, _ := getOne(a.ID)fmt.Println(one)
三、MySQL CRUD 实践
在MySQL中创建一个名为sql_test
的数据库
mysql> create database sql_test;Query OK, 1 row affected (0.01 sec)mysql> use sql_test;Database changedmysql> 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;Query OK, 0 rows affected, 2 warnings (0.02 sec)mysql> show tables;+--------------------+| Tables_in_sql_test |+--------------------+| user |+--------------------+1 row in set (0.01 sec)mysql>
main.go 文件
package mainimport ("database/sql""fmt"_ "github.com/go-sql-driver/mysql")// 定义一个全局对象dbvar db *sql.DB// 定义一个初始化数据库的函数func initDB() (err error) {// DSN:Data Source Namedsn := "root:12345678@tcp(127.0.0.1:3306)/sql_test?charset=utf8mb4&parseTime=True"// 不会校验账号密码是否正确// 注意!!!这里不要使用:=,我们是给全局变量赋值,然后在main函数中使用全局变量dbdb, err = sql.Open("mysql", dsn)if err != nil {return err}// 尝试与数据库建立连接(校验dsn是否正确)err = db.Ping()if err != nil {return err}return nil}func main() {err := initDB() // 调用输出化数据库的函数if err != nil {fmt.Printf("init db failed,err:%v\n", err)return}fmt.Println("connect to database")// one, err := queryRowDemo(1)// if err != nil {// log.Fatal(err.Error())// }// fmt.Println(one)// u := user{// name: "小乔1",// age: 13,// id: 0,// }// err = u.insertRowDemo()// if err != nil {// log.Fatalln(err.Error())// }// u, _ := queryRowDemo(1)// fmt.Println("u:", u)// u.name = "貂蝉"// u.age = 16// err = u.updateRowDemo()// if err != nil {// log.Fatalln(err.Error())// }// u1, _ := queryRowDemo(1)// fmt.Println("u1:", u1)last_row, _ := queryMultiRowDemo(0)fmt.Println("last_row:", last_row)// var u = new(user)// u.id = 3// err = u.deleteRowDemo()// if err != nil {// log.Fatalln(err.Error())// }}
services.go 文件
package mainimport ("fmt")// 查询单条数据示例func queryRowDemo(id int) (u user, err error) {sqlStr := "select id, name, age from user where id=?"// 非常重要:确保QueryRow之后调用Scan方法,否则持有的数据库链接不会被释放err = db.QueryRow(sqlStr, id).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)return}// 查询多条数据示例func queryMultiRowDemo(id int) (u user, err error) {sqlStr := "select id, name, age from user where id > ?"rows, err := db.Query(sqlStr, id)if err != nil {fmt.Printf("query failed, err:%v\n", err)return}// 非常重要:关闭rows释放持有的数据库链接defer rows.Close()// 循环读取结果集中的数据for rows.Next() {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)}return}// 插入数据func (u *user) insertRowDemo() (err error) {sqlStr := "insert into user(name, age) values (?,?)"ret, err := db.Exec(sqlStr, u.name, u.age)if err != nil {fmt.Printf("insert failed, err:%v\n", err)return}theID, err := ret.LastInsertId() // 新插入数据的idif err != nil {fmt.Printf("get lastinsert ID failed, err:%v\n", err)return}fmt.Printf("insert success, the id is %d.\n", theID)return}// 更新数据func (u *user) updateRowDemo() (err error) {sqlStr := "update user set age=? where id = ?"ret, err := db.Exec(sqlStr, u.age, u.id)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)return}// 删除数据func (u *user) deleteRowDemo() (err error) {sqlStr := "delete from user where id = ?"ret, err := db.Exec(sqlStr, u.id)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)return}
models.go 文件
package maintype user struct {id intage intname string}
运行
Code/go/go_sql_demo via ? v1.20.3 via ? base ➜ go build . && ./go_sql_democonnect to databaseid:1 name:小乔 age:16id:2 name:小乔 age:12last_row: {2 12 小乔}Code/go/go_sql_demo via ? v1.20.3 via ? base took 3.3s ➜
本文来自博客园,作者:QIAOPENGJUN,转载请注明原文链接:https://www.cnblogs.com/QiaoPengjun/p/17390319.html