Files
build-web-application-with-…/5.1.md
2012-09-10 23:19:56 +08:00

195 lines
8.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#5.1 database/sql接口
Go和PHP不同的地方是他没有官方提供数据库驱动而是定义了一些标准接口用来开发数据库驱动第三方用户可以根据定义的接口来开发相应的数据库驱动其实这样做有一个好处我们按照标准接口开发的代码将来迁移数据库是相当方便的换一个数据库驱动但是底层的接口没有任何变化。那么Go都定义了那些标准接口呢让我们来详细的分析一下
##sql.Register
这个函数是database/sql里面用来注册数据库驱动的当第三方开发者开发完毕一个数据库驱动都会在驱动里面有一个init函数里面都会调用这个`Register(name string, driver driver.Driver)`
我们来看一下mymysql、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的注册函数可以同时注册多个数据库驱动只要不重复。
>在我们使用database/sql接口和第三方库的时候经常看到如下的import
>
> "database/sql"
> _ "github.com/mattn/go-sqlite3"
>
>新手都会被这个`_`所迷惑其实这个就是Go设计的巧妙之处我们知道在变量赋值的时候看到过这个是用来忽略变量的意思那么这个包引入也是这个只是引入包而不直接使用这个pkg的调用我们在2.3流程和函数里面介绍过init函数的初始化过程包在引入的时候会去初始化包内部的init函数那么我们引入上面的数据库驱动包之后会去调用init函数然后在init函数里面注册了这个数据库驱动这样我们就可以在接下来的代码中直接使用这个数据库驱动了。
##driver.Driver
数据库驱动是一个接口定义他定义了一个method Open(name string)这个方法返回一个数据库的Conn接口这个Conn只能用来进行一次goroutine的操作。
type Driver interface {
Open(name string) (Conn, error)
}
每个驱动的这个函数都是通过name参数用来解析然后初始化一个Conn返回。
##driver.Conn
数据库连接是一个接口定义他定义了一系列方法这个Conn只能应用在一个goroutine里面不能使用在多个goroutine里面。
type Conn interface {
Prepare(query string) (Stmt, error)
Close() error
Begin() (Tx, error)
}
Prepare函数返回与当前连接相关的准备好Sql语句的状态可以进行查询、删除等操作。
Close函数关闭使得当前的连接失效以及和当前连接相关的状态、处理。因为database/sql里面实现了建议的conn pool所以你不要再自己去实现缓存conn之类的这样容易引起问题。
Begin函数返回一个事务处理Tx可以进行事物操作回滚、递交等操作。
##drive.Stmt
Stmt是一种准备好的状态和Conn相关联而且是只能应用于一个goroutine中不能应用在多个goroutine中。
type Stmt interface {
Close() error
NumInput() int
Exec(args []Value) (Result, error)
Query(args []Value) (Rows, error)
}
Close函数关闭当前的链接状态但是如果当前正在执行queryquery还是有效返回raws数据。
NumInput函数返回当前预留参数的个数当返回>=0时数据库驱动就会智能检查调用者的参数。当数据库驱动包不知道预留参数的时候返回-1。
Exec函数执行Prepare准备好的sql传入参数执行update/insert等操作返回Result数据
Query函数执行Prepare准备好的sql传入需要的参数执行select操作返回Rows结果集
##drive.Tx
事务处理一般就两个过程,递交或者回滚。数据库驱动里面也只需要实现这两个函数就可以
type Tx interface {
Commit() error
Rollback() error
}
这两个函数一个用来递交一个事务,一个用来回滚事务。
##drive.Execer
这个接口是Conn可选的可实现可不实现的接口
type Execer interface {
Exec(query string, args []Value) (Result, error)
}
如果这个接口没有定义那么在调用DB.Exec,就会首先调用Prepare返回Stmt然后执行Stmt的Exec然后关闭Stmt。
##drive.Result
这个是执行Update/Insert等操作返回的结果接口定义
type Result interface {
LastInsertId() (int64, error)
RowsAffected() (int64, error)
}
LastInsertId函数返回插入数据库之后返回自动增长字段的IDInsert之后返回的数据
RowsAffected函数返回通过query影响的数据库条数。
##drive.Rows
Rows是执行查询返回的结果集接口定义
type Rows interface {
Columns() []string
Close() error
Next(dest []Value) error
}
Columns函数返回查询数据库表的字段信息这个返回的slice和你sql查询的字段一一对应而不是返回整个表的所有字段。
Close函数用来关闭Rows迭代器。
Next函数用来返回下一条数据把数据赋值给dest。dest里面的元素必须是drive.Value的值除了string所有的string必须转换成[]byte.如果最后没数据了Next函数最后返回io.EOF。
##drive.RowsAffected
RowsAffested其实就是一个int64的别名但是他实现了Result接口用来底层实现Result的表示方式
type RowsAffected int64
func (RowsAffected) LastInsertId() (int64, error)
func (v RowsAffected) RowsAffected() (int64, error)
##drive.Value
Value其实就是一个空接口他可以容纳任何的数据
type Value interface{}
Value的值必须所有的驱动里面控制的Value要么是nil要么是下面的任意一种
int64
float64
bool
[]byte
string [*]除了Rows.Next返回的不能是string.
time.Time
##drive.ValueConverter
ValueConverter接口定义了如何把一个普通的值转化成drive.Value的接口
type ValueConverter interface {
ConvertValue(v interface{}) (Value, error)
}
ValueConverter对于各种各种的值的实现必须保持高度的一致性在驱动包里面很多地方会使用到这个转化接口
- 转化drive.value到数据库表相应的字段例如int64的数据如何转化成数据库表uint16字段
- 把数据库查询结果转化成drive.Value值
- 在scan函数里面如何吧dirve.Value值转化成用户定义的值
##drive.Valuer
Valuer接口定义了返回如何返回一个drive.Value值
type Valuer interface {
Value() (Value, error)
}
很多类型上面都定义了这个Value方法用来实现每个相应类型如何转化为drive.Value类型。
通过上面的讲解,对于驱动的开发有了细致的了解,一个驱动基本上就是实现上面的这些接口就能完成我们平常的增删改所有的操作了,底层就是和相应的数据库进行数据交互就可以了。
##database/sql
database/sql里面定义了基于这个驱动之上的一些方法这些方法都是基于drive接口来封装的同时内部还实现了一个建议的conn pool。
type DB struct {
driver driver.Driver
dsn string
mu sync.Mutex // protects freeConn and closed
freeConn []driver.Conn
closed bool
}
我们可以看到Open函数返回的是DB对象里面有一个freeConn这个就是那个简易的连接池。它的实现也是就是很简单当执行Db.prepare的时候最后会defer:`db.putConn(ci, err)`,也就是放入连接池而每次调用conn的时候也会优先判断freeConn的长度是否大于0大于0说明有使用过的conn直接拿出来就用了。
## links
* [目录](<preface.md>)
* 上一节: [访问数据库](<5.md>)
* 下一节: [使用MySQL数据库](<5.2.md>)
## LastModified
* $Id$