介紹
本代碼包提供一個用于數據庫操作的通用倉庫 (GenericRepository
),利用 Golang 和 GORM (Go ORM) 實現。該倉庫設計用于簡化數據庫的 CRUD (創建、讀取、更新、刪除) 操作,支持批處理、沖突處理、分頁查詢等高級功能。
主要功能
- 創建記錄 (
Create
): 插入單個模型實例到數據庫。 - 創建記錄(沖突時更新) (
CreateOnConflict
): 插入單個模型實例到數據庫,如果存在沖突(例如主鍵沖突),則更新指定的字段。 - 批量創建記錄 (
CreateBatch
): 批量插入模型實例到數據庫,提高大量數據處理的效率。 - 批量創建記錄(沖突時更新) (
CreateBatchOnConflict
): 批量插入模型實例,如果存在沖突,則更新指定的字段。 - 檢索記錄 (
Retrieve
): 根據指定參數查詢數據庫,并將結果填充到提供的輸出變量中。 - 分頁檢索記錄 (
RetrievePage
): 根據指定參數進行分頁查詢,并將結果填充到提供的輸出變量中。 - 檢索單條記錄 (
RetrieveOne
): 根據指定參數查詢單條記錄。 - 更新記錄 (
Update
): 更新數據庫中的現有記錄。 - 按參數更新記錄 (
UpdateByParams
): 根據提供的參數更新符合條件的記錄。 - 刪除記錄 (
Delete
): 刪除數據庫中的指定記錄。 - 按參數刪除記錄 (
DeleteByParams
): 根據提供的參數刪除符合條件的記錄。 - 記錄計數 (
Count
): 根據指定參數計算符合條件的記錄總數。
設計理念
- 靈活性:通過反射和接口調用,支持多種類型的模型操作。
- 性能:支持批處理操作,減少數據庫交互次數,優化性能。
- 易用性:提供高級功能如沖突處理和分頁查詢,簡化常見的數據庫操作。
使用示例
如何在應用程序中使用這個通用的DAO層:
package mainimport ("context""log""your_project/dao" // 確保此路徑與您的實際項目結構匹配"your_project/models" // 確保此路徑與您的實際項目結構匹配repository "your_project/common" // 確保此路徑與您的實際項目結構匹配"gorm.io/gorm"
)func main() {// 初始化數據庫連接db := dao.InitDB()sqlDB, err := db.DB()if err != nil {log.Fatal("Error getting underlying sql.DB:", err)}defer sqlDB.Close() // 確保在函數結束時關閉數據庫連接// 創建GenericRepository實例repo := repository.NewGenericRepository(db, &models.User{})// 創建一個新用戶newUser := models.User{Name: "John Doe", Email: "john@example.com"}err = repo.Create(context.Background(), &newUser)if err != nil {log.Println("Error creating user:", err)}// 檢索用戶var users []models.Userquery := models.User{Name: "John Doe"}err = repo.Retrieve(context.Background(), &query, &users)if err != nil {log.Println("Error retrieving users:", err)}// 更新用戶newUser.Email = "new.email@example.com"err = repo.Update(context.Background(), &newUser)if err != nil {log.Println("Error updating user:", err)}// 刪除用戶err = repo.Delete(context.Background(), &newUser)if err != nil {log.Println("Error deleting user:", err)}
}
代碼解析
1. 模型定義
首先,我們定義一個用戶模型(User
)作為示例:
package modelsimport "gorm.io/gorm"type User struct {gorm.ModelName string `db:"name"`Email string `db:"email"`
}
2. 數據庫初始化與遷移 (dao.go
)
這部分負責創建數據庫連接,并提供一個自動遷移所有模型的函數。
package daoimport ("log""gorm.io/driver/sqlite""gorm.io/gorm""gorm.io/gorm/logger"
)// InitDB 初始化數據庫連接
func InitDB() *gorm.DB {db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})if err != nil {log.Fatalf("Failed to connect database: %v", err)}// Set logger to log SQL statementsdb.Logger = logger.Default.LogMode(logger.Info)return db
}// AutoMigrate 用于自動遷移提供的模型
func AutoMigrate(db *gorm.DB, models ...any) {if err := db.AutoMigrate(models...); err != nil {log.Fatalf("Failed to auto-migrate models: %v", err)}
}
3. 反射查詢處理器 (common/processor.go
)
接下來,我們創建一個反射查詢處理器 ReflectiveQueryProcessor
,該處理器負責根據模型的反射信息構建CRUD操作:
package repositoryimport ("reflect""strings""gorm.io/gorm""gorm.io/gorm/clause"
)type ReflectiveQueryProcessor struct{}func (rqp *ReflectiveQueryProcessor) Count(db *gorm.DB, params any) (int64, error) {query := rqp.QueryBuilder(db, params)var count int64query = query.Model(params)if err := query.Count(&count).Error; err != nil {return 0, err}return count, nil
}func (rqp *ReflectiveQueryProcessor) Insert(db *gorm.DB, model any) *gorm.DB {return db.Create(model)
}func (rqp *ReflectiveQueryProcessor) InsertOnConflict(db *gorm.DB, model any,conflictKeys []string, updateColumns []string,
) *gorm.DB {return db.Clauses(clause.OnConflict{Columns: rqp.toColumns(conflictKeys), // 指定哪些字段沖突DoUpdates: clause.AssignmentColumns(updateColumns), // 指定發生沖突時更新哪些字段}).Create(model)
}func (rqp *ReflectiveQueryProcessor) InsertBatch(db *gorm.DB, models any) *gorm.DB {return db.Create(models)
}func (rqp *ReflectiveQueryProcessor) InsertBatchOnConflict(db *gorm.DB, models any,conflictKeys []string, updateColumns []string,
) *gorm.DB {return db.Clauses(clause.OnConflict{Columns: rqp.toColumns(conflictKeys), // 指定哪些字段沖突DoUpdates: clause.AssignmentColumns(updateColumns), // 指定發生沖突時更新哪些字段}).Create(models)
}// Helper function to convert field names to GORM clause.Columns
func (rqp *ReflectiveQueryProcessor) toColumns(fieldNames []string) []clause.Column {columns := make([]clause.Column, len(fieldNames))for i, fieldName := range fieldNames {columns[i] = clause.Column{Name: fieldName}}return columns
}func (rqp *ReflectiveQueryProcessor) Find(db *gorm.DB, params any) *gorm.DB {query := rqp.QueryBuilder(db, params)return query
}func (rqp *ReflectiveQueryProcessor) Update(db *gorm.DB, model any) *gorm.DB {return db.Save(model)
}func (rqp *ReflectiveQueryProcessor) UpdateByParams(db *gorm.DB, params any, model any) *gorm.DB {query := rqp.QueryBuilder(db, params)return query.Updates(model)
}func (rqp *ReflectiveQueryProcessor) Remove(db *gorm.DB, model any) *gorm.DB {return db.Delete(model)
}func (rqp *ReflectiveQueryProcessor) RemoveByParams(db *gorm.DB, params any, model any) *gorm.DB {query := rqp.QueryBuilder(db, params)return query.Delete(model)
}// QueryBuilder builds a query based on the provided parameters.
func (rqp *ReflectiveQueryProcessor) QueryBuilder(db *gorm.DB, params any) *gorm.DB {val := reflect.ValueOf(params)if val.Kind() == reflect.Ptr {val = val.Elem()}for i := 0; i < val.NumField(); i++ {field := val.Type().Field(i)valueField := val.Field(i)if !valueField.IsZero() {dbFieldName := rqp.parseGormTagForColumn(field.Tag.Get("gorm"))if dbFieldName == "" {dbFieldName = strings.ToLower(field.Name)}db = db.Where(dbFieldName+" = ?", valueField.Interface())}}return db
}func (rqp *ReflectiveQueryProcessor) parseGormTagForColumn(tag string) string {parts := strings.Split(tag, ";")for _, part := range parts {if strings.HasPrefix(part, "column:") {return strings.TrimPrefix(part, "column:")}}return ""
}
4. 通用數據訪問對象 (common/repository.go
)
我們定義 GenericRepository
類,它使用 ReflectiveQueryProcessor
來執行數據庫操作:
package repositoryimport ("context""log""reflect""github.com/pkg/errors""gorm.io/gorm""gorm.io/gorm/clause"
)const DefaultBatchSize = 1000type GenericRepository struct {DB *gorm.DBModel anyBatchSize intQueryProcessor *ReflectiveQueryProcessor
}func NewGenericRepository(db *gorm.DB, model any) *GenericRepository {return &GenericRepository{DB: db,Model: model,BatchSize: DefaultBatchSize,QueryProcessor: &ReflectiveQueryProcessor{},}
}func (gr *GenericRepository) Count(ctx context.Context, params any) (int64, error) {if count, err := gr.QueryProcessor.Count(gr.DB, params); err != nil {log.Printf("Error counting records: %v", err)return 0, err} else {return count, nil}
}func (gr *GenericRepository) Create(ctx context.Context, model any) error {if err := gr.QueryProcessor.Insert(gr.DB, model).Error; err != nil {log.Printf("Error creating record: %v", err)return err}return nil
}func (gr *GenericRepository) CreateOnConflict(ctx context.Context, model any,conflictKeys []string, updateColumns []string,
) error {if err := gr.QueryProcessor.InsertOnConflict(gr.DB, model, conflictKeys, updateColumns).Error; err != nil {log.Printf("Error creating record on conflict: %v", err)return err}return nil
}func (gr *GenericRepository) CreateBatch(ctx context.Context, models any) error {processBatch := func(tx *gorm.DB) error {return gr.BatchProcess(tx, models, tx.Create)}return gr.DB.Transaction(processBatch)
}func (gr *GenericRepository) CreateBatchOnConflict(ctx context.Context, models any, conflictKeys []string, updateColumns []string) error {processBatch := func(tx *gorm.DB) error {return gr.BatchProcess(tx, models, func(batch any) *gorm.DB {return tx.Clauses(clause.OnConflict{Columns: gr.QueryProcessor.toColumns(conflictKeys),DoUpdates: clause.AssignmentColumns(updateColumns),}).Create(batch)})}return gr.DB.Transaction(processBatch)
}func (gr *GenericRepository) BatchProcess(tx *gorm.DB, models any, dbFunc func(any) *gorm.DB) error {sliceValue := reflect.ValueOf(models)if sliceValue.Kind() != reflect.Slice {return errors.New("input data should be a slice type")}total := sliceValue.Len()batchSize := gr.BatchSizeif batchSize <= 0 {batchSize = DefaultBatchSize}for i := 0; i < total; i += batchSize {end := i + batchSizeif end > total {end = total}batch := sliceValue.Slice(i, end).Interface()if err := dbFunc(batch).Error; err != nil {return err}}return nil
}func (gr *GenericRepository) Retrieve(ctx context.Context, params any, out any) error {db := gr.QueryProcessor.Find(gr.DB, params).WithContext(ctx)if err := db.Find(out).Error; err != nil {log.Printf("Error retrieving records: %v", err)return err}return nil
}func (gr *GenericRepository) RetrievePage(ctx context.Context, params any, pageSize int, page int, out any) error {db := gr.QueryProcessor.Find(gr.DB, params).WithContext(ctx)if err := db.Offset((page - 1) * pageSize).Limit(pageSize).Find(out).Error; err != nil {log.Printf("Error retrieving paginated records: %v", err)return err}return nil
}func (gr *GenericRepository) RetrieveOne(ctx context.Context, params any, out any) error {db := gr.QueryProcessor.Find(gr.DB, params).WithContext(ctx)if err := db.First(out).Error; err != nil {log.Printf("Error retrieving single record: %v", err)return err}return nil
}func (gr *GenericRepository) Update(ctx context.Context, model any) error {if err := gr.QueryProcessor.Update(gr.DB, model).Error; err != nil {log.Printf("Error updating record: %v", err)return err}return nil
}func (gr *GenericRepository) UpdateByParams(ctx context.Context, params any, model any) error {if err := gr.QueryProcessor.UpdateByParams(gr.DB, params, model).Error; err != nil {log.Printf("Error updating records by params: %v", err)return err}return nil
}func (gr *GenericRepository) Delete(ctx context.Context, model any) error {if err := gr.QueryProcessor.Remove(gr.DB, model).Error; err != nil {log.Printf("Error deleting record: %v", err)return err}return nil
}func (gr *GenericRepository) DeleteByParams(ctx context.Context, params any) error {if err := gr.QueryProcessor.RemoveByParams(gr.DB, params, gr.Model).Error; err != nil {log.Printf("Error deleting records by params: %v", err)return err}return nil
}
總結
在上述實現中,我們通過創建一個通用的數據訪問層(DAO),提高了代碼的復用性和維護性。這種結構使得對各種模型進行數據庫操作變得更加直接和靈活,同時也簡化了代碼的管理。以下是對整個實現的總結和一些關鍵點的強調:
1. 模型定義的標準化
模型中的每個字段都使用了 db
標簽來指定其在數據庫表中對應的列名。這是一種標準化處理,使得反射機制能夠正確識別和映射字段。
2. 反射查詢處理器的靈活性
ReflectiveQueryProcessor
類通過反射動態處理模型,自動構建CRUD操作。這減少了為每個模型手動編寫CRUD操作的需要,同時也降低了代碼出錯的風險。
- 查詢: 利用模型的字段值(如果非零)來構建查詢條件。
- 插入: 直接利用GORM的
Create
方法插入模型。 - 更新: 使用GORM的
Save
方法更新模型。 - 刪除: 使用GORM的
Delete
方法刪除模型。
3. 通用數據訪問對象(GenericRepository)
GenericRepository
提供了一個統一的接口來處理所有模型的CRUD操作。這種設計模式(Repository模式)有助于隔離業務邏輯和數據訪問代碼,使得業務邏輯更加清晰,數據訪問更加靈活。
4. 應用程序的簡潔性
在主程序中,通過實例化 GenericRepository
并調用其方法來執行具體的數據庫操作。這使得主程序不必關心數據存儲的細節,而可以專注于業務邏輯。
5. 擴展性和維護性
此架構易于擴展和維護。添加新的模型或修改現有模型時,通常不需要修改數據訪問層的代碼。此外,如果需要替換數據庫訪問技術(例如從GORM遷移到其他ORM),則主要修改集中在 ReflectiveQueryProcessor
中,不會影響到業務邏輯層。
后續步驟
后續可以進一步改進和擴展當前的實現:
- 單元測試: 為
ReflectiveQueryProcessor
和GenericRepository
編寫單元測試,確保各種操作的正確性。 - 錯誤處理: 強化錯誤處理機制,確保所有可能的數據庫錯誤都能被妥善處理,并反饋給用戶。
- 性能優化: 分析和優化數據庫操作的性能,特別是對于復雜的查詢和大型數據集。
- 安全性: 確保代碼對SQL注入和其他潛在的安全問題有足夠的防護。
通過這些實現和改進,我們可以確保應用程序的數據訪問層既強大又可靠,能夠支持復雜且多變的業務需求。