原创做者,公众号【程序员读书】,欢迎关注公众号,转载文章请注明出处哦。mysql
应该说,数据库编程是任何编程语言都有提供的基础功能模块,不管是编程语言内置的支持,仍是经过外部库来实现;固然啦,不一样编程语言提供的数据库编程API是不尽相同的,并且须要支持的数据库也是多种多样,如经常使用的MySQL
,SQLServer
,Postgres
等数据库。git
抛开其余编程语言不谈,在这篇文章中,咱们就来聊一聊Go语言数据库编程的那些事,了解如何使用Go语言提供的标准库,编写通用的操做数据库的代码。程序员
标准库database/sql
是Go语言的数据库操做抽象层,具体的数据库操做逻辑实现则由不一样的第三方包来作,而标准库database/sql/driver
提供了这些第三方包实现的标准规范。github
因此,Go语言的数据库编程,通常只须要导入标准库database/sql
包,这个包提供了操做数据库全部必要的结构体、函数与方法,下面是导入这个包的语句:golang
import database/sql
复制代码
Go语言这样作的好处就是,当从一个数据库迁移到另外一个数据库时(如SQL Server
迁移到MySQL
),则只须要换一个驱动包即可以了。sql
前面咱们说Go语言数据操做的由不一样第三方包来实现,那么若是咱们想要链接MySQL数据库的话,要怎么实现一个这样的包呢?实际上,Go语言标准库database/sql/driver
定义了实现第三方驱动包的全部接口,咱们只导入实现了database/sql/driver
相关接口驱动包就能够了。数据库
下面是支持Golang的数据库驱动列表:编程
以MySQL数据库驱动包为例:服务器
$ go get -u github.com/go-sql-driver/mysql
复制代码
import database/sql
import _ "github.com/go-sql-driver/mysql"
复制代码
sql.DB
结构是sql/database
包封装的一个数据库操做对象,包含了操做数据库的基本方法。
DSN
全称为Data Source Name
,表示数据库连来源,用于定义如何链接数据库,不一样数据库的DSN格式是不一样的,这取决于数据库驱动的实现,下面是go-sql-driver/sql
的DSN格式,以下所示:
//[用户名[:密码]@][协议(数据库服务器地址)]]/数据库名称?参数列表
[username[:password]@][protocol[(address)]]/dbname[?param1=value1&...¶mN=valueN]
复制代码
database/sql
包中有两个函数用于初始化并返回一个*sql.DB
结构体对象:
//driverName表示驱动名,如mysql,dataSourceName为上文介绍的DSN
func Open(driverName, dataSourceName string) (*DB, error)
func OpenDB(c driver.Connector) *DB
复制代码
下面演示如何使用这两个函数:
通常而言,咱们使用Open()
函数即可初始化并返回一个*sql.DB
结构体实例,使用Open()
函数只要传入驱动名称及对应的DSN即可,使用很简单,也很通用,当须要链接不一样数据库时,只须要修改驱动名与DSN就能够了。
import "database/sql"
import _ "github.com/go-sql-driver/mysql" //注意前面有_
func open(){
const DRIVER = "mysql"
var DSN = "root:123456@tcp(localhost:3306)/test?charset=utf8&parseTime=True&loc=Local"
var err error
db, err = sql.Open(DRIVER, DSN)
if err != nil {
panic(err)
}
if err = db.Ping();err != nil{
panic(err)
}
}
复制代码
OpenDB()
函数则依赖驱动包实现年sql/database/driver
包中的Connector
接口,这种方法并不通用化,不推荐 使用,下面演示使用mysql
驱动包的driver.Connector
初始化并返回*sql.DB
结构体实例:
import "database/sql"
import "github.com/go-sql-driver/mysql"//注意前面没有_
func openConnector() {
Connector, err := mysql.NewConnector(&mysql.Config{
User: "root",
Passwd: "123456",
Net: "tcp",
Addr: "localhost:3306",
DBName: "test",
AllowNativePasswords:true,
Collation:"utf8_general_ci",
ParseTime:true,
Loc:time.Local,
})
if err != nil {
panic(err)
}
db = sql.OpenDB(Connector)
if err = db.Ping();err != nil{
panic(err)
}
}
复制代码
使用前面定义的方法,初始化一个*sql.DB
指针结构体:
var db *sql.DB
//在init方法初始化`*sql.DB`
func init(){
open()
//或者
openConnector()
}
复制代码
这里要说一下的是,sql.DB
是sql/database
包封装的一个结构体,但不表示一个数据库链接对象,实际上,咱们能够把sql.DB
看做一个简单的数据库链接池,咱们下面的几个方法设置数据库链接池的相关参数:
func (db *DB) SetMaxIdleConns(n int)//设置链接池中最大空闲数据库链接数,<=0表示不保留空闲链接,默认值2
func (db *DB) SetMaxOpenConns(n int)//设置链接池最大打开数据库链接数,<=表示不限制打开链接数,默认为0
func (db *DB) SetConnMaxLifetime(d time.Duration)//设置链接超时时间
复制代码
代码演示
db.SetMaxOpenConns(100)//设置最多打开100个数据连链接
db.SetMaxIdleConns(0)//设置为0表示
db.SetConnMaxLifetime(time.Second * 5)//5秒超时
复制代码
下面咱们演示在Go语言中有关数据库的增删改查(CURD)等基本的操做,为此咱们建立了一个名为users
的数据表,其建立的SQL
语句以下:
CREATE TABLE users(
id INT NOT NULL AUTO_INCREMENT COMMENT 'ID',
username VARCHAR(32) NOT NULL COMMENT '用户名',
moeny INT DEFAULT 0 COMMENT '帐户余额',
PRIMARY KEY(id)
);
INSERT INTO users VALUES(1,'小明',1000);
INSERT INTO users VALUES(2,'小红',2000);
INSERT INTO users VALUES(3,'小刚',1400);
复制代码
查询是数据库操做最基本的功能,在Go语言中,可使用sql.DB
中的Query()
或QueryContext()
方法,这两个方法的定义以下:
func (db *DB) Query(query string, args ...interface{}) (*Rows, error)
func (db *DB) QueryContext(ctx context.Context, query string, args ...interface{}) (*Rows, error)
复制代码
Query()
和QueryContext()
方法返回一个sql.Rows
结构体,表明一个查询结果集,sql.Rows
的定义及其所包含的方法以下:
type Rows struct {
//contains filtered or unexported fields
}
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 //扫描结构体
复制代码
使用sql.Rows
的Next()
和Scan
方法,但能够遍历返回的结果集,下面是示例代码:
func query() {
selectText := "SELECT * FROM users WHERE id = ?"
rows, _ := db.Query(selectText, 2)
defer rows.Close()
for rows.Next() {
var (
id int
username string
money int
)
_ = rows.Scan(&id, &username,&money)
fmt.Println(id, username,money)
}
}
复制代码
还能够用sql.DB
中的QueryRow()
或QueryRowContext()
方法,这两个方法的定义以下所示:
func (db *DB) QueryRow(query string, args ...interface{}) *Row
func (db *DB) QueryRowContext(ctx context.Context, query string, args ...interface{}) *Row
复制代码
QueryRow
和QueryRowContext
返回一个sql.Row
结构体,表明数据表的一行,sql.Row
的定义以下,能够看到sql.Row
结构体只有一个Scan()
方法,用于扫描sql.Row
结构体中的数据。
type Row struct{
}
func (r *Row) Scan(dest ...interface{}) error
复制代码
代码演示
func queryRow(){
selectText := "SELECT * FROM users WHERE id = ?"
row := db.QueryRow(selectText, 2)
var (
id int
username string
money int
)
_ = row.Scan(&id, &username,&money)
fmt.Println(id, username,money)
}
复制代码
另外,使用sql.DB
中的Prepare()
或PrepareContext()
方法,能够返回一个sql.Stmt
结构体`。
注意:sql.Stmt结构体会先把在
Prepare()
或PrepareContext()
定义的SQL语句发给数据库执行,再将SQL语句中须要的参数发给数据库,再返回处理结果。
func (db *DB) Prepare(query string) (*Stmt, error)
func (db *DB) PrepareContext(ctx context.Context, query string) (*Stmt, error)
复制代码
sql.Stmt
提交了与sql.DB
用于查询并返回结果集的方法,下面请看示例:
func queryStmt(){
stmt,err := db.Prepare("SELECT * FROM users WHERE id = ?")
if err != nil{
return
}
defer stmt.Close()
rows,err := stmt.Query(2)
defer rows.Close()
for rows.Next() {
var (
id int
username string
money int
)
_ = rows.Scan(&id, &username,&money)
fmt.Println(id, username,money)
}
}
复制代码
添加数据库记录,可使用sql.DB
中的Exec()
或ExecContext()
方法,这两个方法的定义以下:
func (db *DB) Exec(query string, args ...interface{}) (Result, error)
func (db *DB) ExecContext(ctx context.Context, query string, args ...interface{}) (Result, error)
复制代码
代码示例:
func insert(){
insertText := "INSERT INTO users values(?,?,?)"
rs,err := db.Exec(insertText,4,"juejin",1000)
if err != nil{
fmt.Println(err)
return
}
if id,_ := rs.LastInsertId();id > 0 {
fmt.Println("插入成功")
}
/*也能够这样判断是否插入成功
if n,_ := rs.RowsAffected();n > 0 {
fmt.Println("插入成功")
}
*/
}
复制代码
Exec()
或ExecContext()
方法的第一个返回值为一个实现了sql.Result
接口的类型,sql.Result
的定义以下:
注意
LastInsertId()
方法只有在使用INSERT语句且数据表有自增id时才有返回自增id值,不然返回0。
type Result interface {
LastInsertId() (int64, error)//使用insert向数据插入记录,数据表有自增id时,该函数有返回值
RowsAffected() (int64, error)//表示影响的数据表行数
}
复制代码
咱们能够用sql.Result
中的LastInsertId()
方法或RowsAffected()
来判断SQL
语句是否执行成功。
除了使用sql.DB
中的Exec()
和ExecContext()
方法外,也可使用Prepare()
或PrepareContext()
返回sql.Stmt
结构体,再经过sql.Stmt
中的Exec()
方法向数据表写入数据。
使用sql.Stmt
向数据表写入数据的演示:
func insertStmt(){
stmt,err := db.Prepare("INSERT INTO users VALUES(?,?,?)")
defer stmt.Close()
if err != nil{
return
}
rs,err := stmt.Exec(5,"juejin",1000)
if id,_ := rs.LastInsertId(); id > 0 {
fmt.Println("插入成功")
}
}
复制代码
注意,使用
sql.Stmt
中的Exec()
或ExecContext()
执行SQL
对更新和删除语句一样适合,下面讲更新和删除时再也不演示。
与往数据表里添加数据同样,可使用sql.DB
的Exec()
或ExecContext()
方法,不过,使用数据库UPDATE
语句更新数据时,咱们只能经过sql.Result
结构体中的RowsAffected()
方法来判断影响的数据行数,进而判断是否执行成功。
func update() {
updateText := "UPDATE users SET username = ? WHERE id = ?"
rs,err := db.Exec(updateText,"database",2)
if err != nil{
fmt.Println(err)
return
}
if n,_ := rs.RowsAffected();n > 0 {
fmt.Println("更新成功")
}
}
复制代码
使用DELETE
语句删除数据表记录的操做与上面的更新语句是同样的,请看下面的演示:
func del() {
delText := "DELETE FROM users WHERE id = ?"
rs,err := db.Exec(delText,1)
if err != nil{
fmt.Println(err)
return
}
fmt.Println(rs.RowsAffected())
}
复制代码
在前面的示例中,咱们都没有开启事务,若是没有开启事务,那么默认会把提交的每一条SQL
语句都看成一个事务来处理,若是多条语句一块儿执行,当其中某个语句执行错误,则前面已经执行的SQL
语句没法回滚。
对于一些要求比较严格的业务逻辑来讲(如订单付款、用户转帐等),应该在同一个事务提交多条SQL
语句,避免发生执行出错没法回滚事务的状况。
如何开启一个新的事务?可使用sql.DB
结构体中的Begin()
或BeginTx()
方法,这两个方法的定义以下:
func (db *DB) Begin() (*Tx, error)
func (db *DB) BeginTx(ctx context.Context, opts *TxOptions) (*Tx, error)
复制代码
BeginTx()
方法的第二个参数为TxOptions
,其定义以下:
type TxOptions struct {
// Isolation is the transaction isolation level.
// If zero, the driver or database's default level is used. Isolation IsolationLevel ReadOnly bool } 复制代码
TxOptions
的Isolation
字段用于定义事务的隔离级别,其类型为IsolationLevel
,Ioslation
的取值范围能够为以下常量:
const (
LevelDefault IsolationLevel = iota
LevelReadUncommitted
LevelReadCommitted
LevelWriteCommitted
LevelRepeatableRead
LevelSnapshot
LevelSerializable
LevelLinearizable
)
复制代码
Begin()
和BeginTxt()
方法返回一个sql.Tx
结构体,使用sql.Tx
对数据库进行操做,会在同一个事务中提交,下面演示代码:
tx, err := db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelSerializable})
//Begin方法其实是调用BeginTx()方法,db.BeginTx(context.Background(), nil)
tx, err := db.Begin()
复制代码
下面是sql.Tx
结构体中的基本操做方法,使用方式与咱们前面演示的例子同样
func (tx *Tx) Exec(query string, args ...interface{}) (Result, error)
func (tx *Tx) ExecContext(ctx context.Context, query string, args ...interface{}) (Result, error)
func (tx *Tx) Query(query string, args ...interface{}) (*Rows, error)
func (tx *Tx) QueryContext(ctx context.Context, query string, args ...interface{}) (*Rows, error)
func (tx *Tx) QueryRow(query string, args ...interface{}) *Row
func (tx *Tx) QueryRowContext(ctx context.Context, query string, args ...interface{}) *Row
复制代码
当使用sql.Tx
的操做方式操做数据后,须要咱们使用sql.Tx
的Commit()
方法显式地提交事务,若是出错,则可使用sql.Tx
中的Rollback()
方法回滚事务,保持数据的一致性,下面是这两个方法的定义:
func (tx *Tx) Commit() error
func (tx *Tx) Rollback() error
复制代码
sql.Tx
结构体中的Stmt()
和StmtContext()
能够将sql.Stmt
封装为支持事务的sql.Stmt
结构体并返回,这两个方法的定义以下:
func (tx *Tx) Stmt(stmt *Stmt) *Stmt
func (tx *Tx) StmtContext(ctx context.Context, stmt *Stmt) *Stmt
复制代码
而使用sql.Tx
中的Prepare()
和PrepareContext()
方法则能够直接返回一个支持事务的sql.Stmt
结构体
func (tx *Tx) Prepare(query string) (*Stmt, error)
func (tx *Tx) PrepareContext(ctx context.Context, query string) (*Stmt, error)
复制代码
//修改
func txUpdate(){
tx,_ := db.Begin()
rs,err := tx.Exec("UPDATE users SET username = ? WHERE id = ?","sssss",2)
if err != nil{
panic(err)
}
err = tx.Commit()
if err != nil{
panic(err)
}
if n,_ := rs.RowsAffected();n > 0{
fmt.Println("成功")
}
}
//使用Stmt修改
func txStmt(){
tx,err := db.Begin()
if err != nil{
panic(err)
}
stmt,err := db.Prepare("UPDATE users SET username = ? WHERE id = ?")
stmtTx := tx.Stmt(stmt)
defer stmtTx.Close()
rs,_ := stmtTx.Exec("test",2)
_ = tx.Commit()
if n,_ := rs.RowsAffected();n > 0{
fmt.Println("成功")
}
}
复制代码
前面咱们介绍是Go语言原生对数据库编程的支持,不过,更方便的是,咱们能够直接使用一些开源的ORM
(Object Relational Mapping
)框架,ORM
框架能够封装了底层的SQL
语句,并直接映射为Struct
,Map
等数据类型,省去咱们直接写SQL
语句的工做,很是简单方便。
下面几个比较经常使用的ORM框架:
GORM
是一个很是完善的ORM框架,除了基本增长改查的支持,也支持关联包含一个,包含多个,属于,多对多的数据表,另外也能够在建立/保存/更新/删除/查找以前或以后写钩子回调,同时也支持事务。
GORM目前支持数据库驱动MySQL
、SQLite3
、SQL Server
、Postgres
。
Xorm
也是一个简单又强大的ORM框架,其功能也GORM
是相似的,不过支持的数据库驱动比GORM
多一些,支持MySQL
,SQL Server
,SQLite3
,Postgres
,MyMysql
,Tidb
,Oracle
等数据库驱动。
Beego ORM
是国人开发的Web框架Beego
中的一个模块,虽然是Beego
的一个模块,但能够独立使用,不过目前Beego ORM
只支持MySQL
、SQLite3
,Postgres
等数据库驱动。
除了上面咱们介绍的三个ORM框架,其实还不少很好的ORM的框架,你们有空能够看看。
Go语言将对数据库的操做抽象并封装在sql/database
包中,为咱们操做不一样数据库提供统一的API,很是实用,咱们在这篇文章中讲解了sql/database
包中的sql.DB
,sql.Rows
,sql.Stmt
,sql.Tx
等结构体的使用,相信你经过上面的示例,也必定能掌握Go语言操做数据库的方法。
你的关注,是我写做路上最大的鼓励!