gorm day5

gorm day5

  • gorm更新

删除一条记录

删除一条记录时,删除对象需要指定主键,否则会出发批量Delete,例如

// Email 的 ID 是 `10`
db.Delete(&email)
// DELETE from emails where id = 10;

// 带额外条件的删除
db.Where("name = ?", "jinzhu").Delete(&email)
// DELETE from emails where id = 10 AND name = "jinzhu";

解读:
在 GORM 中,删除操作要求非常谨慎,以避免意外删除过多的记录。这段内容说明了如何在 GORM 中安全地执行删除操作,以及如何附加额外的条件来精确控制哪些记录被删除。

删除单条记录
要删除单条记录,你需要确保删除的对象(在示例中是 email)有一个有效的主键值。GORM 通过这个主键值定位并删除特定的记录。

// 假设 Email 的 ID 是 `10`
db.Delete(&email)

这行代码将删除 emails 表中 id 为 10 的记录。
SQL 语句:DELETE FROM emails WHERE id = 10;
在执行 Delete 方法时,GORM 使用 email 结构体中的主键(ID 字段)来确定需要删除的记录。

带额外条件的删除
你还可以在删除操作中附加额外的条件,以进一步限制哪些记录应被删除。这通过在 Delete 调用之前链式调用 Where 方法实现。

db.Where("name = ?", "jinzhu").Delete(&email)

这行代码尝试删除 emails 表中 id 为 10 且 name 字段值为 “jinzhu” 的记录。
SQL 语句:DELETE FROM emails WHERE id = 10 AND name = “jinzhu”;
通过 Where 方法添加的条件与对象的主键一起用于确定哪些记录将被删除。

注意事项
安全性: 直接使用 Delete 方法而不指定任何条件会导致 GORM 执行批量删除操作,这可能不是你想要的结果。为了安全起见,GORM 要求删除对象必须指定主键,或者通过 Where 等方法明确指定删除条件。
批量删除: 如果需要执行批量删除,应该使用 Where 来指定要删除记录的条件,避免误删除未预期的数据。
软删除: 如果你的模型支持软删除(即通过标记记录为删除状态而不是物理删除),那么使用 Delete 方法时将不会从数据库中永久删除记录,而是更新记录的删除标记字段(如 DeletedAt)。

根据主键删除

GORM允许通过主键(可以是复合主键)和内联条件来删除对象,它可以使用数字(如以下例子,也可以使用字符串)。

db.Delete(&User{}, 10)
// DELETE FROM users WHERE id = 10;

db.Delete(&User{}, "10")
// DELETE FROM users WHERE id = 10;

db.Delete(&users, []int{1,2,3})
// DELETE FROM users WHERE id IN (1,2,3);

解读:
这段内容展示了 GORM 如何通过不同方式删除数据库中的记录,包括通过主键删除单个或多个记录,以及如何使用不同类型的主键值(数字或字符串)。GORM 提供了灵活的方式来指定要删除的对象,从而简化了删除操作的编码过程。

删除单个对象
当你想要删除单个对象时,可以直接在 Delete 方法中指定对象和其主键值。 GORM 会根据提供的主键值定位并删除对应的记录。
通过数字指定主键

db.Delete(&User{}, 10)

这行代码将从 users 表中删除主键 id 值为 10 的记录。
SQL 语句:DELETE FROM users WHERE id = 10;
即使是数字类型的主键值,你也可以直接将其作为 Delete 方法的参数。

通过字符串指定主键

db.Delete(&User{}, "10")

这行代码的作用与上一个示例相同,也是删除 id 为 10 的记录。
尽管主键值以字符串形式提供,GORM 会正确处理并将其转换为适当的类型,前提是这个字符串能成功转换为主键字段的类型。

删除多个对象
如果你想一次性删除多个对象,可以通过传递主键值的切片来实现。

db.Delete(&User{}, []int{1,2,3})

这行代码将删除主键 id 在列表 (1,2,3) 中的所有 users 记录。
SQL 语句:DELETE FROM users WHERE id IN (1,2,3);
这种方法非常适合于批量删除操作可以有效减少数据库访问次数,提高操作效率。

总结:
GORM 提供了灵活的删除操作,允许通过直接指定主键值来删除单个或多个记录。
你可以使用数字或字符串来指定要删除记录的主键值,GORM 会自动处理主键值的类型转换。
通过传递主键值的切片,可以一次性删除多个记录,这对于批量删除操作非常有用。
在使用 Delete 方法时,确保正确指定主键值,以避免意外删除其他未预期的记录。

Delete Hook

对于删除操作,GORM支持BeforeDelete、AfterDelete Hook,在删除记录时会调用这些方法。

func (u *User) BeforeDelete(tx *gorm.DB) (err error) {
    if u.Role == "admin" {
        return errors.New("admin user not allowed to delete")
    }
    return
}

这其实就是钩子机制,但是钩子有很多种类型的,这里我进行解读:
段内容展示了 GORM 的钩子(Hook)机制,特别是在进行删除操作之前如何使用 BeforeDelete 钩子。钩子是 GORM 提供的一种回调函数,可以在执行数据库操作的特定生命周期(如创建、更新、查询、删除等)自动被调用允许开发者插入自定义逻辑。

BeforeDelete 钩子
BeforeDelete 钩子在 GORM 执行删除操作之前被调用。这提供了一个机会来执行预删除逻辑,比如验证是否允许删除记录、修改记录状态、记录日志、清理关联数据等。

示例解释:
在给定的示例中,定义了一个 BeforeDelete 方法作为 User 结构体的方法:

func (u *User) BeforeDelete(tx *gorm.DB) (err error) {
    if u.Role == "admin" {
        return errors.New("admin user not allowed to delete")
    }
    return
}

当尝试删除一个 User 实例之前,BeforeDelete 方法会被自动调用。
在这个方法内,进行了一个检查:如果尝试删除的用户角色是 “admin”,则方法返回一个错误,表示管理员用户不允许被删除。
通过返回错误,这个删除操作会被阻止,错误信息将被传递给调用者。
如果用户角色不是 “admin”,方法会正常返回,没有错误,删除操作将继续执行。

钩子的用途和好处
数据完整性和安全性:BeforeDelete 钩子可以用来确保只有符合特定条件的记录可以被删除,增强了应用的数据完整性和安全性。
自定义逻辑:允许在删除记录之前执行自定义逻辑,如条件验证、资源清理、日志记录等。
错误处理:如果在钩子中返回错误,可以阻止删除操作的执行,并允许错误处理逻辑被应用。

批量删除

如果指定的值不包括主属性,那么GORM会执行批量删除,它将删除所有匹配的记录。

db.Where("email LIKE ?", "%jinzhu%").Delete(&Email{})
// DELETE from emails where email LIKE "%jinzhu%";

db.Delete(&Email{}, "email LIKE ?", "%jinzhu%")
// DELETE from emails where email LIKE "%jinzhu%";

解读:
这段内容说明了 GORM 在处理删除操作时的行为,特别是当指定的删除条件不包含模型的主键属性时。 在这种情况下,GORM 会执行批量删除,意味着它将删除所有与指定条件匹配的记录。这是一种非常强大但也需要谨慎使用的功能,因为它可能会影响数据库中大量的数据。
示例解释
示例 1: 使用 Where 方法指定删除条件

db.Where("email LIKE ?", "%jinzhu%").Delete(&Email{})

这行代码使用 Where 方法来指定删除条件,查找所有 email 字段值包含 “jinzhu” 的 Email 记录。
然后,使用 Delete 方法删除这些记录。
生成的 SQL 语句是:DELETE FROM emails WHERE email LIKE “%jinzhu%”;。
这会删除 emails 表中所有 email 字段符合条件的记录。
Delete(&Email{})这里就是没指明主键属性

示例 2: 在 Delete 方法中直接指定条件

db.Delete(&Email{}, "email LIKE ?", "%jinzhu%")

这行代码直接在 Delete 方法中指定了删除条件,效果与第一个示例相同。
它也会删除所有 email 字段值包含 “jinzhu” 的记录。
生成的 SQL 语句与第一个示例相同。

这里我再做一个对比,更加方便理解:
带主键的删除
假设我们有一个 User 结构体,它对应数据库中的 users 表,表中有一个主键 ID。

type User struct {
    ID   uint
    Name string
}

如果你想删除 ID 为 1 的用户,可以这样做:

var user User
user.ID = 1
db.Delete(&user)

这将生成并执行一个 SQL 删除语句,只删除 ID 为 1 的那条记录:

DELETE FROM users WHERE id = 1;

这个操作是安全的,因为它明确指定了要删除记录的主键值。

不带主键但指定条件的删除
如果你想删除所有名字中包含 “jinzhu” 的用户,而不是通过主键指定,可以这样做:

db.Where("name LIKE ?", "%jinzhu%").Delete(&User{})

或者直接在 Delete 方法中指定条件:

db.Delete(&User{}, "name LIKE ?", "%jinzhu%")

这两种方式都将生成类似的 SQL 删除语句,删除所有 name 字段值包含 “jinzhu” 的记录:

DELETE FROM users WHERE name LIKE "%jinzhu%";

这种删除操作没有指定主键值,而是通过条件来匹配需要删除的记录。这可以导致多条记录被删除,因此使用时需要小心,以确保条件正确无误,避免误删数据。

总结
带主键的删除:明确安全,因为它指定了唯一的主键值来定位要删除的记录。
不带主键但指定条件的删除:更加灵活,可以删除符合特定条件的多条记录,但需要谨慎使用,以确保不会意外删除未预期的数据。

阻止全局删除

如果在没有任何条件的情况下执行批量删除,GORM不会执行该操作,并返回ErrMissingWhereClause错误。对此你必须加一些条件,或者使用原生SQL,或者启用AllowGlobalUpdate模式,例如:

db.Delete(&User{}).Error // gorm.ErrMissingWhereClause

db.Where("1 = 1").Delete(&User{})
// DELETE FROM `users` WHERE 1=1

db.Exec("DELETE FROM users")
// DELETE FROM users

db.Session(&gorm.Session{AllowGlobalUpdate: true}).Delete(&User{})
// DELETE FROM users

这段内容强调了 GORM 在处理批量删除操作时的安全机制。为了防止意外删除数据库中的所有记录,GORM 要求明确指定删除操作的条件。如果尝试执行一个没有任何条件的批量删除操作,GORM 不会执行该操作,而是返回一个 ErrMissingWhereClause 错误。这是一个设计上的选择,用以避免潜在的危险操作。

简单来说就是要带条件,不带条件你用Delete搞批量删除就删不掉,而且还报错。

不同的批量删除方法
示例 1: 无条件批量删除尝试

db.Delete(&User{}).Error // gorm.ErrMissingWhereClause

尝试直接删除 User 表中的所有记录,没有指定任何条件。
GORM 会阻止这次操作并返回 ErrMissingWhereClause 错误。

示例 2: 使用条件的批量删除
这个用法就比较重要,可以学学。

db.Where("1 = 1").Delete(&User{})

通过指定一个恒真的条件 1 = 1,这条语句实际上匹配了所有记录。
这样可以绕过 GORM 的安全检查,执行删除操作,删除 User 表中的所有记录。
尽管这种方法有效,但需要小心使用,以免不小心删除了重要数据。

示例 3: 使用原生 SQL 执行删除
这个方法我觉得是比较符合理解的方法。

db.Exec("DELETE FROM users")

直接使用原生 SQL 语句进行删除,这种方法不受 GORM 安全机制的限制。
这将删除 users 表中的所有记录。
使用原生 SQL 时,应确保 SQL 语句的正确性和安全性。

示例 4: 启用 AllowGlobalUpdate 模式

db.Session(&gorm.Session{AllowGlobalUpdate: true}).Delete(&User{})

通过创建一个新的会话,并在该会话中设置 AllowGlobalUpdate: true,可以临时禁用 GORM 的全局更新和删除保护。
在这个模式下,GORM 允许执行没有指定条件的批量删除。
这种方法同样需要谨慎使用,以避免不必要的数据丢失。

总结:

GORM 通过默认阻止无条件的批量删除操作,提供了一层数据保护,防止误操作造成的数据损失。如果确实需要执行批量删除,应当明确提供条件,或者在了解可能的风险后,**使用原生 SQL 或启用 AllowGlobalUpdate 模式。**这些操作应该在充分理解其影响,并采取适当的数据备份措施后进行。

返回删除行的数据

返回被删除的数据,仅适用于支持Returning的数据库,例如:

// 返回所有列
var users []User
DB.Clauses(clause.Returning{}).Where("role = ?", "admin").Delete(&users)
// DELETE FROM `users` WHERE role = "admin" RETURNING *
// users => []User{{ID: 1, Name: "jinzhu", Role: "admin", Salary: 100}, {ID: 2, Name: "jinzhu.2", Role: "admin", Salary: 1000}}

// 返回指定的列
DB.Clauses(clause.Returning{Columns: []clause.Column{{Name: "name"}, {Name: "salary"}}}).Where("role = ?", "admin").Delete(&users)
// DELETE FROM `users` WHERE role = "admin" RETURNING `name`, `salary`
// users => []User{{ID: 0, Name: "jinzhu", Role: "", Salary: 100}, {ID: 0, Name: "jinzhu.2", Role: "", Salary: 1000}}

我还是有一个疑问的:
什么是Returning?
RETURNING 是 SQL 语句的一个扩展,允许你在执行插入(INSERT)、更新(UPDATE)或删除(DELETE)操作后立即返回被影响的行的数据。这个特性主要由支持它的数据库系统提供.例如 PostgreSQL。它为数据库操作提供了额外的灵活性,允许在单个查询中执行数据操作并检索操作结果,从而减少了对数据库的额外查询请求,提高了效率。

Returning使用场景和优势
获取新插入的记录的ID或其他字段值: 在执行插入操作时,可以使用 RETURNING 来获取新记录的自增ID或其他默认值。
检索更新或删除的记录信息: 在更新或删除操作后,RETURNING 可以直接返回被修改或被删除的行的详细信息,这对于记录日志、验证业务规则或直接在应用逻辑中使用这些数据非常有用。
提高性能: 通过避免额外的查询来获取操作后的数据,RETURNING 特性可以减少数据库交互次数,提高应用性能。

示例
在支持 RETURNING 的数据库中,你可以这样使用它:

插入操作并返回ID

INSERT INTO users (name, email) VALUES ('John Doe', 'john@example.com') RETURNING id;

这个 INSERT 语句在创建新用户后立即返回新用户的 id。

更新操作并返回更新后的数据

UPDATE users SET email = 'newjohn@example.com' WHERE id = 1 RETURNING *;

这个 UPDATE 语句更新了用户的电子邮件地址,并返回了该用户更新后的所有信息。

删除操作并返回被删除的数据

DELETE FROM users WHERE id = 1 RETURNING *;

这个 DELETE 语句删除了指定的用户,并返回了被删除的用户的所有信息。

注意事项
并非所有数据库系统都支持 RETURNING 语句。例如,MySQL 和 SQLite 就不支持这个特性。
在使用 RETURNING 时,应该考虑返回数据的大小,特别是当返回大量数据时,可能会对性能产生影响。

总的来说,RETURNING 是一个非常有用的特性,它在支持的数据库系统中提供了额外的灵活性和效率,使得数据库操作更加简洁和强大。

现在可以看GORM中的应用了:
代码例子解读:
这段内容展示了如何在使用 GORM 进行删除操作时,利用支持 RETURNING 语句的数据库(如 PostgreSQL)的特性来返回被删除数据的信息。这个功能在执行删除操作的同时,能够获取到被删除记录的详细信息,这对于需要记录或处理被删除数据的场景非常有用。

示例 1: 返回所有列

var users []User
DB.Clauses(clause.Returning{}).Where("role = ?", "admin").Delete(&users)

通过 clause.Returning{},这个操作告诉数据库在执行删除操作时返回被删除记录的所有列。
在这个例子中,它删除了 role 为 “admin” 的所有 users 记录,并且返回了这些记录的所有信息(如 ID、Name、Role、Salary 等),这些返回的数据被存储在 users 切片中。
生成的 SQL 语句可能类似于:DELETE FROM users WHERE role = “admin” RETURNING *;。

示例 2: 返回指定的列

DB.Clauses(clause.Returning{Columns: []clause.Column{{Name: "name"}, {Name: "salary"}}}).Where("role = ?", "admin").Delete(&users)

这个操作通过指定 clause.Returning{Columns: …} 来返回被删除记录的特定列(在此例中为 name 和 salary)。
这样,即使记录被删除,你也能获取到每条记录的 name 和 salary 信息,而其他未指定的列(如 ID、Role)则不会返回或返回其零值。
生成的 SQL 语句可能类似于:DELETE FROM users WHERE role = “admin” RETURNING name, salary;。
注意,在返回的 users 切片中,只有指定返回的列会有值,其他未指定的列(如 Role)会是其类型的零值(如空字符串)。

这个功能仅适用于支持 RETURNING 语句的数据库,例如 PostgreSQL。如果你使用的数据库不支持这个特性(如 MySQL),则这种操作将不会起作用。
使用 RETURNING 功能可以在删除数据的同时,获取到这些数据的副本,这在需要审核或记录删除操作的场景中非常有用。
在实际应用中,合理利用 RETURNING 语句可以减少数据库操作的复杂性,实现更高效的数据处理流程。

软删除

如果你的模型包含了一个gorm.deleteat字段(gorm.Model已经包含了该字段),它将自动获得软删除的能力!
拥有软删除能力的模型调用Delete时,记录不会被数据库删除,但GORM会将DeleteAt置为当前时间,并且你不能再通过普通的查询找到该记录。

// user 的 ID 是 `111`
db.Delete(&user)
// UPDATE users SET deleted_at="2013-10-29 10:23" WHERE id = 111;

// 批量删除
db.Where("age = ?", 20).Delete(&User{})
// UPDATE users SET deleted_at="2013-10-29 10:23" WHERE age = 20;

// 在查询时会忽略被软删除的记录
db.Where("age = 20").Find(&user)
// SELECT * FROM users WHERE age = 20 AND deleted_at IS NULL;

如果你不想引入gorm.Model,你也可以这样启用软删除特性:

type User struct {
  ID      int
  Deleted gorm.DeletedAt
  Name    string
}

那就是自己搞一个gorm.DeletedAt类型的数据。

查找被软删除的记录

可以使用Unscoped配合查找操作找到被软删除的记录

db.Unscoped().Where("age = 20").Find(&users)
// SELECT * FROM users WHERE age = 20;

永久删除

使用Unscoped配合删除操作实现永久

db.Unscoped().Delete(&order)
// DELETE FROM orders WHERE id=10;

Delete Flag

什么是Delete Flag
“Delete Flag”(删除标志)是数据库设计中用于标记记录为“已删除”而不实际从数据库中物理删除该记录的技术。这种方法通常用于实现"软删除"(Soft Delete),即在应用逻辑层面上隐藏或忽略某些记录,而在物理层面上保留这些记录的数据。使用删除标志可以让你保留数据的完整性和历史记录,同时还能够从用户视图中隐藏不需要的数据。

如何实现
在数据库表中添加一个字段(通常是布尔类型或时间戳),用来表示记录是否被标记为“已删除”。应用程序在执行删除操作时,不是执行实际的 DELETE SQL 操作,而是更新这个字段来标记记录。

布尔类型的删除标志一个布尔字段,如 is_deleted,可以用来标记记录是否被软删除。未删除的记录为 false,软删除的记录为 true。

时间戳类型的删除标志一个时间戳字段,如 deleted_at,在记录未被删除时为 NULL,在软删除操作执行时,该字段被设置为当前时间戳。这不仅标记了记录被删除,还记录了删除发生的时间。

使用场景
数据恢复:软删除使得错误删除的记录可以被容易地恢复。
数据分析:保留历史数据,以供将来分析或审计使用。
用户隐私:对于需要考虑用户隐私和数据保留政策的应用,软删除提供了一种平衡数据保留和用户隐私的方法。

在GORM中的应用
GORM 支持软删除。如果一个模型包含 gorm.DeletedAt 字段,GORM 会自动把该模型的删除操作处理为软删除。当执行删除操作时,GORM 会设置 DeletedAt 字段的值为当前时间戳,而不是从数据库中物理删除该记录。

type User struct {
    gorm.Model
    Name string
}

// 执行软删除
db.Delete(&user)

// 查询时自动忽略被软删除的记录
db.Find(&users)

在需要检索包括已软删除记录在内的所有记录时,可以使用 Unscoped 方法:

总结:
Delete Flag 或软删除是数据持久化策略中的一种实用技术,它通过在数据库层面保留记录的同时,在应用逻辑层面上实现记录的删除。这种方法提供了数据恢复和历史追踪的能力,同时还能够满足特定的数据保留要求和隐私政策。

看一个具体的例子:
Unix将时间戳作为delete flag

import "gorm.io/plugin/soft_delete"

type User struct {
  ID        uint
  Name      string
  DeletedAt soft_delete.DeletedAt
}

// 查询
SELECT * FROM users WHERE deleted_at = 0;

// 删除
UPDATE users SET deleted_at = /* current unix second */ WHERE ID = 1;

解读:
结构体定义
User 结构体定义了一个用户模型,其中包含 ID、Name 和 DeletedAt 字段。
DeletedAt 字段使用了 soft_delete.DeletedAt 类型,这是 GORM 提供的一个特殊类型,用于支持软删除功能。当记录被软删除时,DeletedAt 字段会被设置为当前的 Unix 时间戳(代表删除的具体时间),而未被删除的记录则保持这个字段的值为零值(0)。

查询操作
对应的SQL语句:
SELECT * FROM users WHERE deleted_at = 0;
这里是没问题的,字段大写变全小,单词之间下划线分隔。
在执行查询操作时,通常只想获取那些未被软删除的记录。因此,查询条件中包含了 deleted_at = 0,这样就只会返回那些 DeletedAt 字段值为零的记录,即未被删除的记录。

删除操作

UPDATE users SET deleted_at = /* current unix second */ WHERE ID = 1;

当需要软删除一个记录时,不是执行一个 DELETE SQL 操作,而是更新 DeletedAt 字段,将其设置为当前的 Unix 时间戳。这个操作实质上是一个 UPDATE 操作,它标记了记录为已删除,而实际上数据仍然存储在数据库中。
这个例子中没有直接展示如何在 GORM 中执行软删除操作,但在实践中,你可以简单地使用 db.Delete(&user) 来软删除一个 User 实例,GORM 会自动处理 DeletedAt 字段的更新。

总结
通过使用 GORM 的软删除插件,你可以轻松地在你的应用中实现软删除功能,保留重要数据的同时,对用户隐藏被删除的记录。这种方法特别适用于需要保留数据完整性和支持数据恢复需求的应用程序。在软删除模型中,DeletedAt 字段成为区分记录是否被删除的关键, 而在应用层面的查询操作中需要考虑这一点以确保只处理有效(未被软删除的)数据。

INFO在配合unique字段使用软删除时,您需要使用这个基于unix时间戳的DeletedAt字段,创建一个复合索引,例如:

import "gorm.io/plugin/soft_delete"

type User struct {
ID        uint
Name      string                `gorm:"uniqueIndex:udx_name"`
DeletedAt soft_delete.DeletedAt `gorm:"uniqueIndex:udx_name"`
}

解读:
这段内容介绍了在使用 GORM 进行软删除时,如何处理含有唯一约束(unique)字段的情况。特别是当你希望在软删除功能与唯一约束共存时,需要考虑如何避免因为软删除而可能引起的唯一约束冲突。

看到这里你可能疑惑,什么是唯一约束字段,为什么软删除和唯一约束会起冲突。这里我进行解读:
唯一约束:
唯一约束是数据库管理系统中用于确保表中的一列或列组合中的数据值唯一的一种约束。换句话说,它防止了在指定的列中插入重复的值。如果尝试插入或更新数据到表中,而这些数据在唯一约束的列上与已存在的记录冲突,数据库将拒绝这次操作并返回一个错误。

例如,如果有一个 users 表,其中的 email 列被设置为唯一约束,那么这个表中不可能有两条记录具有相同的 email 值。

软删除与唯一约束的冲突
冲突产生的原因:
当记录被软删除后,这条记录实际上仍然存在于数据库中,只是被标记为已删除状态。
如果尝试插入一条新记录,其唯一约束字段的值与一条已被软删除的记录相同,数据库将视这次插入操作为违反唯一约束,因为从数据库的角度看,那条已被软删除的记录仍然存在。
这意味着,即使从应用逻辑的角度认为那条记录已经“不存在”了,数据库依然按照唯一约束规则处理所有的数据,包括被软删除的数据。

解决冲突的方法
为了解决软删除与唯一约束之间的冲突,一种常见的方法是使用复合唯一索引,包括 deleted_at 字段原本需要保持唯一性的字段。这样,只有在相同的唯一字段和相同的(或非空的)deleted_at 值的情况下,才会违反唯一约束。这意味着,对于未被删除(deleted_at 为 NULL 或零值)的记录,唯一字段必须唯一;而对于已被软删除的记录,可以再次使用相同的唯一字段值添加新记录,因为 deleted_at 字段的值将不同(一个为 NULL/零值,一个为非零值),从而不违反唯一约束。

有可能上面的看不太懂,这是因为有些概念需要去了解,这里进一步解读,相信看完你就懂了:
什么是复合唯一索引
复合唯一索引是数据库中的一个概念,指的是在两个或多个列上定义的唯一约束,保证这几列的数据组合在表中是唯一的。换句话说,表中的任意两行在这些列上都不会有完全相同的值组合。
复合唯一索引的解释:
假设有一个 users 表,其中包含 email 和 deleted_at 两个字段。email 字段用于存储用户的电子邮件地址,而 deleted_at 字段用于标记用户是否被软删除(如果用户未被删除,该字段为 NULL)。

如果只在 email 字段上设置唯一索引,那么表中就不能有两个具有相同 email 值的记录。这在实践中意味着,如果一个用户被软删除(deleted_at 被设置为非 NULL 值),你仍然无法添加一个新用户使用相同的 email,因为被软删除的用户记录仍然占用着这个唯一约束。

通过在 email 和 deleted_at 上创建复合唯一索引,可以允许在 email 相同的情况下添加记录,只要它们的 deleted_at 字段值不同。这样,即使是同一个 email 值,也可以区分未删除和已删除的记录。

看个例子:
具体示例
考虑以下 User 表结构:

ID: 用户ID
Email: 用户邮箱
DeletedAt: 标记用户是否被软删除的时间戳字段

不使用复合唯一索引

CREATE TABLE users (
  ID INT PRIMARY KEY,
  Email VARCHAR(255) UNIQUE NOT NULL,
  DeletedAt TIMESTAMP
);

在这种情况下,如果你尝试软删除一个用户(设置 DeletedAt 字段),然后再创建一个具有相同 Email 的新用户,这将会违反唯一约束。

使用复合唯一索引

CREATE TABLE users (
  ID INT PRIMARY KEY,
  Email VARCHAR(255) NOT NULL,
  DeletedAt TIMESTAMP,
  UNIQUE (Email, DeletedAt)
);

现在,你可以:

1.创建一个用户,Email 为 “user@example.com”,DeletedAt 为 NULL。
2.软删除这个用户,将 DeletedAt 设置为当前时间戳。
3.再次创建一个新用户,使用相同的 Email “user@example.com”,因为 DeletedAt 为 NULL(对于新记录),与步骤2中的时间戳不同,不会违反复合唯一约束。

解决冲突的过程
1.定义复合唯一索引:在设计数据库时,对需要软删除功能的表使用复合唯一索引,包括业务逻辑上需要唯一的字段和 DeletedAt 字段。
2.执行软删除:当需要删除记录时,不是物理删除,而是设置 DeletedAt 字段为当前时间戳。
3.重新使用唯一字段值:由于复合唯一索引的存在,即使是被软删除的记录,也可以添加具有相同业务唯一字段(如 Email)的新记录,因为 DeletedAt 字段的值不同(一个是时间戳,一个是 NULL)。

现在再来看在GORM中的应用
刚刚的例子再次进行解读:

import "gorm.io/plugin/soft_delete"

type User struct {
ID        uint
Name      string                `gorm:"uniqueIndex:udx_name"`
DeletedAt soft_delete.DeletedAt `gorm:"uniqueIndex:udx_name"`
}

这个 User 结构体定义了一个用户模型,其中包含 ID、Name 和 DeletedAt 字段。

Name 字段被定义为唯一索引的一部分,使用 gorm:“uniqueIndex:udx_name” 标签。

DeletedAt 字段也被包含在相同的唯一索引 udx_name 中,这意味着 Name 和 DeletedAt 的组合必须是唯一的。

通过这种方式,你可以再次插入一个具有相同 Name 值但之前记录已被软删除的新记录,因为新记录的 DeletedAt 将是零(假设你在插入时没有显式设置 DeletedAt),而之前软删除的记录的 DeletedAt 是一个非零的时间戳。

总结:
在使用 GORM 进行软删除并且存在唯一字段约束时,通过创建包含 DeletedAt 字段的复合唯一索引,可以有效地解决软删除与唯一约束潜在的冲突问题。这种方法确保了即使记录被软删除,你也可以插入一个具有相同唯一字段值的新记录,同时保持数据库约束的完整性。
在gorm中使用复合唯一索引的方法就是之间在后面标记是唯一索引这就完成了自动复合。gorm会将这些字段组合成一个复合索引。

使用1 / 0 作为delete flag

import "gorm.io/plugin/soft_delete"

type User struct {
  ID    uint
  Name  string
  IsDel soft_delete.DeletedAt `gorm:"softDelete:flag"`
}

// 查询
SELECT * FROM users WHERE is_del = 0;

// 删除
UPDATE users SET is_del = 1 WHERE ID = 1;

解读:
这个例子演示了在 GORM 中如何使用 1 和 0 作为删除标志(delete flag)来实现软删除(soft delete)的一个简单示例。在这种方式中,1 代表记录已被删除(软删除),而 0 代表记录未被删除。这是通过在模型中定义一个特定的字段(在这个例子中是 IsDel)并使用布尔逻辑来实现的。

模型定义
ID 和 Name 字段定义了用户的基础信息
IsDel 字段是用来表示软删除状态的通过使用 soft_delete.DeletedAt 类型结合 gorm:“softDelete:flag” 标签,来特别指示这个字段应该被用作软删除的标志。这里的设计初衷是利用 GORM 的软删除插件功能,但实际上 使用 soft_delete.DeletedAt 类型并指定 softDelete:flag 标签与传统的布尔逻辑(使用 1 和 0)有所不同。 通常,布尔逻辑的软删除会直接使用 bool 类型或者整型字段。

查询未删除的记录
SELECT * FROM users WHERE is_del = 0;
这条 SQL 语句用于查询未被软删除的用户记录,即 IsDel 字段值为 0 的记录。

执行软删除
UPDATE users SET is_del = 1 WHERE ID = 1;
这条 SQL 语句将用户 ID 为 1 的记录标记为已删除,即将 IsDel 字段的值设置为 1。

注意事项:
1.实际上,如果你打算使用 1 / 0 作为软删除标志,字段类型更可能是 bool 或 int,而不是 soft_delete.DeletedAt。soft_delete.DeletedAt 类型通常用于记录具体的删除时间戳,而不是作为布尔标志使用。
2.在模型定义中使用 gorm:“softDelete:flag” 标签实际上并不符合 GORM 的常规用法,因为 GORM 的软删除机制默认是通过时间戳来标记删除的,而不是通过布尔标志。正确的实现软删除标志应该直接使用布尔类型或整型字段,并在应用逻辑中处理软删除的条件。

  • 18
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值