【Go Web开发】连接PostgreSQL数据库

上一篇文章,项目中的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=>
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容