簡化DB操作:Golang 通用倉庫模式

介紹

本代碼包提供一個用于數據庫操作的通用倉庫 (GenericRepository),利用 Golang 和 GORM (Go ORM) 實現。該倉庫設計用于簡化數據庫的 CRUD (創建、讀取、更新、刪除) 操作,支持批處理、沖突處理、分頁查詢等高級功能。

主要功能

  1. 創建記錄 (Create): 插入單個模型實例到數據庫。
  2. 創建記錄(沖突時更新) (CreateOnConflict): 插入單個模型實例到數據庫,如果存在沖突(例如主鍵沖突),則更新指定的字段。
  3. 批量創建記錄 (CreateBatch): 批量插入模型實例到數據庫,提高大量數據處理的效率。
  4. 批量創建記錄(沖突時更新) (CreateBatchOnConflict): 批量插入模型實例,如果存在沖突,則更新指定的字段。
  5. 檢索記錄 (Retrieve): 根據指定參數查詢數據庫,并將結果填充到提供的輸出變量中。
  6. 分頁檢索記錄 (RetrievePage): 根據指定參數進行分頁查詢,并將結果填充到提供的輸出變量中。
  7. 檢索單條記錄 (RetrieveOne): 根據指定參數查詢單條記錄。
  8. 更新記錄 (Update): 更新數據庫中的現有記錄。
  9. 按參數更新記錄 (UpdateByParams): 根據提供的參數更新符合條件的記錄。
  10. 刪除記錄 (Delete): 刪除數據庫中的指定記錄。
  11. 按參數刪除記錄 (DeleteByParams): 根據提供的參數刪除符合條件的記錄。
  12. 記錄計數 (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 中,不會影響到業務邏輯層。

后續步驟

后續可以進一步改進和擴展當前的實現:

  • 單元測試: 為 ReflectiveQueryProcessorGenericRepository 編寫單元測試,確保各種操作的正確性。
  • 錯誤處理: 強化錯誤處理機制,確保所有可能的數據庫錯誤都能被妥善處理,并反饋給用戶。
  • 性能優化: 分析和優化數據庫操作的性能,特別是對于復雜的查詢和大型數據集。
  • 安全性: 確保代碼對SQL注入和其他潛在的安全問題有足夠的防護。

通過這些實現和改進,我們可以確保應用程序的數據訪問層既強大又可靠,能夠支持復雜且多變的業務需求。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/76460.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/76460.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/76460.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

JavaWeb 課堂筆記 —— 08 請求響應

本系列為筆者學習JavaWeb的課堂筆記&#xff0c;視頻資源為B站黑馬程序員出品的《黑馬程序員JavaWeb開發教程&#xff0c;實現javaweb企業開發全流程&#xff08;涵蓋SpringMyBatisSpringMVCSpringBoot等&#xff09;》&#xff0c;章節分布參考視頻教程&#xff0c;為同樣學習…

雙引擎驅動:解密音視頻體驗的QoS技術底座與QoE感官革命

QoS 定義&#xff1a;QoS&#xff08;Quality of Service&#xff0c;服務質量&#xff09;衡量音視頻傳輸技術層面的性能表現&#xff0c;聚焦網絡傳輸和系統處理能力&#xff0c;通過客觀指標量化服務質量。核心指標 碼率/帶寬&#xff1a;數據傳輸速率上限&#xff0c;直接…

Stable Diffusion + Contronet,調參實現LPIPS最優(帶生成效果+指標對比)——項目學習記錄

目錄 前言 一、數據集&#xff1a;圖像文本&#xff0c;部分選取于DeepFashion 二、優化一&#xff0c;img2img 三、優化二&#xff0c;微調sd參數 四、優化三&#xff0c;dreamshaper優化 五、優化四&#xff0c;sdv1.5contronet 六、問題探索歷程 1. 從 SDXL 到輕量化模…

SQL 不走索引的常見情況

在 SQL 查詢中&#xff0c;即使表上有索引&#xff0c;某些情況下數據庫優化器也可能決定不使用索引。以下是常見的不走索引的情況&#xff1a; 1. 使用否定操作符 NOT IN ! 或 <> NOT EXISTS NOT LIKE 2. 對索引列使用函數或運算 -- 不走索引 SELECT * FROM user…

數據庫主從延遲全解析:原因、影響與解決之道

目錄 一、引言&#xff1a;理解數據庫主從架構 二、數據庫主從延遲的定義與測量 2.1 主從延遲的技術定義 2.2 如何測量主從延遲 2.3 主從延遲對系統的影響 三、主從延遲的常見原因分析 3.1 網絡延遲因素 3.1.1 網絡質量與帶寬限制 3.1.2 地理位置分布造成的延遲 3.2 …

分治-歸并系列一>翻轉對

目錄 題目&#xff1a;解析&#xff1a;策略一&#xff1a; 代碼&#xff1a;策略二&#xff1a; 代碼&#xff1a; 題目&#xff1a; 鏈接: link 這題和逆序對區別點就是&#xff0c;要找到前一個元素是后一個元素的2倍 先找到目標值再&#xff0c;繼續堆排序 解析&#xff1…

從0到1打造一套適合自己接單的腳手架05自動化創建表

上一篇我們是手動創建的表&#xff0c;感覺不方便&#xff0c;后續如果要做成產品在部署的時候一個個的創建表太麻煩了&#xff0c;我們讓ai來自動創建表&#xff0c;輸入如下提示詞 現在這種單獨去navicate執行也不方便&#xff0c;我希望是有一個目錄里存放的表結構的語句&a…

minio改成https+域名訪問

思路有兩個&#xff1a; 方式一&#xff1a;通過nginx反向代理&#xff0c;將https配置在nginx&#xff0c;內部的MinIO還是使用HTTP&#xff1b;方式二&#xff1a;MinIO服務端直接配置成HTTPS&#xff1b; 注意&#xff1a; 私鑰需要命名為&#xff1a;private.key 公鑰需要…

VS Code構建C/C++開發環境(Windows with MinGW and CMake)

文章目錄 目的編譯工具鏈基礎開發與調試基于CMake開發與調試關于settings.json總結 目的 在Windows上進行C/C開發目前最最常用的IDE就是微軟的 Visual Studio &#xff0c;只是對我來說早些年的VS實在是太卡了&#xff0c;留下了不好的印象。后來沒怎么用過&#xff0c;現在下…

一組可能的機器學習問題列表

線性回歸與多項式擬合的關系最小二乘法在機器學習中的應用梯度下降是如何實現的貝葉斯分類器的應用場景高斯分布與判定在哪里用到模型的評估有哪些參數誤差中的偏差和方差定義訓練集分組的快捷方式如何度量模型性能查準率查全率的定義roc,aux的含義正則化是什么意思k均值用來解…

linux下io操作詳細解析

在 Linux 系統下&#xff0c;IO&#xff08;輸入/輸出&#xff09;操作是程序與外部設備&#xff08;如文件、網絡等&#xff09;交互的重要方式。Linux 提供了豐富的系統調用和庫函數來支持各種 IO 操作。以下是對 Linux 下 IO 操作的詳細解析&#xff0c;包括文件 IO、網絡 I…

wsl2+ubuntu22.04安裝blender教程(詳細教程)

本章教程介紹,如何在Windows操作系統上通過wsl2+ubuntu安裝blender并運行教程。Blender 是一款免費、開源的 ??3D 創作套件??,廣泛應用于建模、動畫、渲染、視頻編輯、特效制作等領域。它由全球開發者社區共同維護,支持跨平臺(Windows、macOS、Linux),功能強大且完全…

目標檢測YOLO實戰應用案例100講- 基于卷積神經網絡的小目標檢測算法研究與應用

目錄 知識儲備 基于改進YOLOv5的小目標檢測算法 一、環境配置(Python 3.8+) 二、核心代碼實現 1. 改進模型定義(models/yolov5s_tiny.py ) 2. 小目標數據增強(datasets/tiny_aug.py ) 3. 訓練腳本(train.py ) 三、關鍵改進點說明 四、實驗配置建議 前言 傳統…

智能DNS解析:解決高防IP地區訪問異常的實戰指南

摘要&#xff1a;針對高防IP在部分地區無法訪問的問題&#xff0c;本文設計基于智能DNS的流量調度方案&#xff0c;提供GeoDNS配置與故障切換代碼示例。 一、問題背景 運營商誤攔截或線路波動可能導致高防IP在福建、江蘇等地訪問異常。傳統切換方案成本高&#xff0c;智能DNS可…

根據 PID 找到對應的 Docker 容器

引言 在日常運維與調試過程中&#xff0c;我們常常需要查找某個進程所屬的 Docker 容器。當系統出現問題或資源異常時&#xff0c;根據進程的 PID 找到其所屬容器可以幫助我們迅速定位問題。本文將介紹如何利用 Linux 的 cgroup 機制&#xff0c;以及 Docker 提供的工具來完成…

NO.88十六屆藍橋杯備戰|動態規劃-多重背包|擺花(C++)

多重背包 多重背包問題有兩種解法&#xff1a; 按照背包問題的常規分析?式&#xff0c;仿照完全背包&#xff0c;第三維枚舉使?的個數&#xff1b;利??進制可以表??定范圍內整數的性質&#xff0c;轉化成01 背包問題。 ?建議&#xff1a;并不是所有的多重背包問題都能…

【遠程工具】0 std::process::Command 介紹

std::process::Command 是 Rust 標準庫中用于創建和配置子進程的主要類型。它允許你啟動新的進程、設置其參數和環境變量、重定向輸入/輸出等。 基本用法 use std::process::Command;let output Command::new("echo").arg("Hello, world!").output().ex…

【圖書管理系統】深入解析基于 MyBatis 數據持久化操作:全棧開發圖書管理系統獲取圖書列表接口(后端:計算圖書頁數、查詢當前頁展示的書籍)

圖書列表 實現服務器代碼(計算圖書總數量查詢當前頁需要展示的書籍) 后端響應時&#xff0c;需要響應給前端的數據 records&#xff1a;第 pageNum 頁要展示的圖書有哪些&#xff08;存儲到List集合中&#xff09;total&#xff1a;計算一共有多少本書&#xff08;用于告訴前…

如何在idea中快速搭建一個Spring Boot項目?

文章目錄 前言1、創建項目名稱2、勾選需要的依賴3、在setting中檢查maven4、編寫數據源5、開啟熱啟動&#xff08;熱部署&#xff09;結語 前言 Spring Boot 憑借其便捷的開發特性&#xff0c;極大提升了開發效率&#xff0c;為 Java 開發工作帶來諸多便利。許多大伙伴希望快速…

制作前的關鍵籌備:考試考核系統之核心要點

明確系統使用目的? 制作考試考核系統前&#xff0c;企業需明確系統使用目的&#xff0c;這是開發基石&#xff0c;不同目的決定系統功能特性。用于員工培訓考核時&#xff0c;系統要與培訓內容結合&#xff0c;能生成相應考題&#xff0c;檢驗員工知識掌握程度&#xff0c;具備…