GO语言学习(九)
上一期我们了解了实现web的工作中极为重要的net/http抱的细节讲解,大家学会了实现web开发的一些底层基础知识,在这一期我来为大家讲解一下web工作的一个重要方法,:使用数据库,现在就让我来为大家讲解这一篇章,欢迎大家交流学习
sql接口及database
首先和大家解释一下在golang中没有提供现成的数据库驱动方式,英雌在我们实际开发中一般是实现接口,利用这些接口来实现相应的数据库驱动操作,这样使用可以在迁移数据库的时候,只用使用开发好的标准数据库接口。
sql.register说明
这个存在于database/sql的函数是用来注册数据库驱动的,当第三方开发者开发数据库驱动时,都会实现init函数,在init里面会调用这个
Register(name string, driver driver.Driver)
完成本驱动的注册。
我们来看一下mysql、sqlite3的驱动里面都是怎么调用的,下面直接上代码:
// 实现示例:
//https://github.com/mattn/go-sqlite3驱动
func init() {
sql.Register("sqlite3", &SQLiteDriver{})
}
//https://github.com/mikespook/mymysql驱动
// Driver automatically registered in database/sql
var d = Driver{proto: "tcp", raddr: "127.0.0.1:3306"}
func init() {
Register("SET NAMES utf8")
sql.Register("mymysql", &d)
}
**我们看到第三方数据库驱动都是通过调用这个函数来注册自己的数据库驱动名称以及相应的driver实现。在database/sql内部通过一个map来存储用户定义的相应驱动。**示例说明:
var drivers = make(map[string]driver.Driver)
drivers[name] = driver
因此通过database/sql的注册函数可以同时注册多个数据库驱动,只要不重复。
我们一般通过使用以下代码来使用相应的接口和第三方库:
import (
"database/sql"
_ "github.com/mattn/go-sqlite3"
)
一般的新手都会被这个_所迷惑,其实这个就是Go设计的巧妙之处,我们在变量赋值的时候经常看到这个符号,它是用来忽略变量赋值的占位符,那么包引入用到这个符号也是相似的作用,这儿使用_的意思是引入后面的包名而不直接使用这个包中定义的函数,变量等资源。同时我们引入上面的数据库驱动包之后会自动去调用init函数,然后在init函数里面注册这个数据库驱动,这样我们就可以在接下来的代码中直接使用这个数据库驱动了。
driver.Driver讲解
Driver是一个数据库驱动的接口,他定义了一个method: Open(name string),这个方法返回一个数据库的Conn接口。
type Driver interface {
Open(name string) (Conn, error)
}
返回的Conn只能用来进行一次goroutine的操作,也就是说不能把这个Conn应用于Go的多个goroutine里面。如下代码会出现错误,如下所示:
...
go goroutineA (Conn) //执行查询操作
go goroutineB (Conn) //执行插入操作
...
上面这样的代码可能会使Go不知道某个操作究竟是由哪个goroutine发起的,从而导致数据混乱,比如可能会把goroutineA里面执行的查询操作的结果返回给goroutineB从而使B错误地把此结果当成自己执行的插入数据。
第三方驱动都会定义这个函数,它会解析name参数来获取相关数据库的连接信息,解析完成后,它将使用此信息来初始化一个Conn并返回它。
driver.Conn说明
Conn是一个数据库连接的接口定义,他定义了一系列方法,这个Conn只能应用在一个goroutine里面,不能使用在多个goroutine里面.
type Conn interface {
Prepare(query string) (Stmt, error)
Close() error
Begin() (Tx, error)
}
**1.**Prepare函数返回与当前连接相关的执行Sql语句的准备状态,可以进行查询、删除等操作。
**2.**Close函数关闭当前的连接,执行释放连接拥有的资源等清理工作。因为驱动实现了database/sql里面建议的conn pool,所以你不用再去实现缓存conn之类的,这样会容易引起问题。
**3.**Begin函数返回一个代表事务处理的Tx,通过它你可以进行查询,更新等操作,或者对事务进行回滚、递交。
driver.Stimt详解
**Stmt是一种准备好的状态,和Conn相关联,而且只能应用于一个goroutine中,不能应用于多个goroutine。**实现代码如下:
type Stmt interface {
Close() error
NumInput() int
Exec(args []Value) (Result, error)
Query(args []Value) (Rows, error)
}
Close函数关闭当前的链接状态,但是如果当前正在执行query,query还是有效返回rows数据。
NumInput函数返回当前预留参数的个数,当返回>=0时数据库驱动就会智能检查调用者的参数。当数据库驱动包不知道预留参数的时候,返回-1。
Exec函数执行Prepare准备好的sql,传入参数执行update/insert等操作,返回Result数据
Query函数执行Prepare准备好的sql,传入需要的参数执行select操作,返回Rows结果集
driver.Tx解释
事务处理一般就两个过程,递交或者回滚。数据库驱动里面也只需要实现这两个函数就可以,代码如下:
type Tx interface {
Commit() error
Rollback() error
}
**这两个函数一个用来递交一个事务,一个用来回滚事务。**大家可以在实际开发中感受一下这个的独特特性,不懂的欢迎大家在评论区中分享,大家一起讨论。
driver.Execer讲解
driver.Execer是一个Conn可选择实现的接口,功能有许多好的妙用,如果未使用这个接口,那么在调用DB.Exec,就会首先调用Prepare返回Stmt,然后执行Stmt的Exec,然后关闭Stmt。下面提供实现这个接口的示例代码:
type Execer interface {
Exec(query string, args []Value) (Result, error)
}
driver.Result讲解
driver.Result是执行Update/Insert等操作返回的结果接口定义,LastInsertId函数返回由数据库执行插入操作得到的自增ID号。RowsAffected函数返回执行Update/Insert等操作影响的数据条目数。下面我们来为大家提供一下示例代码:
type Result interface {
LastInsertId() (int64, error)
RowsAffected() (int64, error)
}
driver.Rows讲解
其实Rows是执行查询返回的结果集接口定义,Columns函数返回查询数据库表的字段信息,这个返回的slice和sql查询的字段一一对应,而不是返回整个表的所有字段。Close函数用来关闭Rows迭代器。Next函数用来返回下一条数据,把数据赋值给dest。dest里面的元素必须是driver.Value的值除了string,返回的数据里面所有的string都必须要转换成[]byte。如果最后没数据了,Next函数最后返回io.EOF。示例代码如下:
type Rows interface {
Columns() []string
Close() error
Next(dest []Value) error
}
driver.RowsAffected讲解
RowsAffected其实就是一个int64的别名,但是他实现了Result接口,用来底层实现Result的表示方式,代码如下:
type RowsAffected int64
func (RowsAffected) LastInsertId() (int64, error)
func (v RowsAffected) RowsAffected() (int64, error)
driver.Value讲解
Value其实就是一个空接口,他可以容纳任何的数据,然后其实就是drive的Value是驱动必须能够操作的Value,Value要么是nil,要么是下面的给出的数据类型。
type Value interface{} // 构建value接口
// 数据类型
int64
float64
bool
[]byte
string [*]除了Rows.Next返回的不能是string.
time.Time
database/sql说明
database/sql在database/sql/driver提供的接口基础上定义了一些更高阶的方法,用以简化数据库操作,同时内部还建议性地实现一个conn. pool,然后我们就可以得出一下结论,大家可以自己先思考一下,在结合我给的讲解:
我们可以看到Open函数返回的是DB对象,里面有一个freeConn,它就是那个简易的连接池。它的实现相当简单或者说简陋,就是当执行
db.prepare
->db.prepareDC
的时候会defer dc.releaseConn
,然后调用db.putConn
,也就是把这个连接放入连接池,每次调用db.conn
的时候会先判断freeConn的长度是否大于0,大于0说明有可以复用的conn,直接拿出来用就是了,如果不大于0,则创建一个conn,然后再返回之。
看完这些我在给大家提供代码,大家可以参考,然后自己在试着敲敲下面的示例代码:
type DB struct {
driver driver.Driver
dsn string
mu sync.Mutex // protects freeConn and closed
freeConn []driver.Conn
closed bool
}
结语
我们这一期简单的学习了一下数据库连接的接口实现,并且为大家讲解了些许的相关接口的实现,还有更多的知识大家需要自探索,后面我也会出一个专题来为大家详细解释的。
下一期我会为大家讲解实现MYSQL数据库,同时作为目前Internet上流行的网站构架方式是LAMP,其中的M即MySQL, 作为数据库,MySQL以免费、开源、使用方便为优势成为了很多Web开发的后端数据库存储引擎。欢迎大家期待与认可
在此谢谢大家的支持,你的关注和点赞会是我继续努力写文章的动力,也感谢大家能够持续关注博主,谢谢大家的支持,周末会给大家写一篇福利文章,大家敬请期待~~~