Database/SQL 与 GORM 实践 | 青训营笔记

21 阅读5分钟

🎁理解Database/Sql

database/sql包的存在意义?👉 提供一个统一的接口,让我们操作不一样的数据库

解释一下基本用法的代码:

图片替换文本

❗使用该代码需要先通过

go get github.com/go-sql-driver/mysql

来获取依赖

❗代码地8行的解释:

db,err:=sql.Open("mysql","user:password@tcp(127.0.0.1:3306)/hello")

其中user为mysql的的用户名,pssword为密码,hello指代的是具体数据库的名称。

❗users?user?

我们需要创建一个结构体例如

type User struct{
    ID int 
    Name string 
}

由上面的代码我们可以看到,我们创建了一个名为users的User类型的数组,通过for循环和rows指向的结果逐条塞进user当中。

⚠在这个例子当中,如果我们切换其他数据库,我们只需要把mysql更改为其他的数据源即可,体现出了我们database/sql包的灵活性

🎁设计原理

image.png

❗连接池:采用池化技术,如果遇到请求特别大的情况,会影响我们的程序性能,使用池化技术就能够优化我们的系统。主要应用于把比较昂贵、费事的资源整合到一个特定的池子里。大幅度提高服务性能。

database/sql:具体实现sql执行的过程?

for i:=0 ;i<maxBadConnRetries;i++{
    dc.err:=db.conn(ctx,strategy)
    defer dc.db.putConn(dc,err,true)
    if err!=nil{
        err=dc.ci.Query(sql,args...)
    }
    isBadConn=errors.Is(err,driver.ErrBadConn)
    if !isBadConn{
        break
    }
}

代码解释:

❗其中在for循环中我们默认的maxBadConnRetries是两次,也就是说会有两次连接,如果我们从连接池获取到一个链接的时候,我们也会有两种策略dc,err:=db.conn(ctx,stategy),一种策略是“尽量呼应”(连接池中已经有链接了,我们尽量去使用这个原来的链接),另外一种是“新建一个新的链接”。获取到连接之后。defer表示我们处理完这个连接之后,将连接放回我们的连接池,同时会进行检查。最后判断有没有返回任何错误,如果有,再进行尝试。

它是如何预留接口的?:Driver连接接口

//Driver接口
type Driver interface{
    Open(name string)(Conn,error)
}
//注册全局Driver
func Register(name string,driver driver.Driver){
    driverMu.Lock()
    defer driverMu.Unlock()
    if driver==nil{
        panic("sql:Register driver is nil")
    }
    if _,dup:=driver[name];dup{
        panic("sql:Register called twice for driver "+name)
    }
    drivers[name]=driver
}

❗解释一下课堂中没有讲的panic:

在 Golang 中,当执行代码遇到异常情况时,可以使用 panic 函数产生异常,程序会终止运行并输出异常信息。

panic 函数的语法如下:

func panic(v interface{})

panic 函数接收一个 interface 类型的参数 v,可以是任何类型。当调用 panic 函数时,程序会终止运行,并打印出相应的错误信息。

一般情况下,panic 的使用场景有两种:

  1. 当发生不可预料的异常情况时,我们可以使用 panic 函数抛出异常,以便在统计和分析程序错误时提供更多的信息;
  2. 在程序开发过程中,我们可以使用 panic 函数模拟一些异常情况,以便更好的测试程序的稳定性和容错性。

在 Go 语言中,panic 的典型用法是处理不可恢复的运行时错误,例如数组越界、类型断言、空指针引用、除零等等。因为这些错误无法恢复,程序无法继续运行,此时可以调用 panic 函数来终止程序的执行,并输出相应的错误信息。

举个例子,如果我们要实现一个函数将一个数组的元素倒序,并且要求该数组不能为空,我们可以这样写:

func ReverseArray(arr []int) []int {
    if len(arr) == 0 {
        panic("数组不能为空")
    }

    for i, j := 0, len(arr)-1; i < j; i, j = i+1, j-1 {
        arr[i], arr[j] = arr[j], arr[i]
    }
    return arr
}

在上述代码中,我们首先判断了输入的数组是否为空,如果为空,则调用 panic 函数抛出异常。这样,在使用 ReverseArray 函数时,如果传入空数组,程序会直接终止,并输出异常信息。

注意,当调用 panic 函数时,程序会在当前函数中立即停止执行,但是该函数可能会被其它函数调用,甚至可能被其它 goroutine 调用。在这种情况下,panic 会一直向外层调用的函数传递,直到程序中止运行或者被 defer 函数捕获。

类似于其他编程语言中的 try-catch 语句,Go 语言也提供了 recover 函数用来捕获 panic,recover 函数的语法如下:

func recover() interface{}

该函数会返回传递给 panic 函数的值,并让程序继续执行。如果当前函数中没有遇到 panic,recover 函数会返回 nil。

举个例子:

func foo() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("捕获到了panic的信息:", r)
        }
    }()
    panic("抛出了一个异常")
    fmt.Println("这句话不会执行到")
}

在上述代码中,我们使用了 defer 函数在 panic 函数后面注册了一个用来捕获异常信息的匿名函数,当程序遇到 panic 函数时,该匿名函数会被调用,并输出相应的错误信息。由于使用了 recover 函数,程序不会立即停止运行,而是继续执行后面的代码,例如上述代码中的 fmt.Println 函数就不会被执行到。

需要注意的是,在使用 recover 函数时,必须在 defer 函数中调用 recover 函数,而不能在函数体中调用 recover 函数,否则 recover 函数不会被执行,程序也不会停止运行。因此,在使用 recover 函数时,一定要在 defer 函数中进行调用。