概述
当我们使用golang来构建一个web应用或者其他使用到数据库的应用的时候,通常会选择使用gorm
库。主要原因还是因为gorm
库操作方便,简单易用。
在对数据库的操作中,通常需要对时间进行处理。而gorm
在model层的结构体定义中,也提供了time.Time
类型。但是在实际的使用中,如果我们不注意的话,可能会遇到一些奇怪的问题。
遇到的问题
1. 空时间类型写入数据库,无法匹配mysql中的datetime类型的时间格式
Error 1292 (22007): Incorrect datetime value: ‘0000-00-00’ for column ‘online_at’ at row 1
排查问题
Model层定义
在对于Tag表的定义中,可以看出我们分别定义了三个时间字段:created_at
,updated_at
,online_at
。通常情况下,在数据库中updated_at
字段会设置 on update: CURRENT_TIMESTAMP
。也就是说,当有数据写入或者更新的时候,数据库会自动更新updated_at
中的时间。所以,我们在写业务逻辑代码的时候,就不需要去更新updated_at
的值。
但是,created_at
和 online_at
两个时间字段,就需要我们在业务逻辑中新增或者修改了。
package model
import (
"time"
)
// Tag 表
type Tag struct {
Id uint `gorm:"column:id;type:int(11) unsigned;primary_key;AUTO_INCREMENT" json:"id"`
TagName string `gorm:"column:tag_name;type:varchar(20);comment:关键字;NOT NULL" json:"tag_name"`
CreatedAt time.Time `gorm:"column:created_at;type:datetime;comment:创建时间" json:"created_at"`
UpdatedAt time.Time `gorm:"column:updated_at;type:datetime;comment:更新时间" json:"updated_at"`
OnlineAt time.Time `gorm:"column:online_at;type:datetime;comment:上线时间" json:"online_at"`
}
// TableName -
func (m *Tag) TableName() string {
return "tag"
}
Controller中的使用
在controller
层的Create()
创建业务逻辑代码中,我们指定了CreatedAt
创建时间,并没有指定OnlineAt
。 而且在本身的业务逻辑中,也不应该去指定OnlineAt
。
func (c *TagController) Create(tagName string) {
// 开启事务
tx := c.DB.Begin()
tagModel := &model.Tag{
TagName: tagName,
CreatedAt: time.Now(),
}
if err := tx.Create(tagModel).Error; err != nil {
// 创建失败回滚
tx.Rollback()
fmt.Println(err)
}
// 提交事务
if err := tx.Commit().Error; err != nil {
fmt.Println(err)
}
}
报错
当我们执行上述代码的时候,发现gorm
报错
2024/03/27 11:35:00 /Users/Kunkkawu/go/src/test/gorm_time/controller/tag.go:32 Error 1292 (22007): Incorrect datetime value: '0000-00-00' for column 'online_at' at row 1
[1.955ms] [rows:0] INSERT INTO `tag` (`tag_name`,`created_at`,`updated_at`,`online_at`) VALUES ('gorm_time','2024-03-27 11:35:00.306','2024-03-27 11:35:00.307','0000-00-00 00:00:00')
Error 1292 (22007): Incorrect datetime value: '0000-00-00' for column 'online_at' at row 1
sql: transaction has already been committed or rolled back
从错误信息中可以看出,online_at
由于没有设置具体的值,而被零值'0000-00-00 00:00:00'
占位了。
解决办法
方法一:定义model的时候,添加字段标签default:null
在定义Tag model的时候,由于没有定义default:null
,因此gorm在处理SQL的时候,就会自动使用零值来代替。
// Tag 表
type Tag struct {
Id uint `gorm:"column:id;type:int(11) unsigned;primary_key;AUTO_INCREMENT" json:"id"`
TagName string `gorm:"column:tag_name;type:varchar(20);comment:关键字;NOT NULL" json:"tag_name"`
CreatedAt time.Time `gorm:"column:created_at;type:datetime;comment:创建时间" json:"created_at"`
UpdatedAt time.Time `gorm:"column:updated_at;type:datetime;comment:更新时间" json:"updated_at"`
OnlineAt time.Time `gorm:"column:online_at;type:datetime;default:null;comment:上线时间" json:"online_at"`
}
方法二:使用*time.Time来代替
在定义Tag model的时候,如果类型定义为 *time.Time, 在gorm处理SQL的时候,零值就会使用null来拼接。因此就不会报错。
// Tag 表
type Tag struct {
Id uint `gorm:"column:id;type:int(11) unsigned;primary_key;AUTO_INCREMENT" json:"id"`
TagName string `gorm:"column:tag_name;type:varchar(20);comment:关键字;NOT NULL" json:"tag_name"`
CreatedAt *time.Time `gorm:"column:created_at;type:datetime;comment:创建时间" json:"created_at"`
UpdatedAt *time.Time `gorm:"column:updated_at;type:datetime;comment:更新时间" json:"updated_at"`
OnlineAt *time.Time `gorm:"column:online_at;type:datetime;comment:上线时间" json:"online_at"`
}
总结
上述提供的两种方法,都可以解决由于时间类型的零值,带来的错误问题。个人更推荐使用gorm的标签来制定default值。这样在真正需要指定时间的时候,只需要time.Now()
即可,而不是t := time.Now()
然后将 &t
赋值。
附录
示例代码
代码结构
- main.go : 入口主函数
- controller
- tag.go : Tag控制器,提供给main.go调用
- model
- tag.go : Tag模型,定义表数据结构
model/tag.go
package model
import (
"time"
)
// Tag 表
type Tag struct {
Id uint `gorm:"column:id;type:int(11) unsigned;primary_key;AUTO_INCREMENT" json:"id"`
TagName string `gorm:"column:tag_name;type:varchar(20);comment:关键字;NOT NULL" json:"tag_name"`
CreatedAt *time.Time `gorm:"column:created_at;type:datetime;comment:创建时间" json:"created_at"`
UpdatedAt *time.Time `gorm:"column:updated_at;type:datetime;comment:更新时间" json:"updated_at"`
OnlineAt *time.Time `gorm:"column:online_at;type:datetime;comment:上线时间" json:"online_at"`
}
// TableName -
func (m *Tag) TableName() string {
return "tag"
}
controller/tag.go
package controller
import (
"fmt"
"test/utils/sqlmock/model"
"time"
"gorm.io/gorm"
)
type TagController struct {
DB *gorm.DB
}
func (c *TagController) Create(tagName string) {
// 开启事务
tx := c.DB.Begin()
t := time.Now()
tagModel := &model.Tag{
TagName: tagName,
CreatedAt: &t,
}
if err := tx.Create(tagModel).Error; err != nil {
// 创建失败回滚
tx.Rollback()
fmt.Println(err)
}
// 提交事务
if err := tx.Commit().Error; err != nil {
fmt.Println(err)
}
}
func (c *TagController) GetInfo(tagName string) {
tagModel := &model.Tag{}
if err := c.DB.Where("tag_name = ?", tagName).First(tagModel).Error; err != nil {
fmt.Println(err)
}
fmt.Println(tagModel.Id)
fmt.Println(tagModel.TagName)
fmt.Println(tagModel.CreatedAt.Format(time.DateTime))
fmt.Println(tagModel.UpdatedAt.Format(time.DateTime))
}
main.go
package main
import (
"test/gorm_time/controller"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
func main() {
db := initDB()
tagCtrl := controller.TagController{
DB: db,
}
tagCtrl.Create("gorm_time")
tagCtrl.GetInfo("gorm_time")
}
func initDB() *gorm.DB {
dsn := "root:@tcp(127.0.0.1:3306)/test?charset=utf8&parseTime=true&loc=Asia%2FShanghai"
db, err := gorm.Open(mysql.Open(dsn))
if err != nil {
panic(err)
}
return db
}
评论区