Merging other languages

This commit is contained in:
James Miranda
2016-09-23 18:01:10 -03:00
parent 380a8ee74c
commit de3c5bdaa4
490 changed files with 24539 additions and 24588 deletions

View File

@@ -1,204 +1,204 @@
# 5.1 database/sql接口
Go与PHP不同的地方是Go官方没有提供数据库驱动而是为开发数据库驱动定义了一些标准接口开发者可以根据定义的接口来开发相应的数据库驱动这样做有一个好处只要是按照标准接口开发的代码 以后需要迁移数据库时不需要任何修改。那么Go都定义了哪些标准接口呢让我们来详细的分析一下
## sql.Register
这个存在于database/sql的函数是用来注册数据库驱动的当第三方开发者开发数据库驱动时都会实现init函数在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设计的巧妙之处我们在变量赋值的时候经常看到这个符号它是用来忽略变量赋值的占位符那么包引入用到这个符号也是相似的作用这儿使用`_`的意思是引入后面的包名而不直接使用这个包中定义的函数,变量等资源。
>我们在2.3节流程和函数一节中介绍过init函数的初始化过程包在引入的时候会自动调用包的init函数以完成对包的初始化。因此我们引入上面的数据库驱动包之后会自动去调用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)
}
Prepare函数返回与当前连接相关的执行Sql语句的准备状态可以进行查询、删除等操作。
Close函数关闭当前的连接执行释放连接拥有的资源等清理工作。因为驱动实现了database/sql里面建议的conn pool所以你不用再去实现缓存conn之类的这样会容易引起问题。
Begin函数返回一个代表事务处理的Tx通过它你可以进行查询,更新等操作,或者对事务进行回滚、递交。
## driver.Stmt
Stmt是一种准备好的状态和Conn相关联而且只能应用于一个goroutine中不能应用于多个goroutine。
type Stmt interface {
Close() error
NumInput() int
Exec(args []Value) (Result, error)
Query(args []Value) (Rows, error)
}
Close函数关闭当前的链接状态但是如果当前正在执行queryquery还是有效返回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
这是一个Conn可选择实现的接口
type Execer interface {
Exec(query string, args []Value) (Result, error)
}
如果这个接口没有定义那么在调用DB.Exec,就会首先调用Prepare返回Stmt然后执行Stmt的Exec然后关闭Stmt。
## driver.Result
这个是执行Update/Insert等操作返回的结果接口定义
type Result interface {
LastInsertId() (int64, error)
RowsAffected() (int64, error)
}
LastInsertId函数返回由数据库执行插入操作得到的自增ID号。
RowsAffected函数返回query操作影响的数据条目数。
## driver.Rows
Rows是执行查询返回的结果集接口定义
type Rows interface {
Columns() []string
Close() error
Next(dest []Value) error
}
Columns函数返回查询数据库表的字段信息这个返回的slice和sql查询的字段一一对应而不是返回整个表的所有字段。
Close函数用来关闭Rows迭代器。
Next函数用来返回下一条数据把数据赋值给dest。dest里面的元素必须是driver.Value的值除了string返回的数据里面所有的string都必须要转换成[]byte。如果最后没数据了Next函数最后返回io.EOF。
## driver.RowsAffected
RowsAffected其实就是一个int64的别名但是他实现了Result接口用来底层实现Result的表示方式
type RowsAffected int64
func (RowsAffected) LastInsertId() (int64, error)
func (v RowsAffected) RowsAffected() (int64, error)
## driver.Value
Value其实就是一个空接口他可以容纳任何的数据
type Value interface{}
drive的Value是驱动必须能够操作的ValueValue要么是nil要么是下面的任意一种
int64
float64
bool
[]byte
string [*]除了Rows.Next返回的不能是string.
time.Time
## driver.ValueConverter
ValueConverter接口定义了如何把一个普通的值转化成driver.Value的接口
type ValueConverter interface {
ConvertValue(v interface{}) (Value, error)
}
在开发的数据库驱动包里面实现这个接口的函数在很多地方会使用到这个ValueConverter有很多好处
- 转化driver.value到数据库表相应的字段例如int64的数据如何转化成数据库表uint16字段
- 把数据库查询结果转化成driver.Value值
- 在scan函数里面如何把driver.Value值转化成用户定义的值
## driver.Valuer
Valuer接口定义了返回一个driver.Value的方式
type Valuer interface {
Value() (Value, error)
}
很多类型都实现了这个Value方法用来自身与driver.Value的转化。
通过上面的讲解,你应该对于驱动的开发有了一个基本的了解,一个驱动只要实现了这些接口就能完成增删查改等基本操作了,剩下的就是与相应的数据库进行数据交互等细节问题了,在此不再赘述。
## database/sql
database/sql在database/sql/driver提供的接口基础上定义了一些更高阶的方法用以简化数据库操作,同时内部还建议性地实现一个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直接拿出来用就是了如果不大于0则创建一个conn,然后再返回之。
## links
* [目录](<preface.md>)
* 上一节: [访问数据库](<05.0.md>)
* 下一节: [使用MySQL数据库](<05.2.md>)
# 5.1 database/sql interface
Go doesn't provide any official database drivers, unlike other languages like PHP which do. However, it does have some database driver interface standards for developers to develop database drivers with. The advantage is that if your code is developed according to these interface standards, you will not need to change any code if your database changes. Let's see what these database interface standards are.
## sql.Register
This function is in the `database/sql` package for registering database drivers when you use third-party database drivers. All of these should call the `Register(name string, driver driver.Driver)` function in `init()` in order to register themselves.
Let's take a look at the corresponding mymysql and sqlite3 driver code:
//https://github.com/mattn/go-sqlite3 driver
func init() {
sql.Register("sqlite3", &SQLiteDriver{})
}
//https://github.com/mikespook/mymysql driver
// 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)
}
We see that all third-party database drivers have implemented this function to register themselves, and Go uses a map to save user drivers inside of `databse/sql`.
var drivers = make(map[string]driver.Driver)
drivers[name] = driver
Therefore, this register function can register drivers as many as you want with different names.
We always see the following code when we use third-party drivers:
import (
"database/sql"
_ "github.com/mattn/go-sqlite3"
)
Here the underscore (also known as a 'blank') `_` can be quite confusing for many beginners, but this is a great feature in Go. We already know that this identifier is for discarding values from function returns, and also that you must use all packages that you've imported in your code in Go. So when the blank is used with import, it means that you need to execute the init() function of that package without directly using it, which exactly fits the use-case for registering database drivers.
## driver.Driver
`Driver` is an interface containing an `Open(name string)` method that returns a `Conn` interface.
type Driver interface {
Open(name string) (Conn, error)
}
This is a one-time Conn, which means it can only be used once in one goroutine. The following code will cause errors to occur:
...
go goroutineA (Conn) // query
go goroutineB (Conn) // insert
...
Because Go has no idea which goroutine does what operation, the query operation may get the result of the insert operation, and vice-versa.
All third-party drivers should have this function to parse the name of Conn and return the correct results.
## driver.Conn
This is a database connection interface with some methods, and as i've said above, the same Conn can only be used in one goroutine.
type Conn interface {
Prepare(query string) (Stmt, error)
Close() error
Begin() (Tx, error)
}
- `Prepare` returns the prepare status of corresponding SQL commands for querying and deleting, etc.
- `Close` closes the current connection and cleans resources. Most third-party drivers implement some kind of connection pool, so you don't need to cache connections unless you want to have unexpected errors.
- `Begin` returns a Tx that represents a transaction handle. You can use it for querying, updating, rolling back transactions, etc.
## driver.Stmt
This is a ready status that corresponds with Conn, so it can only be used in one goroutine like Conn.
type Stmt interface {
Close() error
NumInput() int
Exec(args []Value) (Result, error)
Query(args []Value) (Rows, error)
}
- `Close` closes the current connection but still returns row data if it is executing a query operation.
- `NumInput` returns the number of obligate arguments. Database drivers should check their caller's arguments when the result is greater than 0, and it returns -1 when database drivers don't know any obligate argument.
- `Exec` executes the `update/insert` SQL commands prepared in `Prepare`, returns `Result`.
- `Query` executes the `select` SQL command prepared in `Prepare`, returns row data.
## driver.Tx
Generally, transaction handles only have submit or rollback methods, and database drivers only need to implement these two methods.
type Tx interface {
Commit() error
Rollback() error
}
## driver.Execer
This is an optional interface.
type Execer interface {
Exec(query string, args []Value) (Result, error)
}
If the driver doesn't implement this interface, when you call DB.Exec, it will automatically call Prepare, then return Stmt. After that it executes the Exec method of Stmt, then closes Stmt.
## driver.Result
This is the interface for results of `update/insert` operations.
type Result interface {
LastInsertId() (int64, error)
RowsAffected() (int64, error)
}
- `LastInsertId` returns auto-increment Id number after a database insert operation.
- `RowsAffected` returns rows that were affected by query operations.
## driver.Rows
This is the interface for the result of a query operation.
type Rows interface {
Columns() []string
Close() error
Next(dest []Value) error
}
- `Columns` returns field information of database tables. The slice has a one-to-one correspondence with SQL query fields only, and does not return all fields of that database table.
- `Close` closes Rows iterator.
- `Next` returns next data and assigns to dest, converting all strings into byte arrays, and gets io.EOF error if no more data is available.
## driver.RowsAffected
This is an alias of int64, but it implements the Result interface.
type RowsAffected int64
func (RowsAffected) LastInsertId() (int64, error)
func (v RowsAffected) RowsAffected() (int64, error)
## driver.Value
This is an empty interface that can contains any kind of data.
type Value interface{}
The Value must be something that drivers can operate on or nil, so it should be one of following types:
int64
float64
bool
[]byte
string [*] Except Rows.Next which cannot return string
time.Time
## driver.ValueConverter
This defines an interface for converting normal values to driver.Value.
type ValueConverter interface {
ConvertValue(v interface{}) (Value, error)
}
This interface is commonly used in database drivers and has many useful features:
- Converts driver.Value to a corresponding database field type, for example converts int64 to uint16.
- Converts database query results to driver.Value.
- Converts driver.Value to a user defined value in the `scan` function.
## driver.Valuer
This defines an interface for returning driver.Value.
type Valuer interface {
Value() (Value, error)
}
Many types implement this interface for conversion between driver.Value and itself.
At this point, you should know a bit about developping database drivers in Go. Once you can implement interfaces for operations like add, delete, update, etc., there are only a few problems left related to communicating with specific databases.
## database/sql
databse/sql defines even more high-level methods on top of database/sql/driver for more convenient database operations, and it suggests that you implement a connection pool.
type DB struct {
driver driver.Driver
dsn string
mu sync.Mutex // protects freeConn and closed
freeConn []driver.Conn
closed bool
}
As you can see, the `Open` function returns a DB that has a freeConn, and this is a simple connection pool. Its implementation is very simple and ugly. It uses `defer db.putConn(ci, err)` in the Db.prepare function to put a connection into the connection pool. Everytime you call the Conn function, it checks the length of freeCoon. If it's greater than 0, that means there is a reusable connection and it directly returns to you. Otherwise it creates a new connection and returns.
## Links
- [Directory](preface.md)
- Previous section: [Database](05.0.md)
- Next section: [MySQL](05.2.md)