Skip to content

3.4 数据访问层(repository) ​

概述

仓储层做一件事:给 service 一个安全的数据库入口。铁律:禁止裸用 *gorm.DB——统一经 repository.NewXxxRepo().Ctx(ctx) 访问,软删过滤与租户隔离自动注入。

标准结构 ​

go
// internal/repository/example.go
package repository

import (
    "context"
    "gorm.io/gorm"
)

// ExampleRepo 案例演示仓储
type ExampleRepo struct {
    Base
}

// NewExampleRepo 构造案例演示仓储(表名 = gin_ + 模块名)
func NewExampleRepo() *ExampleRepo {
    return &ExampleRepo{Base: NewBase("gin_example")}
}

// Ctx 带 ctx 的查询入口
func (r *ExampleRepo) Ctx(ctx context.Context) *gorm.DB {
    return r.Base.Ctx(ctx)
}

就这么短——结构、构造、Ctx 三段是每个仓储的固定模板,自定义查询方法按需追加在同文件内。关联表(user_role / role_menu)同样建仓储。

Base 提供的能力 ​

Ctx(ctx) 返回的 *gorm.DB 已自动注入:

注入项条件
del_flag = 0所有表——已软删行对查询不可见
tenant_id IN (0, 自身租户)仅租户白名单表且当前身份非平台级(超管)时

Base 附带的便捷方法:

go
// 软删除(业务删除统一入口,UPDATE del_flag=1)
repo.SoftDelete(ctx, "id", id)
repo.SoftDeleteByID(ctx, id)

// 存在性判断(软删/租户已过滤)
ok, err := repo.ExistsByCond(ctx, "name", name)

GORM 查询写法要点 ​

场景写法
等值条件.Where("col = ?", v)
非等值.Where("id <> ?", id)
IN 条件.Where("col IN ?", slice)
LIKE.Where(col+" LIKE ?", "%"+pat+"%")
指定列.Select("a,b")
单行读 map.Take(&one)(one 为 map[string]any,判空用 gorm.ErrRecordNotFound)
多行读.Find(&rows)([]map[string]any 或 *[]*T)
计数.Count(&n)(n 为 int64)
map 插入.Create(&d)(d 为 map[string]any)
map 更新.Where(..).Updates(d)
分页.Offset((p-1)*size).Limit(size).Find(&list)
排序.Order("sort, id")
结构体扫描Find 直接扫结构体(无 gorm 标签时按字段名蛇形映射;非列字段加 gorm:"-")

Raw 入口(受限场景) ​

go
repo.Raw(ctx)   // 不注入软删/租户 Scope

Raw() 仅限导入、字典管理、登录校验等明确场景使用——已软删行会进入结果集、跨租户数据可见,条件完整性由使用者自行保证。常规业务查询一律走 Ctx。

事务 ​

go
// db.WithTrans:事务内所有 Ctx 查询自动复用事务连接
err := db.WithTrans(ctx, func(ctx context.Context) error {
    if err := repoA.Ctx(ctx).Create(&d1).Error; err != nil {
        return err   // 返回错误自动回滚
    }
    return repoB.Ctx(ctx).Create(&d2).Error
})

事务内务必使用回调入参的 ctx(而非外层 ctx),否则拿不到事务连接。

编写检查清单 ​

  • [ ] 表名 gin_ 前缀 + 模块名
  • [ ] 只有 Ctx / Raw 两个查询入口,无裸 *gorm.DB 字段外泄
  • [ ] 删除走 SoftDelete 系列,不写物理 DELETE
  • [ ] Raw 使用处有注释说明场景正当性

下一步 ​

→ 3.5 业务层开发(service)

小蚂蚁云团队 · 提供技术支持