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 關注
- 115 瀏覽
添加回答
舉報