golang web学习随便记5-关系映射、ORM

从前一篇中我们可以发现,实现表的基本CRUD,包括处理关系,并不是很复杂,但很无趣,代码又多。和其他语言一样,golang有一些第三方库实现了关系的映射或ORM。

sqlx库简化操作

我们看使用 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库实现ORM

和 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方式对数据库进行操作,不是一两个例子代码能够学会的。

你可能感兴趣的:(学习)