一、先明确核心定义(一句话概括)
entity(实体):数据结构的纯数据载体,仅定义数据字段(对应数据库表字段),无任何业务逻辑 / 数据库操作,是三者中最 “纯净” 的层。do(Data Object,数据操作对象):面向数据库操作的封装,专注于数据库的 CRUD 条件构造、字段赋值,是衔接dao和数据库的操作载体。dao(Data Access Object,数据访问对象):数据访问层,提供直接可调用的数据库操作方法,封装了与数据库的交互逻辑,对外屏蔽数据库操作细节。
二、核心区别(清晰对比)
| 维度 | entity |
do |
dao |
|---|---|---|---|
| 核心定位 | 纯数据载体(数据模型) | 数据库操作载体(条件 / 赋值封装) | 数据访问层(提供操作方法) |
| 包含内容 | 仅字段定义(对应数据库表)、结构体标签 | 字段赋值、查询条件、更新条件、关联操作封装 | 具体的 CRUD 方法(查询、新增、更新、删除) |
| 有无业务 / 数据库逻辑 | 无(纯结构体,无任何方法) | 仅数据库操作相关逻辑(条件构造) | 封装数据库交互逻辑(与数据库直接通信) |
| 使用者 | 上层业务逻辑(接收 / 返回数据) | dao层(构造操作条件)、上层业务(辅助赋值) |
上层业务逻辑(直接调用完成数据库操作) |
| 灵活性 | 低(结构固定,仅承载数据) | 高(支持动态构造查询 / 更新条件) | 中(方法封装,直接调用即可) |
三、三者的联系
- 依赖关系:
dao依赖do完成数据库操作的条件构造,entity可作为dao操作的输入 / 输出数据格式(do也可与entity相互转换)。 - 数据流转:上层业务 → (可选)构造
entity承载数据 → 转换为do构造操作条件 → 调用dao方法执行数据库操作 →dao返回结果 → (可选)转换为entity供上层业务使用。 - 整体目标:三者协同实现 “数据与操作分离”,降低耦合,提高代码的可维护性和可复用性。
四、实操示例(基于 GoFrame v2)
前提准备
假设我们有一个数据库表 user,结构如下:
CREATE TABLE `user` ( `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '用户ID', `username` VARCHAR(50) NOT NULL COMMENT '用户名', `age` TINYINT(3) UNSIGNED DEFAULT 0 COMMENT '年龄', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
1. entity 定义(纯数据载体)
通常放在 entity/user_entity.go 文件中,仅定义结构体,无任何方法,字段与数据库表一一对应。
package entity
import (
"time"
)
// User 对应数据库user表的纯数据实体
// 仅承载数据,无任何业务/数据库操作逻辑
type User struct {
Id int64 `json:"id" orm:"column:id" description:"用户ID"` // 用户ID
Username string `json:"username" orm:"column:username" description:"用户名"` // 用户名
Age uint8 `json:"age" orm:"column:age" description:"年龄"` // 年龄
CreateTime time.Time `json:"createTime" orm:"column:create_time" description:"创建时间"`// 创建时间
}
// 可选:定义列表类型,方便承载批量数据
type UserList []*User
2. do 定义(数据库操作载体)
通常放在 do/user_do.go 文件中,基于 GoFrame 的gdb封装,专注于条件构造,可通过gf gen工具自动生成,也可手动定义。
package do
import (
"time"
"github.com/gogf/gf/v2/database/gdb"
)
// UserDo 用户表的数据操作对象
// 用于构造数据库查询、更新、新增等操作的条件和字段赋值
type UserDo struct {
gdb.Model `gorm:"table:user,alias:u"` // 绑定数据库表和别名
Id int64 `json:"id" gorm:"column:id"` // 与entity字段一致
Username string `json:"username" gorm:"column:username"` // 与entity字段一致
Age uint8 `json:"age" gorm:"column:age"` // 与entity字段一致
CreateTime time.Time `json:"createTime" gorm:"column:create_time"`// 与entity字段一致
}
// --------------- 以下是常用的条件构造方法(示例)---------------
// WhereId 构造ID等于条件
func (d *UserDo) WhereId(id int64) *UserDo {
d.Where("id = ?", id)
return d
}
// WhereUsernameLike 构造用户名模糊查询条件
func (d *UserDo) WhereUsernameLike(username string) *UserDo {
d.Where("username LIKE ?", "%"+username+"%")
return d
}
// WhereAgeGt 构造年龄大于条件
func (d *UserDo) WhereAgeGt(age uint8) *UserDo {
d.Where("age > ?", age)
return d
}
// WithCreateTimeBetween 构造创建时间区间条件
func (d *UserDo) WithCreateTimeBetween(start, end time.Time) *UserDo {
d.Where("create_time BETWEEN ? AND ?", start, end)
return d
}
// --------------- 字段赋值方法(示例)---------------
func (d *UserDo) SetUsername(username string) *UserDo {
d.Username = username
d.Set("username", username)
return d
}
func (d *UserDo) SetAge(age uint8) *UserDo {
d.Age = age
d.Set("age", age)
return d
}
3. dao 定义(数据访问层,提供操作方法)
通常放在 dao/user_dao.go 文件中,封装具体的数据库 CRUD 方法,对外提供简洁的调用接口,内部使用do构造条件。
package dao
import (
"context"
"time"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
"your-project/do"
"your-project/entity"
)
// UserDao 用户表的数据访问对象
// 封装所有与user表相关的数据库操作,对外屏蔽实现细节
var UserDao = NewUserDao()
func NewUserDao() *userDao {
return &userDao{}
}
type userDao struct{}
// --------------- 新增操作 ---------------
// Insert 新增单个用户
func (d *userDao) Insert(ctx context.Context, userDo *do.UserDo) (int64, error) {
result, err := userDo.Save(ctx)
if err != nil {
return 0, err
}
return result.LastInsertId(ctx)
}
// --------------- 查询操作 ---------------
// GetById 根据ID查询单个用户,返回entity(供上层业务使用)
func (d *userDao) GetById(ctx context.Context, id int64) (*entity.User, error) {
var userDo = do.NewUserDo() // 实例化do对象
// 使用do构造查询条件,执行查询
userEntity := &entity.User{}
err := userDo.WhereId(id).Scan(ctx, userEntity)
if err != nil {
return nil, err
}
return userEntity, nil
}
// GetListByUsername 模糊查询用户列表,返回entity列表
func (d *userDao) GetListByUsername(ctx context.Context, username string) (entity.UserList, error) {
var userDo = do.NewUserDo()
userList := entity.UserList{}
err := userDo.WhereUsernameLike(username).Scan(ctx, &userList)
if err != nil {
return nil, err
}
return userList, nil
}
// --------------- 更新操作 ---------------
// UpdateAgeById 根据ID更新用户年龄
func (d *userDao) UpdateAgeById(ctx context.Context, id int64, age uint8) (int64, error) {
var userDo = do.NewUserDo()
// 使用do构造更新条件和赋值
result, err := userDo.WhereId(id).SetAge(age).Update(ctx)
if err != nil {
return 0, err
}
return result.RowsAffected(ctx)
}
// --------------- 删除操作 ---------------
// DeleteById 根据ID删除用户
func (d *userDao) DeleteById(ctx context.Context, id int64) (int64, error) {
var userDo = do.NewUserDo()
result, err := userDo.WhereId(id).Delete(ctx)
if err != nil {
return 0, err
}
return result.RowsAffected(ctx)
}
4. 上层业务调用示例(如何协同使用三者)
package service
import (
"context"
"your-project/dao"
"your-project/do"
"your-project/entity"
)
type UserService struct{}
var UserServiceIns = &UserService{}
// CreateUser 新增用户(业务层调用)
func (s *UserService) CreateUser(ctx context.Context, username string, age uint8) (int64, error) {
// 1. 构造do对象(用于数据库操作)
userDo := do.NewUserDo().SetUsername(username).SetAge(age)
// 2. 调用dao层方法执行新增
return dao.UserDao.Insert(ctx, userDo)
}
// GetUserInfo 根据ID查询用户信息(业务层调用)
func (s *UserService) GetUserInfo(ctx context.Context, id int64) (*entity.User, error) {
// 直接调用dao层方法,返回entity(纯数据)供上层接口使用
return dao.UserDao.GetById(ctx, id)
}
五、补充说明
- 实际开发中,
do和dao通常可以通过 GoFrame 的代码生成工具gf gen dao自动生成,无需手动编写大量重复代码,仅需补充自定义的条件构造和业务相关方法。 entity可根据业务需求灵活调整,比如忽略部分数据库字段、添加 json 标签适配接口返回,与数据库表字段无需完全一一对应(但核心业务字段需保持一致)。- 三者的分层设计遵循 “单一职责原则”:
entity只管数据,do只管操作条件,dao只管数据访问,便于后续维护和扩展(比如更换数据库时,仅需修改dao和do,无需改动上层业务和entity)。
总结
entity是 “数据容器”,纯结构体无逻辑,负责承载数据在各层之间流转。do是 “操作工具”,负责构造数据库 CRUD 的条件和赋值,为dao提供支撑。dao是 “访问入口”,封装数据库交互逻辑,对外提供简洁的操作方法,是上层业务与数据库之间的桥梁。- 三者协同工作,实现了数据、操作、访问的分层解耦,是 GoFrame 框架中数据操作的核心最佳实践。