从前一篇中我们可以发现,实现表的基本CRUD,包括处理关系,并不是很复杂,但很无趣,代码又多。和其他语言一样,golang有一些第三方库实现了关系的映射或ORM。
我们看使用 sqlx 库如何简化前面对 Post 的操作:sqlx库基本和sql是兼容的,所以,一些地方只是少量修改。建好项目后先用 go get "github.com/jmoiron/sqlx" 添加该库。
package main
import (
"fmt"
"log"
"time"
_ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"
)
type Post struct {
Id int
Content string
AuthorName string `db:"author"` // tag标记不要有空格,双引号包围!!
}
var Db *sqlx.DB
func init() {
var err error
Db, err = sqlx.Open("mysql", "gwp:dbpassword@tcp(172.17.0.1:3306)/gwp?charset=utf8mb4,utf8")
if err != nil {
panic(err)
}
}
func GetPost(id int) (post Post, err error) {
post = Post{}
err = Db.QueryRowx("SELECT id, content, author FROM post WHERE id = ?", id).StructScan(&post)
if err != nil {
return
}
return
}
func (post *Post) Create() (err error) {
sql := "INSERT INTO post (content, author) VALUES (?, ?)"
if result, err := Db.Exec(sql, post.Content, post.AuthorName); err != nil {
log.Fatal(err)
} else {
if last_insert_id, err := result.LastInsertId(); err != nil {
log.Fatal(err)
} else {
post.Id = int(last_insert_id)
}
}
return
}
func main() {
post := Post{Content: "你好, C++! " + time.Now().Format("150405"), AuthorName: "李四"}
post.Create()
fmt.Println(post) // 显示刚刚创建的
if post_read, err := GetPost(5); err != nil { // 注意:这里 id 为 5 的记录必须存在
log.Fatal(err)
} else {
fmt.Println(post_read)
}
}
运行结果类似如下:
sjg@sjg-PC:~/go/src/db_store3$ go run .
{11 你好, C++! 163822 李四}
{5 你好, C++ 李四}
代码中将作者字段改名为 AuthorName 是为了刻意演示tag标记的用法,tag标记类似json中的用法,注意冒号和名称之间不要有空格,名称用双引号包围。
Db改成了类型为 *sqlx.DB,打开相应修改为 sqlx.Open,查询一行修改为 Db.QueryRowx 且绑定时直接绑定到结构体变量(这里省了很多事)。
和 Gorm 相比,sqlx太过简单,功能太弱了。我们来看一下 Gorm 实现的 ORM:建好项目后先用 go get "github.com/jinzhu/gorm" 添加该库,因为我们下面的代码对Post和Comment增加了时间信息,而且Gorm对表名的规则是使用复数,而我们前面表名是单数,Gorm又能AutoMigrate创建表,所以,我们先把 post表和comment表drop掉,即 DROP TABLE comment; DROP TABLE post;
package main
import (
"fmt"
"time"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/mysql"
)
type Post struct {
Id int
Content string
Author string `sql:"not null"`
Comments []Comment
CreatedAt time.Time // 注意 time.Time 类型需要连接 DSN串添加 parseTime 参数
}
type Comment struct {
Id int
Content string
Author string `sql:"not null"`
PostId int `sql:"index"`
CreatedAt time.Time
}
var Db *gorm.DB
func init() { // 此 init 不显式调用,自动隐式调用,实现初始化全局变量 Db
var err error
Db, err = gorm.Open("mysql", "gwp:dbpassword@tcp(172.17.0.1:3306)/gwp?charset=utf8mb4,utf8&parseTime=true")
if err != nil {
panic(err)
}
Db.AutoMigrate(&Post{}, &Comment{}) // 自动 migration
}
func main() {
post := Post{Content: "你好, C++! " + time.Now().Format("15:04:05"), Author: "李四"}
fmt.Println(post) // 创建前打印
Db.Create(&post) // 表中插入模型数据
fmt.Println(post) // 创建后打印
comment := Comment{Content: "C++确实好,就是太难学" + time.Now().Format("15:04:05"), Author: "张三"}
Db.Model(&post).Association("comments").Append(comment) // 在模型的关联表中插入记录
var post_read Post
Db.Where("author = ?", "李四").First(&post_read) // 查询获取模型的一个实例
fmt.Println(post_read) // 读取李四的一个帖子
var comments []Comment
Db.Model(&post_read).Related(&comments) // 获取模型实例关联的模型列表
fmt.Println(comments[0]) // 读取前面的李四帖子的第一条评论
}
输出结果:
sjg@sjg-PC:~/go/src/db_store4$ go run .
{0 你好, C++! 08:51:08 李四 [] 0001-01-01 00:00:00 +0000 UTC}
{3 你好, C++! 08:51:08 李四 [] 2023-05-12 08:51:08.408810808 +0800 CST m=+0.007789272}
{1 你好, C++! 08:33:04 李四 [] 2023-05-12 00:33:04 +0000 UTC}
{1 C++确实好,就是太难学08:33:04 张三 1 2023-05-12 00:33:04 +0000 UTC}
上面的代码中,Db.AutoMigrate(...) 方法接近 yii2 中命令 ./yii migrate/create ,编写建表、修改表、删表语句,再 ./yii migrate/up,只是 Db.AutoMigrate(..)只会自动建表或给表添加字段,不会删除字段或者删除表。实际应用中让 Gorm 自动建表可能不一定合适,目前还不清楚怎么让 string类型字段去生成text类型的表字段。
Db的Create(..)方法插入记录,Model(..)方法指定待操作的模型,Association(..)进入关联模式(返回*Association),Related(..)获取关联关系,Where(..)对应SQL WHERE语句,First(..) 获取第一个记录。Association的 Append(..) 添加关联的模型。这些方法具体用法可以查阅官网gorm package - github.com/jinzhu/gorm - Go Packages。
Gorm下打包了常见数据库的驱动,所以,可以直接从它引入mysql驱动。另外,golang的time.Time不能简单和数据库的数据类型对应,如果你也遇到类似以下错误 sql: Scan error on column index 3, name "created_at": unsupported Scan, storing driver.Value type []uint8 into type *time.Time,那么多半是数据库连接的DSN中少了parseTime参数。
真正要熟练使用 Gorm ORM方式对数据库进行操作,不是一两个例子代码能够学会的。