温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

Go语言开发(十七)、Go语言database/sql接口

发布时间:2020-06-22 20:20:51 来源:网络 阅读:1271 作者:天山老妖S 栏目:编程语言

Go语言开发(十七)、Go语言database/sql接口

一、database/sql接口

Go语言官方没有提供数据库驱动,而是为开发数据库驱动定义了标准接口database/sql,开发者可以根据database/sql接口来开发相应的数据库驱动,只要是按照标准接口database/sql开发的代码,以后需要迁移数据库时,不需要任何修改。

二、database/sql常用接口

1、sql.Register

sql.Register函数用来注册数据库驱动,第三方开发者开发数据库驱动时,会在init函数内调用sql.Register完成本驱动的注册。

func Register(name string, driver driver.Driver) {
   driversMu.Lock()
   defer driversMu.Unlock()
   if driver == nil {
      panic("sql: Register driver is nil")
   }
   if _, dup := drivers[name]; dup {
      panic("sql: Register called twice for driver " + name)
   }
   drivers[name] = driver
}

Go-SQL-Driver/MySQL数据库驱动的实现如下:

func init() {
   sql.Register("mysql", &MySQLDriver{})
}

第三方数据库驱动通常通过调用sql.Register函数来注册自己的数据库驱动名称以及相应的driver实现。在database/sql内部通过一个map来存储用户定义的相应驱动。
var drivers = make(map[string]driver.Driver)

2、driver.Driver

Driver是一个数据库驱动的接口,定义了一个Open(name string)方法,返回一个数据库的Conn接口。

type Driver interface {
      Open(name string) (Conn, error)
}

Conn只能用来进行一次goroutine操作,即Conn应用于多个goroutine。
第三方驱动都会实现driver.Driver接口,Open方法会解析name参数来获取相关数据库的连接信息,解析完成后,使用此连接信息来初始化一个Conn并返回。

3、driver.Conn

driver.Conn是一个数据库连接的接口,定义了一系列方法,Conn只能应用在一个goroutine中,不能使用在多个goroutine中。

type Conn interface {
   Prepare(query string) (Stmt, error)
   Close() error
   Begin() (Tx, error)
}

Prepare函数返回与当前连接相关的执行Sql语句的准备状态,可以进行查询、删除等操作。
Close函数关闭当前的连接,执行释放连接拥有的资源等清理工作。通常第三方数据库驱动实现了database/sql建议的连接池,开发者不必去实现缓存连接。
Begin函数返回一个代表事务处理的Tx,通过Tx可以进行查询、更新等操作或者对事务进行回滚、递交。

4、driver.Stmt

driver.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结果集

5、driver.Tx

driver.Tx是事务接口,包含Commit、Rollback方法,数据库驱动只需实现Commit、Rollback函数即可。

type Tx interface {
   Commit() error
   Rollback() error
}

Go-SQL-Driver/MySQL数据库驱动的实现如下:

type mysqlTx struct {
   mc *mysqlConn
}

func (tx *mysqlTx) Commit() (err error) {
   if tx.mc == nil || tx.mc.closed.IsSet() {
      return ErrInvalidConn
   }
   err = tx.mc.exec("COMMIT")
   tx.mc = nil
   return
}

func (tx *mysqlTx) Rollback() (err error) {
   if tx.mc == nil || tx.mc.closed.IsSet() {
      return ErrInvalidConn
   }
   err = tx.mc.exec("ROLLBACK")
   tx.mc = nil
   return
}

6、driver.Execer

driver.Execer是一个Conn可选择实现的接口。

type Execer interface {
   Exec(query string, args []Value) (Result, error)
}

如果第三方数据库驱动没有实现driver.Execer接口,调用DB.Exec会首先调用Prepare返回Stmt,然后执行Stmt的Exec,然后关闭Stmt。

7、driver.Result

driver.Result是执行Update/Insert等操作返回的结果接口。

type Result interface {
   LastInsertId() (int64, error)
   RowsAffected() (int64, error)
}

LastInsertId函数返回由数据库执行插入操作得到的自增ID号。
RowsAffected函数返回query操作影响的数据条目数。

8、driver.Rows

driver.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。

9、driver.RowsAffected

driver.RowsAffected是int64的别名,但实现了Result接口,用来底层实现Result的表示方式。

type RowsAffected int64

var _ Result = RowsAffected(0)

func (RowsAffected) LastInsertId() (int64, error) {
   return 0, errors.New("no LastInsertId available")
}

func (v RowsAffected) RowsAffected() (int64, error) {
   return int64(v), nil
}

10、driver.Value

driver.Value是空接口,是数据库驱动必须能够操作的Value,Value可以是nil,int64,float64,bool,[]bytestring,time.Time。
type Value interface{}

11、driver.ValueConverter

driver.ValueConverter接口定义了如何把一个普通的值转化成driver.Value的接口

type ValueConverter interface {
   // ConvertValue converts a value to a driver Value.
   ConvertValue(v interface{}) (Value, error)
}

driver.ValueConverter接口实现如下:

type stringType struct{}

func (stringType) ConvertValue(v interface{}) (Value, error) {
   switch v.(type) {
   case string, []byte:
      return v, nil
   }
   return fmt.Sprintf("%v", v), nil
}

数据库驱动开发中,ConvertValue方法用途广泛:
(1)转化driver.value到数据库表相应的字段,例如int64的数据如何转化成数据库表uint16字段。
(2)把数据库查询结果转化成driver.Value值
(3)在scan函数里面如何把driver.Value值转化成用户定义的值

12、driver.Valuer

driver.Valuer接口定义一个返回driver.Value的方法。

type Valuer interface {
   // Value returns a driver Value.
   Value() (Value, error)
}

13、sql.Open

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
}

Open函数返回DB对象,

type DB struct {
   connector driver.Connector
   numClosed uint64

   mu           sync.Mutex // protects following fields
   freeConn     []*driverConn
   connRequests map[uint64]chan connRequest
   nextRequest  uint64 // Next key to use in connRequests.
   numOpen      int    // number of opened and pending open connections
   openerCh    chan struct{}
   resetterCh  chan *driverConn
   closed      bool
   dep         map[finalCloser]depSet
   lastPut     map[*driverConn]string // stacktrace of last conn's put; debug only
   maxIdle     int                    // zero means defaultMaxIdleConns; negative means 0
   maxOpen     int                    // <= 0 means unlimited
   maxLifetime time.Duration          // maximum amount of time a connection may be reused
   cleanerCh   chan struct{}

   stop func() // stop cancels the connection opener and the session resetter.
}

freeConn是简易的连接池。当执行db.prepare -> db.prepareDC时会defer dc.releaseConn,然后调用db.putConn,把连接放入连接池,每次调用db.conn的时候会先判断freeConn的长度是否大于0,大于0说明有可以复用的conn,如果不大于0,则创建一个conn,然后返回。

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI