需要注意,该特性仅对链式操作有效。

gdb模块支持对数据记录的写入、更新、删除时间自动填充,提高开发维护效率。为了便于时间字段名称、类型的统一维护,如果使用该特性,我们约定:

  • 字段应当设置允许值为null
  • 字段的类型必须为时间类型,如:date,  datetime,  timestamp。不支持数字类型字段,如int
  • 字段的名称支持自定义设置,默认名称约定为:
    • created_at用于记录创建时更新,仅会写入一次。
    • updated_at用于记录修改时更新,每次记录变更时更新。
    • deleted_at用于记录的软删除特性,只有当记录删除时会写入一次。

字段名称其实不区分大小写,也会忽略特殊字符,例如CreatedAt,  UpdatedAt,  DeletedAt也是支持的。此外,时间字段名称可以通过配置文件进行自定义修改,并可使用TimeMaintainDisabled配置完整关闭该特性,具体请参考 ORM使用配置 章节。

对时间类型的固定其实是为了形成一种规范。

特性的启用

当数据表包含created_atupdated_atdeleted_at任意一个或多个字段时,该特性自动启用。

以下的示例中,我们默认示例中的数据表均包含了这3个字段。

created_at写入时间

在执行Insert/InsertIgnore/BatchInsert/BatchInsertIgnore方法时自动写入该时间,随后保持不变。

// INSERT INTO `user`(`name`,`created_at`,`updated_at`) VALUES('john', `2020-06-06 21:00:00`, `2020-06-06 21:00:00`)
g.Model("user").Data(g.Map{"name": "john"}).Insert()

// INSERT IGNORE INTO `user`(`uid`,`name`,`created_at`,`updated_at`) VALUES(10000,'john', `2020-06-06 21:00:00`, `2020-06-06 21:00:00`)
g.Model("user").Data(g.Map{"uid": 10000, "name": "john"}).InsertIgnore()

// REPLACE INTO `user`(`uid`,`name`,`created_at`,`updated_at`) VALUES(10000,'john', `2020-06-06 21:00:00`, `2020-06-06 21:00:00`)
g.Model("user").Data(g.Map{"uid": 10000, "name": "john"}).Replace()

// INSERT INTO `user`(`uid`,`name`,`created_at`,`updated_at`) VALUES(10001,'john', `2020-06-06 21:00:00`, `2020-06-06 21:00:00`) ON DUPLICATE KEY UPDATE `uid`=VALUES(`uid`),`name`=VALUES(`name`),`updated_at`=VALUES(`updated_at`)
g.Model("user").Data(g.Map{"uid": 10001, "name": "john"}).Save()

需要注意的是Replace方法也会更新该字段,因为该操作相当于删除已存在的旧数据并重新写一条数据。

updated_at更新时间

在执行Insert/InsertIgnore/BatchInsert/BatchInsertIgnore方法时自动写入该时间,在执行Save/Update时更新该时间(注意当写入数据存在时会更新updated_at时间,不会更新created_at时间)。

// UPDATE `user` SET `name`='john guo',`updated_at`='2020-06-06 21:00:00' WHERE name='john'
g.Model("user").Data(g.Map{"name" : "john guo"}).Where("name", "john").Update()

// UPDATE `user` SET `status`=1,`updated_at`='2020-06-06 21:00:00' ORDER BY `login_time` asc LIMIT 10
g.Model("user").Data("status", 1).Order("login_time asc").Limit(10).Update()

// INSERT INTO `user`(`id`,`name`,`update_at`) VALUES(1,'john guo','2020-12-29 20:16:14') ON DUPLICATE KEY UPDATE `id`=VALUES(`id`),`name`=VALUES(`name`),`update_at`=VALUES(`update_at`)
g.Model("user").Data(g.Map{"id": 1, "name": "john guo"}).Save()

需要注意的是Replace方法也会更新该字段,因为该操作相当于删除已存在的旧数据并重新写一条数据。

deleted_at数据软删除

软删除会稍微比较复杂一些,当软删除存在时,所有的查询语句都将会自动加上deleted_at的条件。

// UPDATE `user` SET `deleted_at`='2020-06-06 21:00:00' WHERE uid=10
g.Model("user").Where("uid", 10).Delete()

查询的时候会发生一些变化,例如:

// SELECT * FROM `user` WHERE uid>1 AND `deleted_at` IS NULL
g.Model("user").Where("uid>?", 1).All()

可以看到当数据表中存在deleted_at字段时,所有涉及到该表的查询操作都将自动加上deleted_at IS NULL的条件

联表查询的场景

如果关联查询的几个表都启用了软删除特性时,会发生以下这种情况,即条件语句中会增加所有相关表的软删除时间判断。

// SELECT * FROM `user` AS `u` LEFT JOIN `user_detail` AS `ud` ON (ud.uid=u.uid) WHERE u.uid=10 AND `u`.`deleted_at` IS NULL AND `ud`.`deleteat` IS NULL LIMIT 1
g.Model("user", "u").LeftJoin("user_detail", "ud", "ud.uid=u.uid").Where("u.uid", 10).One()

Unscoped忽略时间特性

Unscoped用于在链式操作中忽略自动时间更新特性,例如上面的示例,加上Unscoped方法后:

// SELECT * FROM `user` WHERE uid>1
g.Model("user").Unscoped().Where("uid>?", 1).All()

// SELECT * FROM `user` AS `u` LEFT JOIN `user_detail` AS `ud` ON (ud.uid=u.uid) WHERE u.uid=10 LIMIT 1
g.Model("user", "u").LeftJoin("user_detail", "ud", "ud.uid=u.uid").Where("u.uid", 10).Unscoped().One()



Content Menu

  • No labels

19 Comments

  1. kim

    针对结构体为参数传入的insertsave方法无效,因为结构体参数中CreatedAtUpdatedAt这两个属性都是空值,解析出的sql语句是把这两个字段设置为空值去了,OmitEmpty()方法也无效,建议这一块优化一下,因为实际开发中直接用结构体来作为参数的比较多,用Map的更多的在于查询的时候用。

    1. 把代码提个issue,我看看呢。

      1. 我也遇到了同样的问题,请问有优化了吗?

  2. orm有类似laravel的eloquent一样的全局作用域的功能吗,为每个模型都加上约束条件,比如全局查询加上站点id约束

    1. 你指的是HOOK功能吧,那个可以自定义接口来实现,具体查看章节:ORM接口开发-回调处理

      1. 应该不是这个吧,像Eloquent 的软删除就是用了全局作用域实现的,gf的软删除,看源码好像是直接判断加上的,大佬能不能加上这种特性,使用场景也是蛮多的laravel全局作用域

  3. 大神,数据库的时间,created_at,updated_at和deleted_at能不能支持 时间戳呀 int 类型的

  4. 有计划做支撑int类型吗?

  5. 强烈建议deleted_at支持int格式,用timestamp实现的软删除无法做unique键太伤了

    1. 提个issue记录下呢。

  6. 时间格式用datetime,时间戳只能保存到2038年。


    03 日期类型:TIMESTAMP 可能是巨坑

    1. 还要管到2038年?工作能保到2028年都了不起了

    2. MySQL的TIMESTAMP类型可以表示从1970-01-01 00:00:01 UTC到2038-01-19 03:14:07 UTC的日期和时间。然而,从MySQL 5.6.4版本开始,TIMESTAMP类型的存储范围已经被扩展到从1970-01-01 00:00:01 UTC到9999-12-31 23:59:59 UTC,所以它也可以处理2038年之后的日期和时间。

  7. 怎么能在保留软删除的情况下关联表查询不受deleted_at影响。加上Unscoped之后虽然不判断deleted_at但是直接就是真删除了

  8. // SELECT * FROM `user` WHERE uid>1 AND `deleted_at` IS NULL
    db.Model("user").Where("uid>?", 1).All()

    这个 `AND `deleted_at` IS NULL` 可以改成  AND `deleted_at` > '1970-01-01 00:00:00' 吗?

    因为我们不允许在数据列中出现 null 的值,影响索引效率。

  9. date类型返回的json格式也是Y-m-d H:i:s,怎么去掉后边的时间,很烦

    1. 把输出的类型改为string就行了

  10. 建议软删除增加时间戳/Integer格式 兼容秒值/毫秒值,至少可以定义默认软删除的value 现在是写死的delete_at is null 希望可以增加配置项兼容delete_at = 0
    https://github.com/go-gorm/soft_delete 一样

    • v2.7.0
      • add field type detection for soft time field like created_at/updated_at/deleted_at to support unix timestamp or bool deleting table field #3293
    • v2.4.0
      • 改进时间维护功能,写入/更新/删除时间支持完整的时间(粒度到纳秒)写入 #2512

    例子 v2.7.0+

    • gf_book.sql
    DROP TABLE IF EXISTS `gf_book`;
    
    CREATE TABLE `gf_book`
    (
      `id`           INT(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'ID',
      `name`         VARCHAR(60) NOT NULL COMMENT '书名',
      `author`       VARCHAR(50) NOT NULL COMMENT '作者',
      `price` DOUBLE NOT NULL COMMENT '价格',
      `publish_time` DATE     DEFAULT NULL COMMENT '出版时间',
      `create_at`    datetime DEFAULT NULL COMMENT 'Created Time',
      `update_at`    datetime DEFAULT NULL COMMENT 'Updated Time',
      `delete_at`    bit(1)   DEFAULT 0,
      PRIMARY KEY (`id`)
    ) ENGINE = INNODB
      DEFAULT CHARSET = utf8mb4;
    • model.go
    package foo
    
    import "github.com/gogf/gf/v2/os/gtime"
    
    type Model struct {
        Id          int         `json:"id,omitempty" orm:"id"`
        Name        string      `json:"name" orm:"name"`
        Author      string      `json:"author" orm:"author"`
        Price       float64     `json:"price,omitempty" orm:"price"`
        PublishTime *gtime.Time `json:"publish_time,omitempty" orm:"publish_time"`
    
        CreateAt *gtime.Time `json:"create_at,omitempty" orm:"create_at"`
        UpdateAt *gtime.Time `json:"update_at,omitempty" orm:"update_at"`
        Delete   int64       `json:"delete,omitempty" orm:"delete_at"`
    }