gorm.Open()与sql包的源码_1

Liiu · · 3178 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

欢迎关注我的:[go源码专栏](https://zhuanlan.zhihu.com/c_135808959) ####gorm的Open函数 ``` db, err := gorm.Open(config.GetDBName(), config.GetDBSource()) ``` ####gorm.Open() ``` func Open(dialect string, args ...interface{}) (db *DB, err error) { var source string var dbSQL SQLCommon switch value := args[0].(type) { case string: var driver = dialect if len(args) == 1 { fmt.Println("args[0]:",args[0]) fmt.Println("value:",value) source = value } else if len(args) >= 2 { driver = value source = args[1].(string) } dbSQL, err = sql.Open(driver, source) case SQLCommon: dbSQL = value } db = &DB{ db: dbSQL, logger: defaultLogger, values: map[string]interface{}{}, callbacks: DefaultCallback, dialect: newDialect(dialect, dbSQL), } db.parent = db if err != nil { return } // Send a ping to make sure the database connection is alive. if d, ok := dbSQL.(*sql.DB); ok { if err = d.Ping(); err != nil { d.Close() } } return } ``` 首先根据第一个参数得到:SQLCommon接口的实例,sql.db实现. 通过调用sql.Open(driver, source)得到, 或者直接传入. ####SQLCommon接口 ``` // SQLCommon is the minimal database connection functionality gorm requires. Implemented by *sql.DB. type SQLCommon interface { Exec(query string, args ...interface{}) (sql.Result, error) Prepare(query string) (*sql.Stmt, error) Query(query string, args ...interface{}) (*sql.Rows, error) QueryRow(query string, args ...interface{}) *sql.Row } ``` SQLCommon是最小的数据库连接功能.*sql.DB实现的.(sql包实现的连接池.) ####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) } db := &DB{ driver: driveri, dsn: dataSourceName, openerCh: make(chan struct{}, connectionRequestQueueSize), lastPut: make(map[*driverConn]string), connRequests: make(map[uint64]chan connRequest), } go db.connectionOpener() return db, nil } ``` 首先这里有个读写锁.(防止读的时候被更改) 获取mysql的init()赋值过来的驱动driver. 然后根据这个驱动实现的driver,得到连接池DB的一个实例指针db. 最后开启一个线程go db.connectionOpener().返回这个db. ####connectionOpener() ``` // Runs in a separate goroutine, opens new connections when requested. func (db *DB) connectionOpener() { for range db.openerCh { db.openNewConnection() } } ``` 在一个独立的goroutine中,当有需要的时候开启一个新连接. 这个地方会一直阻塞在这里,因为db.openerCh 是一个channel,当db.openerCh 中没有放入数据的时候,会一直阻塞在这里. 所以这里必须是一个独立的goroutine,否则会死锁. openerCh chan struct{} ####openNewConnection() ``` // Open one new connection func (db *DB) openNewConnection() { // maybeOpenNewConnctions has already executed db.numOpen++ before it sent // on db.openerCh. This function must execute db.numOpen-- if the // connection fails or is closed before returning. ci, err := db.driver.Open(db.dsn) db.mu.Lock() defer db.mu.Unlock() if db.closed { if err == nil { ci.Close() } db.numOpen-- return } if err != nil { db.numOpen-- db.putConnDBLocked(nil, err) db.maybeOpenNewConnections() return } dc := &driverConn{ db: db, createdAt: nowFunc(), ci: ci, } if db.putConnDBLocked(dc, err) { db.addDepLocked(dc, dc) } else { db.numOpen-- ci.Close() } } ``` 这个开启新的连接函数 是真正建立数据库连接,而且是通过最开始注册的比如mysql driver来连接的. 返回一个数据库连接conn. 并且由于conn是有状态的,不能被多个goroutine共享,所以每个goroutine要自己开启一个conn, 并且保存起来,如下 dc := &driverConn{ db: db, createdAt: nowFunc(), ci: ci, } 这样将共享的连接池db和独有的连接ci 保存在一个新的连接driverConn(包含了锁?)里面,然后putConnDBLocked ####putConnDBLocked(dc *driverConn, err error) 作用主要是:满足一个连接请求或者将一个连接放到闲置连接池 就返回true,否则false ``` func (db *DB) putConnDBLocked(dc *driverConn, err error) bool { if db.closed { return false } if db.maxOpen > 0 && db.numOpen > db.maxOpen { return false } if c := len(db.connRequests); c > 0 { var req chan connRequest var reqKey uint64 for reqKey, req = range db.connRequests { break } delete(db.connRequests, reqKey) // Remove from pending requests. if err == nil { dc.inUse = true } req <- connRequest{ conn: dc, err: err, } return true } else if err == nil && !db.closed && db.maxIdleConnsLocked() > len(db.freeConn) { db.freeConn = append(db.freeConn, dc) db.startCleanerLocked() return true } return false } ``` 如果db.connRequests有请求,就range里面的请求. 因为db.connRequests是一个map,并且值是 channel. (channel一定是引用!?) 所以满足连接条件后,将db.connRequests删除这个请求,并且将传入的新建的连接dc放到这个请求req中. 然后返回true. #### 满足连接请求之后(返回true) ``` func (db *DB) addDepLocked(x finalCloser, dep interface{}) { if db.dep == nil { db.dep = make(map[finalCloser]depSet) } xdep := db.dep[x] if xdep == nil { xdep = make(depSet) db.dep[x] = xdep } xdep[dep] = true } ``` 这个不知道为什么要这么做,好像是加了一个锁(加了一个判断),把这个新建的连接dc锁住. ##最后回到sql.Open 最后返回的是db. 中间主要是两步,第一步是创建的了一个db实例返回,第二步是在这个db里面,开启了一个一直在阻塞的goroutine,处理db中放入连接请求通道中的请求.一旦放入连接请求,就会创建一个新的连接. ##最后回到gorm.Open 返回了一个sql.db之后, ``` db = &DB{ db: dbSQL, logger: defaultLogger, values: map[string]interface{}{}, callbacks: DefaultCallback, dialect: newDialect(dialect, dbSQL), } db.parent = db if err != nil { return } // Send a ping to make sure the database connection is alive. if d, ok := dbSQL.(*sql.DB); ok { if err = d.Ping(); err != nil { d.Close() } } return ``` 将db放入gorm中的db实例中的db属性中,并且设置该gorm.db的parent为其本身. 最后再ping一次,检测是否能ping通. 然后返回这个gorm的db. 到此结束. --作者白维,转载请通知作者--

有疑问加站长微信联系(非本文作者))

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

3178 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传