这篇文章将以创建事件时间记录功能为示例,介绍如何使用 Gorm 完成数据库表的基本映射、简单的创建表数据操作和注意事项。
数据库表设计
设计一个事件时间记录功能通常需要记录事件的开始时间、结束时间和事件类型,以及一些额外的功能,像事件描述。下面提供一个简单的数据库表设计方案。
我们提供两张数据库表:记录类别表 Record_Type 和时间记录表 Time_Record。Record_Type 用于记录事件类型,时间记录表是主要的记录表,记录事件发生的起始时间和结束时间。基本结构如下:
Record_Type:
- ID:唯一标识符
- Type:时间类型
Time_Record:
- ID:唯一标识符
- start_time:事件起始时间
- end_time:事件结束事件
- Description:事件描述
- type_id:事件类型ID
其中 Record_Type 和 Time_Record 的主键都是自增的唯一标识符,type_id 和 Record_Type 的 ID 建立外键约束,建表语句如下:
Record_Type:
CREATE TABLE Record_Types (
ID INT AUTO_INCREMENT PRIMARY KEY,
Type VARCHAR(255) NOT NULL
);
Time_Record:
CREATE TABLE Time_Records (
ID INT AUTO_INCREMENT,
start_time DATETIME NOT NULL,
end_time DATETIME,
Description TEXT,
type_id INT,
PRIMARY KEY (ID),
FOREIGN KEY (type_id) REFERENCES Record_Types(ID)
);
需要注意的是这里的 start_time
非空而 end_time
可以为空,这会影响到我们 Go 语言中的结构体对应的字段类型。
使用 Gorm 映射数据库表
使用 Gorm 之后,我们可以使用 Go 语言中的结构体来映射数据库表,不同类型则对应不同字段。常见的映射有:INT
对应 int
,VARCHAR
和 TEXT
对应 string
,DATETIME
等时间类型则使用 time.Time
来映射。上述映射都是针对数据库中的非空字段,而数据库中允许为空的字段,则替换成对应的指针类型。下面是实现上述示例映射的具体代码:
RecordType:
// RecordType 记录类别
type RecordType struct {
// 主键
ID int `gorm:"primaryKey"`
Type string
}
TimeRecord:
// TimeRecord 时间记录
type TimeRecord struct {
// 主键
ID int `gorm:"primaryKey"`
StartTime time.Time
EndTime *time.Time
Description string
// 外键约束
TypeId int `gorm:"check: ID <> 'RecordType''"`
}
可以注意到上述 EndTime
字段的数据类型为 *time.Time
,这正是因为 end_time
在数据库表中没有声明为非空,所以我们使用time.Time
的指针类型来表示。另外,在 Gorm 中主键和外键的声明则是通过标签来完成的。TimeRecord 表中 ID 字段类型声明后跟的 gorm:"primaryKey"
就是主键标签。如果你需要建立联合主键,则可以通过对多个字段同时加上主键标签,并且注意字段顺序的编排,因为 Gorm 会根据字段顺序来确定联合主键的顺序。外键则是使用 gorm:"check: ID <> 'RecordType
标签来声明的其中的 RecordType 指定表,ID 指定表中的字段。
名称映射
我们在给结构体和字段体起名时需要额外注意 Gorm 的名称映射规则:
-
结构体的字段名称会默认映射为数据库表的字段名。但是结构体名需要是单数形式,例如
RecordType
对应表record_types
,TimeRecord
对应表time_records
。 -
RecordType
并不会映射为recordtypes
而是带下划线的record_types
。这条规则同时适用于字段名和结构体名称。
通过 Gorm 插入表数据
结构体设计完成之后,我们可以创建一个专门用于插入数据行的函数,方便后续使用。而插入数据的方式也很简单,主要分两步:创建结构体,然后调用 *gorm.DB
的 Create
即可。示例如下:
// CreateType 创建新记录类别
func CreateType(db *gorm.DB, typeName string) (*RecordType, *gorm.DB) {
rtype := &RecordType{
Type: typeName,
}
result := db.Create(rtype)
return rtype, result
}
// StartRecord 开启新记录
func StartRecord(db *gorm.DB, description string, recordTypeId int) (*TimeRecord, *gorm.DB) {
record := &TimeRecord{
StartTime: time.Now(),
Description: description,
TypeId: recordTypeId,
}
result := db.Create(record)
return record, result
}
可以注意到,对于自增的 ID 主键,我们在创建结构体时对其省略。但是需要注意的是对于非空且没有默认值的对象,我们进行省略是存在风险的,如 time.Time 类型的非空对象省略后,Gorm 会将结构体初始化时的零值赋给其转换的 SQL 语句从而导致错误。
到此,简单的 Gorm 表映射便完成了。