欢迎关注我的:[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.
到此结束.
--作者白维,转载请通知作者--
有疑问加站长微信联系(非本文作者))