假设我想在 CRUD-API 中读写更新两种对象:
type Player struct {
ID *uuid.UUID `gorm:"type:uuid;primary_key;" json:"ID"`
TeamID *uuid.UUID `gorm:"type:uuid" json:"UserID"`
Name string `json:"Content"`
gorm.Model
}
type Team struct {
ID *uuid.UUID `gorm:"type:uuid;primary_key;" json:"ID"`
Players []Player `gorm:"foreignKey:TeamID;references:ID" json:"Players"`
Name string `json:"Content"`
gorm.Model
}
gorm.Model
包含3个时间戳:CreatedAt
、UpdatedAt
和DeletedAt
。当我更改 Player
的名称时,UpdatedAt
时间戳也会更改。到目前为止,一切都很好。现在,我想做的是将相关 UpdatedAt
的 Team
时间戳设置为当前时间戳。 (为什么?看下面...)
GORM
(理论上)具有现成的强大功能:BeforeSave()
,BeforeUpdate()
,AfterSave()
,AfterUpdate()
... - 这些功能应该做我想要的,当我执行以下操作...
func (player *Player) BeforeUpdate(tx *gorm.DB) (err error) {
if player.TeamID != nil && *player.TeamID != uuid.Nil {
tx.Model(&Team{}).Where("id = ?", *player.TeamID).Update("updated_at", time.Now())
}
return
}
这应该是我想要的——但事实并非如此。也许有人可以帮忙。
BeforeUpdate()
对数据库没有任何影响。但是,该函数被调用 - 我检查过。另外,如果您认为原因是 UpdatedAt
是 gorm.Model
的一部分... - 事实并非如此。我尝试更改 Team
表中的其他字段。也没有用。
我觉得让这些功能发挥作用应该是最干净的方式,但如果你认为你有替代解决方案,那么我也对此非常开放。
最后一点 - 在这个玩具示例中,
Player
的更新应该更新Team
,而不是更多。但在我的实际示例中,当时发生的 Team
更新应该会触发相关的另一次更新......假设 League
记录。此信息可能很重要。
我想这样做的原因是为了跨设备同步数据。这个想法如下。如果我是该数据模型所属应用程序的用户,那么我可能上周同步了本地存储的数据。今天,我上线了,与此同时,一个
Player
更改了名字。当上述行为可能时,应用程序只需检查哪个 Team
已更新,而不是更新我拥有的所有团队的整个本地数据。
你的想法是正确的。你只是用错了方法。
您应该使用AfterUpdate。检查这个例子:
package main
import (
"fmt"
"log"
"github.com/google/uuid"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type Player struct {
ID *uuid.UUID `gorm:"type:uuid;primaryKey;" json:"ID"`
TeamID *uuid.UUID `gorm:"type:uuid" json:"TeamID"`
Name string `json:"Name"`
gorm.Model
}
type Team struct {
ID *uuid.UUID `gorm:"type:uuid;primaryKey;" json:"ID"`
Players []Player `gorm:"foreignKey:TeamID;references:ID" json:"Players"`
Name string `json:"Name"`
gorm.Model
}
func (player *Player) AfterUpdate(tx *gorm.DB) (err error) {
if player.TeamID != nil && *player.TeamID != uuid.Nil {
tx.Model(&Team{}).Where("id = ?", *player.TeamID).Update("updated_at", player.UpdatedAt)
}
return
}
func main() {
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
if err != nil {
log.Fatalf("failed to connect database: %v", err)
}
if err := db.AutoMigrate(&Team{}, &Player{}); err != nil {
log.Fatalf("failed to migrate database: %v", err)
}
teamID := uuid.New()
team := Team{
ID: &teamID,
Name: "Team A",
}
if err := db.Create(&team).Error; err != nil {
log.Fatalf("failed to create team: %v", err)
}
playerID := uuid.New()
player := Player{
ID: &playerID,
TeamID: &teamID,
Name: "Player 1",
}
if err := db.Create(&player).Error; err != nil {
log.Fatalf("failed to create player: %v", err)
}
fmt.Printf("Initial UpdatedAt - Team: %v, Player: %v\n", team.UpdatedAt, player.UpdatedAt)
if err := db.Model(&player).Update("Name", "Updated Player 1").Error; err != nil {
log.Fatalf("failed to update player: %v", err)
}
var updatedTeam Team
if err := db.First(&updatedTeam, "id = ?", teamID).Error; err != nil {
log.Fatalf("failed to retrieve team: %v", err)
}
var updatedPlayer Player
if err := db.First(&updatedPlayer, "id = ?", playerID).Error; err != nil {
log.Fatalf("failed to retrieve player: %v", err)
}
fmt.Printf("Updated UpdatedAt - Team: %v, Player: %v\n", updatedTeam.UpdatedAt, updatedPlayer.UpdatedAt)
}
跑步时:
go run main.go
Initial UpdatedAt - Team: 2025-01-21 09:56:07.517172905 -0300 -03, Player: 2025-01-21 09:56:07.52014034 -0300 -03
Updated UpdatedAt - Team: 2025-01-21 09:56:07.521738862 -0300 -0300, Player: 2025-01-21 09:56:07.521738862 -0300 -0300