上一篇文章,项目中的greenlight数据库已经创建好了,接下来一起看看如何从Go应用程序连接到数据库。
要连接SQL数据库,我们需要使用一个数据库驱动程序,作为Go和数据库之间的“中间人”。你可以在Go wiki中找到一个可用的PostgreSQL驱动程序列表,但对于我们的项目,我们将选择pq包,它的特点是常用、可靠和功能完善。
如果你跟随本系列文章操作,那么使用go get下载pq的v1.10.0版本,如下所示:
$ go get github.com/lib/pq@v1.10.0
go: downloading github.com/lib/pq v1.10.0
为了连接到数据库,我们还需要一个数据源名称(DSN),它是一个包含必要连接参数的字符串。DSN格式将取决于您使用的数据库驱动程序(应该在驱动程序文档中有描述),但当使用pq时,以greenlight用户结合DSN,您应该能够连接到您的本地greenlight数据库:
postgres://greenlight:pa55word@localhost/greenlight
创建数据库连接池
我们在Go应用程序连接到greenlight数据库的代码几乎与mysql中的代码完全相同。我就不细讲了,希望大家都很熟悉。
包含以下几点:
- 我们希望DSN在运行时可配置,因此我们将使用命令行参数将它传递给应用程序,而不是硬编码。在开发过程中,为了简单起见,我们将使用上面的DSN作为参数的默认值。
- 在cmd/api/main.go文件中我们创建一个新的openDB()函数。在该函数中,使用sql.Open()来建立sql.DB连接池,因为数据库连接是在第一次需要时惰性地建立的。因此,我们还需要使用db.PingContext()方法来创建一个实际的连接,并验证数据库设置正确。
回到cmd/api/main.go文件更新代码如下:
File: cmd/api/main.go
package main
import (
"context"
"database/sql"
"flag"
"fmt"
"log"
"net/http"
"os"
"time"
//导入pq驱动程序,通过包database/sql来注册数据库驱动程序
_ "github.com/lib/pq"
)
const version = "1.0.0"
//添加一个db结构字段来保存数据库连接的配置
type config struct {
port int
env string
db struct {
dsn string
}
}
type application struct {
config config
logger *log.Logger
}
func main() {
var cfg config
flag.IntVar(&cfg.port, "port", 4000, "API server port")
flag.StringVar(&cfg.env, "env", "development", "Environment (development|staging|production)")
// 将DSN值从db-dsn命令行参数中读入配置,设置默认值
flag.StringVar(&cfg.db.dsn, "db-dsn", "postgres://greenlight:pa55word@localhost/greenlight", "PostgreSQL DSN")
flag.Parse()
logger := log.New(os.Stdout, "", log.Ldate|log.Ltime)
//调用openDB()帮助函数(参见下面)来创建连接池
db, err := openDB(cfg)
if err != nil {
logger.Fatal(err)
}
// defer调用, 以便main()函数退出之前关闭连接池。
defer db.Close()
//打印连接数据库成功日志
logger.Printf("database connection pool established")
app := &application{config: cfg,
logger: logger}
srv := &http.Server{
Addr: fmt.Sprintf(":%d", cfg.port), Handler: app.routes(),
IdleTimeout: time.Minute,
ReadTimeout: 10 * time.Second, WriteTimeout: 30 * time.Second,
}
logger.Printf("starting %s server on %s", cfg.env, srv.Addr)
err = srv.ListenAndServe()
logger.Fatal(err)
}
//openDB()函数返回一个sql.DB连接池。
func openDB(cfg config) (*sql.DB, error) {
//使用sql.Open()创建一个空连接池
db, err := sql.Open("postgres", cfg.db.dsn)
if err != nil {
return nil, err
}
//创建一个具有5秒超时期限的上下文。
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
//使用PingContext()建立到数据库的新连接,并传入上下文信息,连接超时就返回
err = db.PingContext(ctx)
if err != nil {
return nil, err
}
// 返回sql.DB连接池
return db, nil
}
重要提示:我们将在本书的后面讨论如何使用上下文来正确地管理超时,所以现在不要过多地担心这个问题。现在,只需要知道PingContext()调用不能在5秒内完成,将返回一个超时错误。
接下来启动应用程序,你将看到条关于连接数据库成功的日志,如下所示:
$ go run ./cmd/api
2021/04/07 15:56:54 database connection pool established
2021/04/07 15:56:54 starting development server on :4000
解耦DSN
目前,DSN的命令行参数默认值是在cmd/api/main.go文件中写死的。尽管DSN中的用户名和密码只是用于您自己机器上的开发数据库,但最好不要将这些敏感信息硬编码到项目文件中(这些文件可能在未来被共享或分发)。
因此,我们采取一些步骤将DSN从我们的项目代码中解耦,并将其存储为本地机器上的一个环境变量。
如果你按照本文操作,先创建GREENLIGHT_DB_DSN环境变量,在$HOME/.profile文件中添加如下内容:
File: $HOME/.profile
...
export GREENLIGHT_DB_DSN='postgres://greenlight:pa55word@localhost/greenlight'
或者在$HOME/.bashrc文件中添加以上内容也可以。
完成上面操作之后,需要重新启动您的计算机,如果现在不方便重启的话,可以在刚刚编辑的文件上运行source命令来实现环境变量生效。例如:
$ source $HOME/.profile
注意:运行source命令只会在当前的终端窗口生效。因此,如果您切换到另一个终端,您需要再次运行source生效。
不管怎样,一旦您重启机器或运行source,通过运行echo命令就可以在终端中看到GREENLIGHT_DB_DSN环境变量的值。像这样:
$ echo $GREENLIGHT_DB_DSN
postgres://greenlight:pa55word@localhost/greenlight
下面更新cmd/api/main.go文件,可以通过访问环境变量来获取DSN值。Go中读取环境变量值可以调用os.Getenv()函数,然后将DSN命令行参数默认值设置为环境变量值。
File:cmd/api/main.go
package main
...
func main() {
var cfg config
flag.IntVar(&cfg.port, "port", 4000, "API server port")
flag.StringVar(&cfg.env, "env", "development", "Environment (development|staging|production)")
// 使用GREENLIGHT_DB_DSN环境变量,作为DSN命令行参数默认值
flag.StringVar(&cfg.db.dsn, "db-dsn", os.Getenv("GREENLIGHT_DB_DSN"), "PostgreSQL DSN")
flag.Parse()
...
}
如果您现在再次重新启动应用程序,您应该会发现可以正常编译,并像之前一样工作。
您还可以尝试在运行应用程序时指定-help标志。应该会输出三个命令行参数的描述内容和默认值,包括从环境变量中提取的DSN值。类似于:
$ go run ./cmd/api -help
Usage of /tmp/go-build417842398/b001/exe/api:
-db-dsn string
PostgreSQL DSN (default "postgres://greenlight:pa55word@localhost/greenlight")
-env string
Environment (development|staging|production) (default "development")
-port int
API server port (default 4000)
附加内容
psql使用DSN连接数据库
将DSN存储在环境变量中的一个很好处是:能够以greenlight用户轻松地连接到greenlight数据库,而不是在运行psql时手动指定所有连接配置。像这样:
$ psql $GREENLIGHT_DB_DSN
psql (13.4)
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, bits: 256, compression: off)
Type "help" for help.
greenlight=>