在完成了数据库的初步设置之后,现在是时候创建我们的首条数据库记录了。本节还会用到之前几节展示过的Post
结构,跟之前不一样的是,这次展示的程序将不会再把Post
结构包含的信息存储到内存或者文件中,而是把这些信息存储到Postgres数据库中,并在需要的时候从数据库中获取这些信息。
前面的示例程序向我们展示了如何使用不同的函数执行数据的创建、获取、更新和删除操作,而在这一节,我们将会了解到使用Create
函数创建新帖子的更多细节。在仔细研究Create
函数的代码之前,让我们先来了解一下创建帖子的具体步骤。
创建帖子首先要做的是创建一个Post
结构,并为该结构的Content
字段和Author
字段设置值。需要注意的是,因为结构的Id
字段的值通常是由数据库的自增主键自动生成的,所以我们并不需要为这个字段设置值。
post := Post{Content: "Hello World!", Author: "Sau Sheong"}
如果我们现在使用一个fmt.Println
语句打印这个结构,会看到Id
字段的值被初始化成了0:
fmt.Println(post) //①
现在,我们可以通过执行Post
结构的Create
方法,把结构中包含的数据存储到数据库的记录(record)里面:
post.Create()
Create
方法在发生故障时会返回一个错误,但为了让代码保持简单,我们这里暂且先省略相应的错误处理代码。现在,再次打印这个Post
结构:
fmt.Println(post) //①{1 Hello World! Sau Sheong}
从打印的结果可以看到,Id
字段的值现在被设置成了1
。在了解了使用Create
函数创建新帖子的具体步骤之后,现在是时候来看看代码清单6-8,了解一下它的具体实现代码了。
代码清单6-8 创建一篇帖子
func (post *Post) Create() (err error) {
statement := "insert into posts (content, author) values ($1, $2) returning id "
stmt, err := db.Prepare(statement)
if err != nil {
return
}
defer stmt.Close()
err = stmt.QueryRow(post.Content, post.Author).Scan(&post.Id)
if err != nil {
return
}
return
}
Create
函数是Post
结构的一个方法,这一点可以通过Create
函数的定义看出:在func
关键字和函数名Create
之间,有一个指向Post
结构的引用,这个名为post
的引用也被称为方法的接收者receiver
,接收者可以不使用&
符号,直接在方法内部对结构进行引用。
Create
方法做的第一件事是定义一条SQL
预处理语句,一条预处理语句(prepared statement)就是一个SQL
语句模板,这种语句通常用于重复执行指定的SQL
语句,用户在执行预处理语句时需要为语句中的参数提供实际值。
比如,在创建数据库记录的时候,Create
函数就会使用实际值去替换以下语句中的$1
和$2
:
statement := "insert into posts (content, author) values ($1, $2) returning id"
除了在数据库里面创建记录之外,这个语句还会要求数据库返回id
列的值,本文稍后就会说明这样做的具体原因。
为了创建预处理语句,程序使用了sql.DB
结构的Prepare
方法:
stmt, err := db.Prepare(statement)
这行代码会创建一个指向sql.Stmt
接口的引用,这个引用就是上面提到的预处理语句。sql.Stmt
接口的定义位于sql.Driver
包当中,而具体的结构则由数据库驱动实现。
之后,程序会调用预处理语句的QueryRow
方法,并把来自接收者的数据传递给该方法,以此来执行预处理语句:
err = stmt.QueryRow(post.Content, post.Author).Scan(&post.Id)
我们之所以在这里使用QueryRow
方法,是因为我们只想要获取一个指向sql.Row
结构的引用:如果QueryRow
发现被执行的SQL语句返回了多于一个sql.Row
,那么它只会返回结果中的第一个sql.Row
,并丢弃剩余的所有sql.Row
。因为QueryRow
方法的返回值只有一个sql.Row
结构,它不会返回任何错误,所以QueryRow
方法通常会跟Row
结构的Scan
方法搭配使用,并由Scan
方法把行中的值复制到程序为其提供的参数里面。正如上面的代码所示,Scan
方法会把SQL查询语句返回的id
列的值设置为post
接收者的Id
字段的值,这也是我们前面在编写预处理语句时,要求SQL
查询语句返回id
列的值的原因。很明显,因为接收者的Content
字段和Author
字段都已经有值了,所以程序最后要做的就是将接收者的Id
字段的值设置成数据库生成的自增整数。现在,正如你所料,因为post
变量的Id
字段也已经设置了值,所以程序得到的将是一个完整地进行了设置的Post
结构,并且该结构包含的数据与数据库记录的数据完全一致。