DaoSingle门面单例结构,一个说明开发文档
·
DaoSingle是基于GoWebFrame3框架实现的数据访问门面单例,采用门面模式统一封装多个DAO组件,提供简洁的数据访问入口。其核心职责包括统一访问入口、单例管理、缓存支持和简化调用。架构上位于业务层与数据访问层之间,通过DaoQuery结构体聚合多个Facade组件。文档详细介绍了其生命周期管理、公共API、使用示例和扩展指南,并提供了最佳实践建议。DaoSingle通过统一门面设计简化调用、解耦业务、支持缓存扩展,适用于GoTrain项目的数据访问层开发。
https://gitee.com/gowebframe3/webframe.git
DaoSingle 开发文档
1. 概述
1.1 定义
DaoSingle 是基于 GoWebFrame3 框架实现的 数据访问门面单例,采用 门面模式(Facade Pattern) 将多个数据访问层(DAO)组件统一封装,提供简洁、统一的数据访问入口。
1.2 核心职责
| 职责 | 说明 |
|---|---|
| 统一入口 | 聚合多个 DAO Facade,提供单一访问点 |
| 单例管理 | 通过依赖注入容器管理生命周期 |
| 缓存支持 | 提供用户信息等缓存查询能力 |
| 简化调用 | 屏蔽底层复杂的 DAO 调用链 |
1.3 架构定位
┌─────────────────────────────────────────────────────────────┐
│ Controller / Service │
│ (业务层) │
└───────────────────────────┬─────────────────────────────────┘
│ 调用
▼
┌─────────────────────────────────────────────────────────────┐
│ DaoSingle │
│ (门面层 - 单例) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ DaoQuery │ │
│ │ CreditFacade | OpcFacade | PlanFacade | ... │ │
│ └─────────────────────────────────────────────────────┘ │
└───────────────────────────┬─────────────────────────────────┘
│ 委托调用
▼
┌─────────────────────────────────────────────────────────────┐
│ DAO Facades (数据访问层) │
│ CreditFacade | OpcFacade | PlanFacade | StudentFacade ... │
└─────────────────────────────────────────────────────────────┘
2. 核心结构
2.1 DaoSingle 结构体
go
type DaoSingle struct {
basedto.BaseEntitySingle // 继承基础单例实体
*daoquery.DaoQuery // 组合 DAO 查询组件
}
字段说明:
| 字段 | 类型 | 说明 |
|---|---|---|
BaseEntitySingle |
basedto.BaseEntitySingle |
提供单例基础能力 |
DaoQuery |
*daoquery.DaoQuery |
聚合多个 DAO Facade |
2.2 DaoQuery 结构体
go
type DaoQuery struct {
basedto.BaseEntity
*daofacade.CreditFacade // 积分系统
*daofacade.OpcFacade // 操作日志
*daofacade.PlanFacade // 训练计划
*daofacade.StudentFacade // 学生管理
*daofacade.UserDaoFacade // 用户管理
*daofacade.WordDaoFacade // 单词管理
*daofacade.KbaseFacade // 知识库
}
包含的 Facade 组件:
| Facade | 职责 |
|---|---|
CreditFacade |
积分查询、积分变更记录 |
OpcFacade |
操作日志记录与查询 |
PlanFacade |
训练计划 CRUD 操作 |
StudentFacade |
学生信息管理 |
UserDaoFacade |
用户信息查询、缓存 |
WordDaoFacade |
单词库查询、词表管理 |
KbaseFacade |
知识库管理 |
3. 生命周期管理
3.1 单例注册流程
plainText
┌─────────────────────────────────────────────────────┐
│ init() │
│ 调用 registerBeanDaoSingle() │
└───────────────────────────┬─────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ basedi.RegisterLoadBean() │
│ 注册单例名称和加载函数 LoadDaoSingle │
└───────────────────────────┬─────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ LoadDaoSingle() │
│ 1. NewDaoSingle() 创建实例 │
│ 2. InjectDaoSingle() 依赖注入 │
│ 3. 返回单例实例 │
└─────────────────────────────────────────────────────┘
3.2 初始化代码
go
// 单例名称常量
var singleNameDaoSingle = "*daosingle.DaoSingle-98055d62-ef8f-4464-a518-57ddd65c92d9"
// 注册单例
func registerBeanDaoSingle() {
err := basedi.RegisterLoadBean(singleNameDaoSingle, LoadDaoSingle)
if err != nil {
logrus.Error("register bean error!", err)
}
}
// 加载单例
func LoadDaoSingle() baseiface.ISingleton {
var inst = NewDaoSingle()
InjectDaoSingle(inst)
return inst
}
3.3 获取单例实例
go
func FindBeanDaoSingle() *DaoSingle {
if bean, ok := basedi.FindBeanOk(singleNameDaoSingle); ok {
return bean.(*DaoSingle)
}
logrus.Error("not find bean!")
return nil
}
4. 公共 API
4.1 Facade 获取方法
| 方法 | 返回类型 | 说明 |
|---|---|---|
FindBeanPlanFacade() |
*daofacade.PlanFacade |
获取训练计划 Facade |
go
func FindBeanPlanFacade() *daofacade.PlanFacade {
return FindBeanDaoSingle().PlanFacade
}
4.2 用户缓存查询方法
| 方法 | 参数 | 返回类型 | 说明 |
|---|---|---|---|
CacheFillName() |
nameField *string, userId int64 |
*pagemodel.IchubResult[*userentity.Users] |
根据用户ID填充用户名 |
CacheFindUser() |
userId int64 |
*pagemodel.IchubResult[*userentity.Users] |
从缓存查询用户信息 |
go
// 从缓存查询用户信息
func CacheFindUser(userId int64) *pagemodel.IchubResult[*userentity.Users] {
return FindBeanDaoSingle().UserDaoFacade.FindByIdAtCache(userId)
}
// 填充用户名称到指定字段
func CacheFillName(nameField *string, userId int64) *pagemodel.IchubResult[*userentity.Users] {
return FindBeanDaoSingle().UserDaoFacade.CacheFillName(nameField, userId)
}
5. 使用示例
5.1 获取计划 Facade
go
// 获取训练计划 Facade
planFacade := daosingle.FindBeanPlanFacade()
// 使用 Facade 执行查询
plans, err := planFacade.FindByStudentId(studentId)
if err != nil {
// 处理错误
}
5.2 查询用户缓存
go
// 从缓存查询用户
result := daosingle.CacheFindUser(userId)
if result.IsSuccess() {
user := result.Data
fmt.Printf("用户名: %s\n", user.Name)
}
// 填充用户名到字段
var userName string
result := daosingle.CacheFillName(&userName, userId)
if result.IsSuccess() {
fmt.Printf("用户名已填充: %s\n", userName)
}
5.3 直接使用 DaoSingle
go
// 获取 DaoSingle 实例
dao := daosingle.FindBeanDaoSingle()
if dao == nil {
log.Error("DaoSingle 初始化失败")
return
}
// 调用各个 Facade
// 1. 查询训练计划
plans, _ := dao.PlanFacade.FindAll()
// 2. 查询用户
user, _ := dao.UserDaoFacade.FindById(123)
// 3. 查询单词
words, _ := dao.WordDaoFacade.Search("apple")
// 4. 记录操作日志
dao.OpcFacade.Record("train", "start", "用户开始训练")
6. 扩展指南
6.1 添加新的 Facade
步骤 1:在 DaoQuery 中添加新字段
go
type DaoQuery struct {
basedto.BaseEntity
*daofacade.CreditFacade
*daofacade.OpcFacade
*daofacade.PlanFacade
// ... 现有字段 ...
*daofacade.NewFacade // 添加新 Facade
}
步骤 2:在 Init() 方法中初始化
go
func (self *DaoQuery) Init() {
// ... 现有初始化 ...
self.NewFacade = daofacade.FindBeanNewFacade()
}
步骤 3:在 ShutDown() 方法中添加清理
go
func (self *DaoQuery) ShutDown() {
// ... 现有清理 ...
self.NewFacade.Shutdown()
}
步骤 4(可选):在 daosingle 包中添加便捷方法
go
func FindBeanNewFacade() *daofacade.NewFacade {
return FindBeanDaoSingle().NewFacade
}
7. 最佳实践
7.1 调用方式选择
| 场景 | 推荐方式 | 说明 |
|---|---|---|
| 频繁使用单一 Facade | 使用便捷方法(如 FindBeanPlanFacade()) |
代码更简洁 |
| 需要多个 Facade | 获取 DaoSingle 实例后直接调用 |
减少多次获取开销 |
| 性能敏感场景 | 缓存 DaoSingle 引用 |
避免重复查找 |
7.2 错误处理
go
// 正确:检查返回结果
result := daosingle.CacheFindUser(userId)
if !result.IsSuccess() {
log.Warnf("查询用户失败: %v", result.Error)
return nil
}
// 正确:检查单例是否为空
dao := daosingle.FindBeanDaoSingle()
if dao == nil {
log.Error("DaoSingle 未初始化")
return
}
7.3 避免滥用
- 不要在 DAO 层调用:
DaoSingle是门面层,不应在 DAO 内部使用,避免循环依赖 - 不要存储到结构体字段:单例应通过
FindBeanDaoSingle()获取,避免持有过期引用 - 注意并发安全:虽然单例本身是线程安全的,但具体 Facade 的方法需要确认是否线程安全
8. 依赖关系
plainText
DaoSingle
├── basedto.BaseEntitySingle (基础单例能力)
└── DaoQuery
├── basedto.BaseEntity (基础实体能力)
├── CreditFacade (积分系统)
├── OpcFacade (操作日志)
├── PlanFacade (训练计划)
├── StudentFacade (学生管理)
├── UserDaoFacade (用户管理)
├── WordDaoFacade (单词管理)
└── KbaseFacade (知识库)
9. 总结
DaoSingle 作为数据访问层的统一门面,提供了以下价值:
| 价值 | 说明 |
|---|---|
| 简化调用 | 统一入口,无需关注底层 DAO 实现 |
| 解耦业务 | 业务层只依赖门面,不依赖具体 DAO |
| 单例管理 | 通过依赖注入容器统一管理生命周期 |
| 缓存支持 | 内置用户缓存查询能力 |
| 扩展性强 | 新增 Facade 只需修改 DaoQuery |
文档版本:v1.0
创建时间:2026-05-20
适用范围:GoTrain 项目数据访问层开发
import (
"gitea.super-study.com/ys-study/gotrain/beapi/facade/daoquery"
"gitee.com/gowebframe3/webframe.git/goconfig/base/basedto"
)
type DaoSingle struct {
basedto.BaseEntitySingle
*daoquery.DaoQuery
}
func NewDaoSingle() *DaoSingle {
return &DaoSingle{
DaoQuery: daoquery.FindBeanDaoQuery(),
}
}
package daosingle
import (
"gitee.com/gowebframe3/webframe.git/goconfig/base/baseiface"
"gitee.com/gowebframe3/webframe.git/goconfig/basedi"
"github.com/sirupsen/logrus"
)
/*
@Title 文件名称: dao_single_init.go
@Desp 描述: 依赖自动注入
@Company 公司: www.learn.com
@Author 作者: raymond@163.com 时间: 2026-05-17 01:23:23
@Update 作者: raymond@163.com 时间: 2026-05-17 01:23:23
*/
var singleNameDaoSingle = "*daosingle.DaoSingle-98055d62-ef8f-4464-a518-57ddd65c92d9"
// init register load
func init() {
registerBeanDaoSingle()
}
// register DaoSingle
func registerBeanDaoSingle() {
err := basedi.RegisterLoadBean(singleNameDaoSingle, LoadDaoSingle)
if err != nil{
logrus.Error("register bean error!", err)
}
}
// FindBeanDaoSingle
func FindBeanDaoSingle() *DaoSingle {
if bean, ok := basedi.FindBeanOk(singleNameDaoSingle); ok {
return bean.(*DaoSingle)
}
logrus.Error("not find bean!")
return nil
}
func LoadDaoSingle() baseiface.ISingleton {
var inst = NewDaoSingle()
InjectDaoSingle(inst)
return inst
}
func InjectDaoSingle(s *DaoSingle) {
// s.Init()
}
package daosingle
import "gitea.super-study.com/ys-study/gotrain/beapi/facade/daofacade"
func FindBeanPlanFacade() *daofacade.PlanFacade {
return FindBeanDaoSingle().PlanFacade
}
package daosingle
import (
"gitea.super-study.com/ys-study/gotrain/beapi/db/dbentity/userentity"
"gitee.com/gowebframe3/webframe.git/goweb/pagemodel"
)
func CacheFillName(nameField *string, userId int64) *pagemodel.IchubResult[*userentity.Users] {
return FindBeanDaoSingle().UserDaoFacade.CacheFillName(nameField, userId)
}
func CacheFindUser(userId int64) *pagemodel.IchubResult[*userentity.Users] {
return FindBeanDaoSingle().UserDaoFacade.FindByIdAtCache(userId)
}
package daoquery
import (
"gitea.super-study.com/ys-study/gotrain/beapi/facade/daofacade"
"gitee.com/gowebframe3/webframe.git/goconfig/base/basedto"
)
type DaoQuery struct {
basedto.BaseEntity
*daofacade.CreditFacade
*daofacade.OpcFacade
*daofacade.PlanFacade
*daofacade.StudentFacade
*daofacade.UserDaoFacade
*daofacade.WordDaoFacade
*daofacade.KbaseFacade
}
func NewDaoQuery() *DaoQuery {
return &DaoQuery{}
}
func (self *DaoQuery) Init() {
self.CreditFacade = daofacade.FindBeanCreditFacade()
self.OpcFacade = daofacade.FindBeanOpcFacade()
self.PlanFacade = daofacade.FindBeanPlanFacade()
self.StudentFacade = daofacade.FindBeanStudentFacade()
self.UserDaoFacade = daofacade.FindBeanUserDaoFacade()
self.WordDaoFacade = daofacade.FindBeanWordDaoFacade()
self.KbaseFacade = daofacade.FindBeanKbaseFacade()
}
func (self *DaoQuery) AutoInit() bool {
return true
}
func (self *DaoQuery) ShutDown() {
self.CreditFacade.Shutdown()
self.OpcFacade.Shutdown()
self.PlanFacade.Shutdown()
self.StudentFacade.Shutdown()
self.UserDaoFacade.Shutdown()
self.WordDaoFacade.Shutdown()
self.KbaseFacade.Shutdown()
}
package daofacade
import (
"gitea.super-study.com/ys-study/gotrain/beapi/db/dbdao/creditdao"
"gitea.super-study.com/ys-study/gotrain/beapi/gotool/dbframe/dbsequence"
"gitee.com/gowebframe3/webframe.git/goconfig/base/basedto"
)
// 多例使用区
type CreditFacade struct {
basedto.BaseEntity
CreditSchemeDao *creditdao.CreditSchemeDao
CreditTransactionsDao *creditdao.CreditTransactionsDao
}
func NewCreditFacade() *CreditFacade {
return &CreditFacade{}
}
func (self *CreditFacade) Init() {
self.CreditSchemeDao = creditdao.FindBeanCreditSchemeDao()
self.CreditTransactionsDao = creditdao.FindBeanCreditTransactionsDao()
}
func (self *CreditFacade) AutoInit() bool {
return true
}
func (self *CreditFacade) InitAllTransNoWhenEmptty() *basedto.IchubResult {
var dao = self.CreditTransactionsDao.NewDao()
dao.DbOrFieldsCurrent().DbEq("trans_no", "").DbIsNull("trans_no")
var ret = dao.QueryModelMax()
if ret.ExistRecord() {
for _, item := range ret.Data {
if item.TransNo == "" {
item.TransNo, _ = dbsequence.FindBeanDbFacade().FindSeqTransNo()
dao.UpdateMap2Result(item.Id, map[string]any{
"trans_no": item.TransNo,
})
}
}
}
return &ret.IchubResult
}
package daofacade
import (
"gitea.super-study.com/ys-study/gotrain/beapi/common"
"gitea.super-study.com/ys-study/gotrain/beapi/db/dbdao/userdao"
"gitea.super-study.com/ys-study/gotrain/beapi/db/dbentity/userentity"
"gitea.super-study.com/ys-study/gotrain/beapi/db/dbparam"
"gitee.com/gowebframe3/webframe.git/goconfig/base/basedto"
"gitee.com/gowebframe3/webframe.git/goweb/pagefacade"
"gitee.com/gowebframe3/webframe.git/goweb/pagemodel"
"github.com/gogf/gf/v2/util/gconv"
)
type UserDaoFacade struct {
basedto.BaseEntity
common.BaseService
UsersDao *userdao.UsersDao
PagePtrFacade *pagefacade.PagePtrFacade[*userentity.Users, *dbparam.StudentParam]
UserExtendDao *userdao.UserExtendDao
}
func NewUserDaoFacade() *UserDaoFacade {
return &UserDaoFacade{}
}
func (self *UserDaoFacade) Init() {
self.UsersDao = userdao.FindBeanUsersDao()
self.UserExtendDao = userdao.FindBeanUserExtendDao()
}
func (self *UserDaoFacade) AutoInit() bool {
return true
}
func (self *UserDaoFacade) UpdateStudent(param *dbparam.StudentParam) *basedto.IchubResult {
//var p,err=self.PagePtrFacade.From(param)
var ret = self.CheckPhone(param)
if ret.ExistRecord() {
return basedto.ResultFailMsg("手机号已存在")
}
return self.UsersDao.UpdateMap2Result(param.Id, param.Stru2MapFields("id"))
}
func (self *UserDaoFacade) CheckPhone(param *dbparam.StudentParam) *pagemodel.PageResult[*userentity.Users] {
var dao = self.UsersDao.NewDao()
dao.DbNe("id", param.Id)
dao.DbEq("phone", param.Phone)
var ret = dao.QueryModelOne()
return ret
}
func (self *UserDaoFacade) FindByIdAtCache(userIds any) *pagemodel.IchubResult[*userentity.Users] {
var userId = gconv.Int64(userIds)
if userId == 0 {
return pagemodel.ResultOk(&userentity.Users{}, false)
}
var rets, exist = self.GetCache(userentity.FindBeanUsers().ObjectKeyOfPkey(userId))
if exist {
return rets.(*pagemodel.IchubResult[*userentity.Users])
}
var dao = userdao.FindBeanUsersDao()
var ret = dao.FindByIdResult(userId)
if ret.ExistRecord() {
self.SetCache(ret.Data.ObjectKey(), ret)
}
return ret
}
func (self *UserDaoFacade) CacheFillName(nameField *string, userId int64) *pagemodel.IchubResult[*userentity.Users] {
if userId == 0 {
return pagemodel.ResultOk(&userentity.Users{}, false)
}
var ret = self.FindByIdAtCache(userId)
if ret.ExistRecord() {
*nameField = ret.Data.Name
}
return ret
}
DBQueryPool
package daopool
import (
"gitea.super-study.com/ys-study/gotrain/beapi/facade/daoquery"
"gitee.com/gowebframe3/webframe.git/goconfig/base/basedto"
"gitee.com/gowebframe3/webframe.git/gomini/gopool"
)
type DaoQueryPool struct {
basedto.BaseEntitySingle
*gopool.GeneralObjectPool[*daoquery.DaoQuery]
}
func NewDaoQueryPool() *DaoQueryPool {
return &DaoQueryPool{
GeneralObjectPool: gopool.NewGeneralObjectPool[*daoquery.DaoQuery](),
}
}
package daopool
import (
"gitee.com/gowebframe3/webframe.git/goconfig/base/baseiface"
"gitee.com/gowebframe3/webframe.git/goconfig/basedi"
"github.com/sirupsen/logrus"
)
/*
@Title 文件名称: dao_query_pool_init.go
@Desp 描述: 依赖自动注入
@Company 公司: www.learn.com
@Author 作者: raymond@163.com 时间: 2026-05-17 00:32:37
@Update 作者: raymond@163.com 时间: 2026-05-17 00:32:37
*/
var singleNameDaoQueryPool = "*daopool.DaoQueryPool-c397125c-7d8b-42a7-a44f-23773919d71c"
// init register load
func init() {
registerBeanDaoQueryPool()
}
// register DaoQueryPool
func registerBeanDaoQueryPool() {
err := basedi.RegisterLoadBean(singleNameDaoQueryPool, LoadDaoQueryPool)
if err != nil{
logrus.Error("register bean error!", err)
}
}
// FindBeanDaoQueryPool
func FindBeanDaoQueryPool() *DaoQueryPool {
if bean, ok := basedi.FindBeanOk(singleNameDaoQueryPool); ok {
return bean.(*DaoQueryPool)
}
logrus.Error("not find bean!")
return nil
}
func LoadDaoQueryPool() baseiface.ISingleton {
var inst = NewDaoQueryPool()
InjectDaoQueryPool(inst)
return inst
}
func InjectDaoQueryPool(s *DaoQueryPool) {
// s.Init()
}
func (suite *TestViewUiSuite) Test007_borrowDaoQuery() {
var pool = daopool.FindBeanDaoQueryPool()
var daoQuery, err = pool.BorrowObject(context.Background())
if err != nil {
golog.Error("borrow object error!", err)
return
}
daoQuery.WordsDao.DbEq("id", 101552)
var ret = daoQuery.WordsDao.QueryModel()
golog.Info(ret)
pool.ReturnObject(context.Background(), daoQuery)
daoQuery, err = pool.BorrowObject(context.Background())
daoQuery.WordsDao.DbEq("id", 7125)
ret = daoQuery.WordsDao.QueryModel()
golog.Info(ret, err)
}
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)