1 回答
TA贡献1786条经验 获得超13个赞
在评论中反复讨论之后,我将在此处发布这个最小的答案。这绝不是一个明确的“这就是你所做的”类型的答案,但我希望这至少可以为你提供足够的信息来帮助你开始。为了达到这一点,我根据您提供的代码片段做出了一些假设,并且我假设您希望通过某种命令(例如)为数据库播种your_bin seed
。这意味着做出了以下假设:
存在模式和相应的模型/类型(类似
AirportCodes
等)每种类型都有自己的源文件(名称来自
Seed()
方法,返回一个.json
文件名)因此,假定种子数据的格式类似于
[{"seed": "data"}, {"more": "data"}]
.可以附加种子文件,如果模式发生变化,种子文件中的数据也可以一起更改。这在 ATM 中不太重要,但仍然是一个应该注意的假设。
好的,让我们首先将所有 JSON 文件移动到可预测的位置。在一个相当大的真实世界应用程序中,您会使用类似XDG 基本路径的东西,但为了简洁起见,我们假设您在一个临时容器中运行它,并且/
所有相关资产都已复制到所述容器中。
将所有种子文件放在seed_data
目录下的基本路径中是有意义的。每个文件都包含特定表的种子数据,因此一个文件中的所有数据都整齐地映射到一个模型上。让我们暂时忽略关系数据。我们只是假设,现在,这些文件中的数据至少在内部是一致的,并且任何X-to-X
关系数据都必须具有正确的 ID 字段,允许 JOIN 等。
开始吧
所以我们有了我们的模型,以及 JSON 文件中的数据。现在我们可以只创建所述模型的一部分,确保在插入其他数据之前您想要/需要存在的数据表示为比另一个更高的条目(较低的索引)。有点像这样:
seederModelList = []globals.Seeder{
m.AirportCodes{}, // seeds before Term
m.Term{}, // seeds after AirportCodes
}
但是,或者从这个方法返回文件名Seed,为什么不传入连接并让模型像这样处理自己的数据:
func (_ AirportCodes) Seed(db *gorm.DB) error {
// we know what file this model uses
data, err := os.ReadFile("seed_data/airport_codes.json")
if err != nil {
return err
}
// we have the data, we can unmarshal it as AirportCode instances
codes := []*AirportCodes{}
if err := json.Unmarshal(data, &codes); err != nil {
return err
}
// now INSERT, UPDATE, or UPSERT:
db.Clauses(clause.OnConflict{
UpdateAll: true,
}).Create(&codes)
}
对其他模型执行相同的操作,例如Terms:
func (_ Terms) Seed(db *gorm.DB) error {
// we know what file this model uses
data, err := os.ReadFile("seed_data/terms.json")
if err != nil {
return err
}
// we have the data, we can unmarshal it as Terms instances
terms := []*Terms{}
if err := json.Unmarshal(data, &terms); err != nil {
return err
}
// now INSERT, UPDATE, or UPSERT:
return db.Clauses(clause.OnConflict{
UpdateAll: true,
}).Create(&terms)
}
当然,考虑到我们在模型中有数据库访问权限,这确实会导致一些混乱,如果你问我的话,它实际上应该只是一个 DTO。这在错误处理方面也有很多不足之处,但它的基本要点是:
func main() {
db, _ := gorm.Open(mysql.Open(dsn), &gorm.Config{}) // omitted error handling for brevity
seeds := []interface{
Seed(*gorm.DB) error
}{
model.AirportCodes{},
model.Terms{},
// etc...
}
for _, m := range seeds {
if err := m.Seed(db); err != nil {
panic(err)
}
}
db.Close()
}
好的,这应该让我们开始了,但让我们通过以下方式将这一切变成更好的东西:
将整个数据库交互移出 DTO/模型
将事情包装到事务中,这样我们就可以回滚错误
稍微更新初始切片以使事情更清晰
因此,如前所述,我假设您有存储库之类的东西来处理单独包中的数据库交互。我们不应该调用Seed
模型并将数据库连接传递给模型,而应该依赖我们的存储库:
db, _ := gorm.Open() // same as before
acs := repo.NewAirportCodes(db) // pass in connection
tms := repo.NewTerms(db) // again...
现在我们的模型仍然可以返回 JSON 文件名,或者我们可以将其作为constrepos 中的一个。在这一点上,这并不重要。最主要的是,我们可以在存储库中完成实际的数据插入。
如果你愿意,你可以将你的种子切片更改为这样的东西:
calls := []func() error{
acs.Seed, // assuming your repo has a Seed function that does what it's supposed to do
tms.Seed,
}
然后循环执行所有播种:
for _, c := range calls {
if err := c(); err != nil {
panic(err)
}
}
现在,这只剩下交易问题了。值得庆幸的是,gorm 使这变得非常简单:
db, _ := gorm.Open()
db.Transaction(func(tx *gorm.DB) error {
acs := repo.NewAirportCodes(tx) // create repo's, but use TX for connection
if err := acs.Seed(); err != nil {
return err // returning an error will automatically rollback the transaction
}
tms := repo.NewTerms(tx)
if err := tms.Seed(); err != nil {
return err
}
return nil // commit transaction
})
您可以在这里摆弄更多的东西,例如创建可以单独提交的相关数据批次,您可以添加更精确的错误处理和更多信息的日志记录,更好地处理冲突(区分 CREATE 和 UPDATE 等...)。不过,最重要的是,有一点值得牢记:
Gorm有一个迁移系统
我不得不承认,我已经有一段时间没有处理 gorm 了,但是 IIRC,如果模型发生变化,你可以让表自动迁移,并在启动时运行自定义 go 代码和/或 SQL 文件,这些都可以使用,相当容易地播种数据。可能值得研究一下它的可行性……
- 1 回答
- 0 关注
- 90 浏览
添加回答
举报