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 *stringuserId 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)
}

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐