Go语言操作数据库
Go语言提供了关于数据库的操作,包下有sql/driver
该包用来定义操作数据库的接口,这保证了无论使用哪种数据库,操作方式都是相同的;
准备工作:
下载驱动
需要在代码所在文件夹下执行相应的命令
go get github.com/go-sql-driver/mysql
PS D:\GolandData> go get github.com/go-sql-driver/mysql
go: downloading github.com/go-sql-driver/mysql v1.7.1
go: added github.com/go-sql-driver/mysql v1.7.1
安装驱动
go install github.com/go-sql-driver/mysql
安装之后检查go.mod文件
Go提供了sql包,但是没有指定是哪一个数据库的,用于访问特定数据库的方法交给了数据库驱动实现;
匿名导入包
匿名导入包——只导入包但是不使用包内的类型和数据,使用匿名的方式(在包路径前添加下画线“_”)导入MySQL驱动。
为什么需要匿名导包—开发者不应该直接使用导入的驱动包所提供的方法,而应该使用sql.DB对象所提供的统一方法;
在导入一个数据库驱动后,该驱动会自行初始化并注册到Golang的database/sql上下文中
连接数据库
连接数据库基础API
Driver接口中有一个方法Open()
/*数据库驱动程序可以实现DriverContext访问在一个连接池中只解析一次名称而不是每个连接一次。*/
type Driver interface {
/* Open返回到数据库的新连接。该名称是驱动程序特定格式的字符串。 Open可能返回一个缓存的连接(先前的一个关闭的),但这样做是不必要的;SQL包维护一个空闲连接池,以便有效地重用。返回的连接一次只被一个线程使用。*/
Open(name string) (Conn, error)
}
sql包中有一个open(driverName, dataSourceName string)方法
该方法打开一个数据库连接,
func Open(driverName, dataSourceName string) (*DB, error) {
driversMu.RLock()//这里说明一个线程用一个连接
driveri, ok := drivers[driverName]
driversMu.RUnlock()
if !ok {
return nil, fmt.Errorf("sql: unknown driver %q (forgotten import?)", driverName)
}
if driverCtx, ok := driveri.(driver.DriverContext); ok {
connector, err := driverCtx.OpenConnector(dataSourceName)
if err != nil {
return nil, err
}
return OpenDB(connector), nil
}
return OpenDB(dsnConnector{dsn: dataSourceName, driver: driveri}), nil
}
参数释义----即连接数据库三要素
- driverName–数据库驱动名
- dataSourceName:数据库连接信息,数据库地址,用户名.密码, 数据库名等信息
由open()源代码可以看到,sql.Open()返回的sql.DB对象是并发安全的,即每次只能一个Goroutine使用一个,所以高并发下如果没有及时关闭不需要的连接,就会导致系统资源耗尽;
sql.DB的设计初衷是为长连接设计的,不宜频繁开关;
比较好的做法是,为每个不同的datastore建一个DB对象,保持这些对象打开。
如果需要短连接(一次连接一次数据交互),就把DB作为参数传入function,而不要在function中开关。
CURD操作
来一段代码Demo
数据库查询
type BookInfo struct {
BookId, BookName, BookPublish, BookKind string
BookPrice float64
BookCount int
}
func (b *BookInfo) DatabaseMysql() {
//db, err := sql.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/gavin?charset=utf8") //返回*DB, error
db, err := sql.Open("mysql", "root:123456@tcp(172.21.114.22:3306)/gavin?charset=utf8") //返回*DB, error
if err != nil {
fmt.Println("打开失败")
fmt.Println(err.Error())
return
}
fmt.Println("打开成功")
fmt.Printf("%T\n", db)
queryStr := `select * from bookstore ;` //sql语句
//Exec执行一个不返回任何行的查询。
//args用于查询中的任何占位参数。 看样子不能用这个方法
//exec, err := db.Exec(queryStr) //返回Result ,err
//fmt.Printf("类型%T \n 值 %v", exec, exec) // nil,nil
//构建一个DB对象
queryResult, err := db.Query(queryStr) //返回 *Rows, error
if err != nil {
fmt.Println(err.Error())
return
}
for queryResult.Next() {
queryResult.Scan(&b.BookId, &b.BookName, &b.BookPublish, &b.BookPrice, &b.BookKind, &b.BookCount)
fmt.Printf("bookInfo信息: bookid: %s\t bookname: %s \t bookpublish: %s \t bookprice: %f \t bookkind: %s bookcount %d \n", b.BookId, b.BookName, b.BookPublish, b.BookPrice, b.BookKind, b.BookCount)
}
db.Close()
}
func main() {
b := BookInfo{}
b.DatabaseMysql()
}
获得的结果:
我们可以看到代码的基本思路跟java一样,go是定义一个结构体用于接收数据库中的数据,然后我们需要掌握下面的几个函数用于处理数据库数据;最后关闭数据库连接
- 使用db.Query()来发送查询到数据库,获取结果集Rows,并检查错误。
- 使用rows.Next()作为循环条件,迭代读取结果集。
- 使用rows.Scan从结果集中获取一行结果。
- 使用rows.Err()在退出迭代后检查错误。
- 使用rows.Close()关闭结果集,释放连接。
rows.Scan()方法的参数顺序很重要,必须和查询结果的column相对应(数量和顺序都需要一致)
插入数据
// 插入
func (b *BookInfo) insertData() {
//连接数据库
db, err := sql.Open("mysql", "root:123456@tcp(172.21.114.22:3306)/gavin?charset=utf8")
chkERR(err)
insertStr := `insert into bookstore VALUES("1024","Go并发实践","泉城出版社", 168.8,"计算机",198)`
db.Exec(insertStr)
chkERR(err)
//_, err = exec.RowsAffected() //返回影响的行数,并不是所有数据库都支持,
defer db.Close()
}
更新数据
// 更新
func (b *BookInfo) updateData() {
db, err := sql.Open("mysql", "root:123456@tcp(172.21.114.22:3306)/gavin?charset=utf8")
chkERR(err)
updateStr := `update bookstore set bookprice=109 where bookname="Go并发实践"`
exec, err := db.Exec(updateStr) //如果操作成功,返回
affected, err := exec.RowsAffected()
fmt.Println("update影响行数-->", affected)
checkError(err)
defer db.Close()
}
删除数据
// 删除
func (b *BookInfo) deleteData() {
db, err := sql.Open("mysql", "root:123456@tcp(172.21.114.22:3306)/gavin?charset=utf8")
chkERR(err)
deleteStr := `delete from bookstore where bookprice>100 `
exec, err := db.Exec(deleteStr)
affected, err := exec.RowsAffected()
fmt.Println("影响行数:", affected)
defer db.Close()
}
以上操作数据库的方式很容易就会产生sql注入
为此go也为我们提供了预编译的方式来操作数据库;
// 预编译的方式插入数据
func preInsert(book BookInfo) (int64, error) {
db, err := sql.Open("mysql", "root:955945@tcp(172.21.114.22:3306)/gavin?")
chkERR(err)
queryStr := `insert into bookstore values (?,?,?,?,?,?)`
//返回一个预编译的对象
prepare, err := db.Prepare(queryStr) //*Stmt, error
chkERR(err)
exec, err := prepare.Exec(book.BookId, book.BookName, book.BookPublish, book.BookPrice, book.BookKind, book.BookCount)
affected, err := exec.RowsAffected() //返回受影响的行数
fmt.Println("受影响的行数--->>", affected)
defer db.Close()
if err != nil {
return -1, err
}
return affected, nil
}
func main() {
b := BookInfo{
BookId: "1096",
BookName: "大雨倾盆",
BookPublish: "烟台出版社",
BookKind: "小说",
BookPrice: 56.5,
BookCount: 100,
}
insert, err := preInsert(b)
if err != nil {
return
}
fmt.Println(insert)
}
结果:
数据库数据:
预编译参数用? 来表示
func preQuery(bookname string) {
db, err := sql.Open("mysql", "root:955945@tcp(172.21.114.22:3306)/gavin?")
chkERR(err)
queryStr := `select * from bookstore where BookName=? ;`
prepare, err := db.Prepare(queryStr)
query, err := prepare.Query(bookname) //*Rows, error
b := new(BookInfo)
for query.Next() {
query.Scan(&b.BookId, &b.BookName, &b.BookPublish, &b.BookPrice, &b.BookKind, &b.BookCount)
fmt.Println(*b)
}
defer query.Close()
}
func main() {
preQuery("大雨倾盆")
}
db.Query()会从数据库连接池中获取一个连接,这个底层连接在结果集(rows)未关闭前会被标记为处于繁忙状态。当遍历读到最后一条记录时,会发生一个内部EOF错误,自动调用rows.Close()。但如果出现异常,提前退出循环,rows不会关闭,连接不会回到连接池中,连接也不会关闭,则此连接会一直被占用。因此通常使用defer rows.Close()来确保数据库连接可以正确放回到连接池中。
在实际开发中应尽量封装 curd这些方法;
但是们实际开发时并不会按照上面的方式去开发,而是借助框架去更快的实现CURD,所以后面要学习Beego框架