数据访问层(DAO层)在 Go 中的实现

数据访问层(DAO层)在 Go 中的实现

写项目时遇到了这个概念,于是就去了解了一些,看看别人的博客,最后输出这一篇文章

一、数据访问层(DAO层)的概念

数据访问层(DAO,Data Access Object)是软件架构设计中的一个概念,旨在将数据库的访问逻辑抽象化和封装起来,以便于更高层次的业务逻辑和数据访问代码之间的分离,从而使代码更易于维护和测试。这个概念主要来源于对象-关系映射(Object-Relational Mapping, ORM)分层设计模式,特别是在企业级应用中的应用非常广泛。

DAO的由来与背景

DAO模式的出现主要是为了解决早期软件开发中遇到的一些问题,其中包括:

  1. 代码重用性和维护性差:在没有使用DAO模式之前,数据库访问的代码通常与业务逻辑代码紧密耦合在一起,这导致了代码的重用性和维护性差。
  2. 缺乏抽象层:直接在业务逻辑代码中进行数据库操作,使得代码难以适应数据库的变化,比如更换数据库类型时,需要对业务逻辑代码进行大量修改。
  3. 测试困难:紧密耦合的代码使得单元测试变得非常困难,因为业务逻辑和数据库操作代码难以分离。

为了解决这些问题,DAO模式应运而生。它提供了一个中间层,将业务逻辑和底层的数据访问代码分离开来,从而增加了代码的重用性、便于维护,并且使得单元测试变得更加容易。

在 Go 语言中的优势

  • 清晰的结构:通过将数据访问代码与业务逻辑分开,开发者可以更清晰地理解系统的结构。这使得代码更易于阅读和理解。
  • 易于测试:DAO层可以被模拟或替换,使得单元测试更加方便。开发者可以在不依赖实际数据库的情况下测试业务逻辑。
  • 灵活性:如果需要更改数据源(例如从SQL数据库切换到NoSQL),只需修改DAO层的实现,而不影响业务逻辑。

在Go语言中,DAO层通常会定义接口以及具体的实现,这样可以通过依赖注入等方式灵活地替换实现。使用DAO层后,代码的可维护性和可扩展性都会大幅提高。

二、在Go语言项目中实现DAO层

实现DAO层的步骤相对简单,主要包括定义数据模型、创建DAO接口、实现DAO接口以及使用DAO层。以下我以我自己的项目为例:

第1步:定义模型

首先先在models目录下创建一个表示数据的结构体。在我的项目中要有一个记录用户成绩的表,于是我按如下创建:

1
2
3
4
5
6
type ColorWordRecord struct {
gorm.Model
UserID string `gorm:"size:36;not null;index"` // 用户ID
sunccessNum int `gorm:"check:sunccess_num"`
TrainingNum int `gorm:"check:training_num BETWEEN 1 AND 6"`
}

这里我引用了 GORM 框架,这个我将在下面介绍

第2步:创建DAO接口

一般来说要定义一个接口来描述数据访问操作,但我到没有定义接口,直接把各个函数作为 dao 变量的方法,实际效果与接口一致
以下是其他博客的例子,仅供参考

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package dao

import (
"context"
"go_dao_example/model"
)

// UserDAO 定义了关于用户操作的接口
type UserDAO interface {
CreateUser(ctx context.Context, user *model.User) error
GetUserByID(ctx context.Context, id int64) (*model.User, error)
UpdateUser(ctx context.Context, user *model.User) error
DeleteUser(ctx context.Context, id int64) error
}

第3步:实现DAO接口

接下来就是来实现 DAO 接口,我的代码省去了上面的步骤,直接来到了这一步

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package dao

import (
"braintraining/backend/models"
"gorm.io/gorm"
)

type ColorWordDAO struct { //这是一个数据访问对象的实现,*gorm.DB:指向 GORM 数据库连接对象的指针。
db *gorm.DB
}

func NewColorWordDAO(db *gorm.DB) *ColorWordDAO { //建立一个新的 dao 对象
return &ColorWordDAO{db: db}
}

func (d *ColorWordDAO) CreateRecord(record *models.ColorWordRecord) error { //实现添加数据的方法
return d.db.Create(record).Error
}

func (d *ColorWordDAO) GetUserRecords(userID string, limit int) ([]models.ColorWordRecord, error) { //实现一个获得用户数据的方法
var records []models.ColorWordRecord
err := d.db.Where("user_id = ?", userID).
Order("created_at DESC").
Limit(limit).
Find(&records).
Error
return records, err
}

//其他方法实现。。。

第4步:使用DAO

在业务逻辑中,使用DAO接口而不是具体的实现,便于进行单元测试和未来的修改。

在我的业务逻辑中,代码是这样使用DAO的:

1
2
3
4
5
6
7
8
type ColorWordHandler struct {
dao *dao.ColorWordDAO
}

func NewColorWordHandler(dao *dao.ColorWordDAO) *ColorWordHandler {
return &ColorWordHandler{dao: dao}
}
//我在这里新加了一个名为 ColorWordHandler 的结构体及其构造函数,用于处理 HTTP 请求并调用 DAO 层进行数据操作

接下来就像这样去使用

1
2
3
func (h *ColorWordHandler) ColorWordsMatrix(r *gin.Context) {
//方法实现
}

测试 DAO 层的实现

测试DAO层的实现是确保数据访问逻辑正确性的关键部分。Go语言提供了强大的测试工具,使得测试变得简单。以下是一些步骤和注意事项:

  • 使用模拟对象:通过创建一个模拟的DAO实现,可以隔离测试,避免依赖于实际的数据库。例如,您可以创建一个MockUserDAO结构体,实现UserDAO接口:
1
2
3
4
5
6
7
8
9
10
11
12
13
type MockUserDAO struct {
Users map[int]*User
}

func (m *MockUserDAO) GetUserByID(id int) (*User, error) {
user, exists := m.Users[id]
if !exists {
return nil, fmt.Errorf("user not found")
}
return user, nil
}

// 其他方法实现...
  • 编写单元测试:使用Go的testing包编写单元测试,验证DAO层的各种操作。例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func TestGetUserByID(t *testing.T) {
mockDAO := &MockUserDAO{
Users: map[int]*User{
1: {ID: 1, Name: "Alice", Email: "alice@example.com"},
},
}

user, err := mockDAO.GetUserByID(1)
if err != nil {
t.Fatalf("expected no error, got %v", err)
}
if user.Name != "Alice" {
t.Fatalf("expected user Alice, got %v", user.Name)
}
}
  • 运行测试:使用go test命令运行测试,确保您的DAO实现符合预期。

通过使用模拟对象和单元测试,可以有效地测试DAO层的实现,确保其正确性。这种方法不仅提升了代码的质量,还增强了团队的开发信心。

三、GORM 框架使用

我在 DAO 层中使用 GORM 框架进行数据库操作,接下来我将去介绍 GORM 框架的使用

(1)GORM 简介

GORM 是用 Go 语言编写的 ORM 库,它基于 httprouter 和 Go 标准库构建。其主要特点包括:

  • 简洁易用:通过定义结构体来映射数据库表,简化数据操作;
  • 功能全面:支持 CRUD、事务、预加载、关联关系、自动迁移等常见功能;
  • 扩展性强:内置钩子函数、插件机制以及对多种数据库(MySQL、PostgreSQL、SQLite、SQL Server 等)的支持;
  • 性能优秀:经过大量优化,能够在高并发场景下保持稳定性能。

参考:GORM 官方文档

(2)环境搭建与安装

在使用 GORM 之前,要通过 go get 命令安装 GORM 及所需数据库驱动。例如,如果使用 MySQL 数据库,可以这样安装:

1
2
3
4
5
# 安装 GORM 框架
go get -u gorm.io/gorm

# 安装 MySQL 驱动
go get -u gorm.io/driver/mysql

安装完成后,在项目代码中导入相关包:

1
2
3
4
import (
"gorm.io/gorm"
"gorm.io/driver/mysql"
)

(3)基本用法

3.1 定义模型

在 GORM 中,数据库中的每个表通常对应一个 Go 的结构体。通过结构体字段和标签,GORM 可以自动映射表结构及字段属性。例如,定义一个用户表:

1
2
3
4
5
6
7
type User struct {
ID uint `gorm:"primaryKey"`
Name string `gorm:"size:100;not null"`
Age int `gorm:"not null"`
Email string `gorm:"unique;not null"`
CreatedAt time.Time
}

3.2 初始化数据库连接

通过指定数据库驱动和连接字符串,初始化数据库连接:

1
2
3
4
5
dsn := "username:password@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}

3.3 自动迁移

GORM 提供自动迁移功能,可以根据模型自动创建或更新表结构:

1
2
// 自动迁移 User 模型对应的数据库表
db.AutoMigrate(&User{})

3.4 基本 CRUD 操作

  • 创建记录
1
2
user := User{Name: "Alice", Age: 30, Email: "alice@example.com"}
db.Create(&user)
  • 查询记录
1
2
3
var user User
db.First(&user, 1) // 根据主键查询
db.Where("name = ?", "Alice").First(&user)
  • 更新记录
1
2
3
db.Model(&user).Update("Age", 31)
// 或者批量更新
db.Model(&user).Updates(User{Age: 32, Email: "newalice@example.com"})
  • 删除记录
1
db.Delete(&user, 1)

(4) 处理关联关系

GORM 支持多种关联关系,如一对一、一对多、多对多以及多态关联。以下是一些常见关联关系的示例:

4.1 一对多关系

假设一个用户(User)拥有多个订单(Order):

1
2
3
4
5
6
7
8
9
10
11
type Order struct {
ID uint `gorm:"primaryKey"`
Item string
UserID uint
}

type User struct {
ID uint `gorm:"primaryKey"`
Name string
Orders []Order
}

查询用户及其订单:

1
2
var user User
db.Preload("Orders").First(&user, 1)

4.2 多对多关系

例如,文章(Article)和标签(Tag)之间存在多对多关系:

1
2
3
4
5
6
7
8
9
10
type Article struct {
ID uint `gorm:"primaryKey"`
Title string
Tags []Tag `gorm:"many2many:article_tags;"`
}

type Tag struct {
ID uint `gorm:"primaryKey"`
Name string
}

插入和查询多对多数据:

1
2
3
4
5
6
7
8
9
10
11
article := Article{
Title: "GORM 教程",
Tags: []Tag{
{Name: "GoLang"},
{Name: "ORM"},
},
}
db.Create(&article)

var art Article
db.Preload("Tags").First(&art, article.ID)

(5)性能优化与最佳实践

在实际项目中使用 GORM 时,可以考虑以下几点来提高性能和代码质量:

  • 合理使用 Preload:对于复杂关联查询,预加载(Preload)可以减少 N+1 查询问题,但在数据量较大时要注意性能;
  • 事务管理:对于需要保证操作原子性的场景,使用事务(db.Transaction())来管理操作;
  • 批量操作:尽可能使用批量插入和更新,减少数据库连接次数;
  • 日志与调试:开启 GORM 日志模式(db.Debug())可以帮助调试,但在生产环境中建议关闭;
  • 连接池配置:合理配置数据库连接池参数(如最大连接数、空闲连接数、连接超时)以适应高并发需求。

四、结语

我们可以看到,数据访问层(DAO)在软件架构设计中扮演着至关重要的角色。通过提供一个抽象层,DAO模式不仅提高了代码的重用性、便于维护和测试,也增加了软件的灵活性和可扩展性。随着软件开发实践的不断进化,DAO模式的概念也在不断地被优化和改进,但其核心价值——解耦业务逻辑与数据访问代码,保证软件架构的清晰和高效——始终不变。

参考博客

深入浅出数据访问层(DAO):从概念到Go语言实践-腾讯云开发者社区-腾讯云

.go语言怎么找到dao层 • Worktile社区

GORM 指南 | GORM - The fantastic ORM library for Golang, aims to be developer friendly.

Golang 开发 - GORM 框架使用技术解析 - 知乎

DAO层概览 | GORM - The fantastic ORM library for Golang, aims to be developer friendly.


数据访问层(DAO层)在 Go 中的实现
http://example.com/2025/07/16/数据访问层(DAO层)在-Go-中的实现/
作者
yuhua
发布于
2025年7月16日
许可协议