简化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.mzph.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;具备…