xorm最佳实践

489 阅读3分钟

GORM vs XORM

本来我是用GORM来着,原因是它在GitHub上的Star是最多的,随大流总是不会错的,
github.com/search?q=or…

image.png

后来无意中发现XORM的案例中好多耳熟能详的项目:
xorm.io/zh/docs/cha…
image.png

加上平常使用GORM时,确实有一些不适应的地方,索性就给换成XORM了。
然后累积了一点点个人经验,不一定适合其他人,但也想写个文章总结一下。

Sync 同步数据库结构

xorm.io/zh/docs/cha…
这种模式叫代码优先,先写struct再生成数据库,
我也尝试了XORMSync方法,挺好的,没啥毛病,
但我个人还是喜欢数据库优先,先用数据库工具创建表,再写struct
(可以搜索一下sql to struct,有在线工具将数据库转换为struct)

created & updated

xorm.io/zh/docs/cha…

type TableName struct {
    CreatedAt     *carbon.DateTime `xorm:"created" `
    UpdatedAt     *carbon.DateTime `xorm:"updated" `
}

tag中添加createdupdatedXORM会自动填充创建时间和更新时间。
但我更喜欢在创建数据库时设置默认CURRENT_TIMESTAMP

CREATE TABLE `table_name` (
	`created_at` DATETIME NULL DEFAULT CURRENT_TIMESTAMP,
	`updated_at` DATETIME NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
)

然后tag中添加<-标记为只读

type TableName struct {
    CreatedAt     *carbon.DateTime `xorm:"<-" `
    UpdatedAt     *carbon.DateTime `xorm:"<-" `
}

id 要添加 autoinc 和 pk 两个标签

type TableName struct {
    Id        int64            `xorm:"autoincr pk" `
}

添加了autoincr调用Insert方法之后Id才会被填充,
添加了pk才能调用ID方法按主键查询。

type User struct {
    Id       int64 `xorm:"autoincr pk" `
    NickName string
}
func TestUser(t *testing.T) {
    user := &User{NickName: "xxx"}
    tmd.DB2().Insert(user)
    
    fmt.Printf("id: %v", user.Id) // 标签中带有 autoincr,Id才会被填充
    
    user2 := &User{}
    tmd.DB2().ID(1).Get(user2) // 标签中带有 pk,才能调用 .ID(1) 查询

    fmt.Printf("user2: %v", user2)
}

存为JSON的自定义类型 不需要实现 Scanner 和 Valuer 接口

实际开发中自定义类型经常是存为json
GORM每个自定义类型都需要实现ScannerValuer接口。
gorm.io/zh_CN/docs/…
XORM文档是说struct默认会存为json
xorm.io/zh/docs/cha…

image.png

但实际上要加json标签才会存为json
不然会抛出no primary key for col xxx错误。
不知道是不是我理解有误,
不管怎样总比一个个实现Scanner & Valuer来得方便。

type SpotPosInfo struct {
    Name      string
    Address   string
    Latitude  string
    Longitude string
}
type Spot struct {
    Id      int64        `xorm:"autoincr pk" `
    PosInfo *SpotPosInfo `xorm:"json" ` // 不加json会出现错误:no primary key for col pos_info
}

func TestUser(t *testing.T) {
    spot := &Spot{
       PosInfo: &SpotPosInfo{
          Name:      "aaa",
          Address:   "bbb",
          Latitude:  "ccc",
          Longitude: "ddd",
       },
    }
    rows, err := tmd.DB2().Insert(spot)
    dump.P(rows, err)
}

另外如果不想存为JSON,比如只想以逗号连接字符串,
Scanner & ValuerXORMToDBFromDB
就命名而言 个人感觉比ScanValue直观很多,
不过XORM也支持标准的Scanner & Valuer接口。

不用复制 struct

这一条其实和库无关,只是换成XORM后发现这样更适合自己,

比如用户表有三个字段id,username,password,有时候我们只需要id,username
GORM时候看到文档这么写的:
gorm.io/zh_CN/docs/…

image.png

XORM当然也可以这么用,
但是表结构一有改动,就得检查所有struct,而且会存在大量相似的struct
换成XORM后我这么写:

type User struct {
    ID     uint   `json:"id"`
    Name   string `json:"name"`
    Age    int    `json:"age,omitempty"`
    Gender string `json:"gender,omitempty"`
}

const UserApiFields = "id,name"

func TestUser(t *testing.T) {
    users := make([]User, 0)
    err := tmd.DB2().Limit(10).Select(UserApiFields).Find(&users)
    dump.P(err)
}

重点在json标签中加上,omitempty,然后把常用的字段定义成常量。
这样子序列化成json的时候就不会看到age,gender

未完待续

其它的暂时没想到,等想到了再来更新。