详解Go语言如何对数据库进行CRUD操作

 更新时间:2023年05月21日 15:50:49   作者:LightSaid  
在这篇文章中,主要带大家来学习一下在Go语言中如何对数据库进行CRUD操作,从而探讨一下Go的接口编程,感兴趣的小伙伴可以跟随小编一起学习一下

Go语言中的接口

在这里我将不会介绍接口基础知识,如:接口定义和实现、空接口、类型断言、v, ok := i.(T)、switch x.(type)、接口嵌套、指针接收器与值接收器实现接口、接口零值 这些基础概念不作多说,如不太清楚上面接口知识,自行学习补充。这里介绍在开发中接口实在用法,也是由上面基础演变而成。

使用接口编程

有这么一个普通场景,上传并保存文件,三七二十一蹭蹭蹭,一下子搞完,so easy,代码如下:

项目准备上线,公司购买了OSS存储,文件资源要存到OSS上,那么如何修改上面的代码呢? 这个时候接口就可以上场了。 实现如下:

// NOTE: storage.go
// 存储文件接口
type Storage interface {
    Save(data []byte) (string, error)
}
// 开发时候使用本地存储
type localStorage struct {
    config *struct{ base string }
}
func NewLocalStorage(cfg struct{ base string }) *localStorage {
    return &localStorage{config: &cfg}
}
func (store localStorage) Save(data []byte) (string, error) {
    return "http://127.0.0.1/" + store.config.base + "/a.png", nil
}
// 生产使用oss存储
type ossStorage struct{}
func NewOSSStorage() *ossStorage {
    return &ossStorage{}
}
func (store ossStorage) Save(data []byte) (string, error) {
    return "https://abc.com/oss/a.png", nil
}
// NOTE: upload.go
// 保存文件
func saveFile(store Storage) {
    var data []byte // 伪代码
    url, _ := store.Save(data)
    fmt.Println(url)
}
func main() {
    var store Storage
    if os.Getenv("ENV") == "prod" {
        store = NewOSSStorage()
    } else {
        store = NewLocalStorage(struct{ base string }{base: "/static"})
	}
    saveFile(store)
}

上面定义Storage接口,localStorage、ossStorage 都是实现了接口,而 saveFile(store Storage) 接受指针。 这种方式就是接受接口返回结构,是 Go 语言接口编程中非常经典实用模式。

接受接口返回结构

上面已经的例子就是使用这个法则,使用这个法则让代码变得更加灵活,它是松耦合的,更加方便mock数据测试,可以避免一些不必要的bug。

现在举一个不方便测试的例子:

type Repository struct {
    DB *sql.DB
}
func NewRepository(db *sql.DB) *Repository {
    return &Repository{DB: db}
}
func (repo *Repository) Insert() {
    // do some thing
}
func (repo *Repository) Update() {
    // do some thing
}
type Service struct {
    Repo Repository
}
func NewService(db *sql.DB) *Service {
    return &Service{
        Repo: *NewRepository(db),
    }
}
func main() {
    // 假设是真实的链接 &sql.DB{}
    srv := NewService(&sql.DB{})
    srv.Repo.Insert()
    srv.Repo.Update()
}

上面的例子看着无任何问题啊?这不很正常。是的很正常,就是测试的时候不方便。 如果要测试起来,你必须要创建一个数据库,连接数据库,创建sql.DB实例。才能完成测试。如果想简单mock数据,就不好办了。

因此还是建议,使用接口编程。修改如下:

type Repository interface {
	Insert()
	Update()
}

type repository struct {
	DB *sql.DB
}

func NewRepository(db *sql.DB) *repository {
	return &repository{DB: db}
}

func (repo *repository) Insert() {
	// do some thing
}

func (repo *repository) Update() {
	// do some thing
}

type Service struct {
	Repo Repository
}

func NewService(r Repository) *Service {
	return &Service{
		Repo: r,
	}
}

func main() {
	// 假设是真实的链接 &sql.DB{}
	r := NewRepository(&sql.DB{})
	srv := NewService(r)
	srv.Repo.Insert()
	srv.Repo.Update()
}

接口检查

上面的例子,func NewRepository(db *sql.DB) *repository 返回的是结构体指针。在编码过程,很容易忘记实现接口,甚至全部接口都没实现,编译器也不会报错。我怎么知道repository结构体就一定全部实现了Repository接口呢,对吧。

那这个时候就要用到接口检查了。 接口检查的语法是

var _ MyInterface = (*MyStruct)(nil)

上面当我没有实现Inser方法时就会直接编译不通过。

这就是接口检查带来好处。

扩展接口

假设有这么一场景,在开发中我们使用到了第三方库,第三库提供一个Worker接口,我们在work、next两个方法都使用到Worker接口,入参就是它。这个时候我们希望在work函数中对Context增加额外的属性,提供给next函数使用。但现实是没法直接通过context.WithValue(w.Context(), "greet", "Hello")设置值的,因为Worker接口仅返回Context并重新没有设置Context方法提供;由于Worker是第三方的接口,不可直接去改的。因此就需要扩展接口。问题如图:

如何解决这个问题呢?现在要解决什么问题?如果把问题细分的话,要解决2个问题:

  • 扩展一个设置Context的方法
  • 保留原来的方法,只做扩充,不做删除 那要解决这个问题最好的方式就是接口嵌套。

代码该如何写呢?解法如下:

首先是要定义新的接口wrapWorker,将旧的接口嵌套进来,这样就保留了原始有的方法,然后在定义新的方法SetContext(context.Context),接口定义好了,就用实现接口对吧,思路很直接。

紧接着定义一个 wrap 结构体, 保存ctx,同时实SetContext(context.Context)

还有重要的一环节,就是重写 Context() context.Context 方法。

实现代码如下:

// 第三方的接口定义
type Worker interface {
    Context() context.Context
    DoSomeThing()
    // ...
}

type wrapWorker interface {
    Worker
    SetContext(context.Context)
}
type wrap struct {
    Worker
    ctx context.Context
}

func newWrap(w Worker) wrapWorker {
    return &wrap{
        w,
        w.Context(),
    }
}
func (wp *wrap) SetContext(ctx context.Context) {
    wp.ctx = ctx
}

func (wp wrap) Context() context.Context {
    return wp.ctx
}

type contextKey string

func work(w Worker) {
    wp := newWrap(w)
    ctx := context.WithValue(w.Context(), contextKey("greet"), "Hello")
    wp.SetContext(ctx)

    next(wp)
}

func next(w Worker) {
    v := w.Context().Value(contextKey("greet"))
    fmt.Printf("-> %v \n", v)
    w.DoSomeThing()
}

type person string

func (person) Context() context.Context {
    return context.Background()
}
func (p person) DoSomeThing() {
    fmt.Println(string(p), "吃饭睡觉打代码~")
}

func main() {
    var p person = "张三"
    work(p)
}

执行结果符合预期,OK 没问题~

其实上面不定义新接口wrapWorker也是可以的,用wrap包裹接口即可。用接口设计更符合接口编程思想。

到此这篇关于详解Go语言如何对数据库进行CRUD操作的文章就介绍到这了,更多相关Go语言数据库CRUD操作内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Golang defer延迟语句的实现

    Golang defer延迟语句的实现

    defer拥有注册延迟调用的机制,本文主要介绍了Golang defer延迟语句的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2024-07-07
  • Go 语言入门学习之时间包

    Go 语言入门学习之时间包

    这篇文章主要介绍了Go 语言入门学习之时间包,GO 语言提供了 ​​time​​包来测量和显示时间,下文关于GO时间包的相关介绍需要的小伙伴可以参考一下
    2022-04-04
  • golang中channel+error来做异步错误处理有多香

    golang中channel+error来做异步错误处理有多香

    官方推荐golang中错误处理当做值处理, 既然是值那就可以在channel中传输,这篇文章主要介绍了golang 错误处理channel+error真的香,需要的朋友可以参考下
    2023-01-01
  • Go语言底层原理互斥锁的实现原理

    Go语言底层原理互斥锁的实现原理

    这篇文章主要介绍了Go语言底层原理互斥锁的实现原理,Go sync包提供了两种锁类型,分别是互斥锁sync.Mutex和读写互斥锁sync.RWMutex,都属于悲观锁,更多相关内容需要的朋友可以查看下面文章内容
    2022-08-08
  • Go语言map实现顺序读取

    Go语言map实现顺序读取

    当我们遍历 map 时,那就是输出的键值对顺序是不确定的,本文主要介绍了Go语言map实现顺序读取, 文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-05-05
  • 使用Go语言简单模拟Python的生成器

    使用Go语言简单模拟Python的生成器

    这篇文章主要介绍了使用Go语言简单模拟Python的生成器,Python的generator是非常酷的功能,用Go实现的代码也较为简洁,需要的朋友可以参考下
    2015-08-08
  • Go语言常见错误之滥用getters/setters误区实例探究

    Go语言常见错误之滥用getters/setters误区实例探究

    在Go语言编程中,恰如其分地使用getters和setters是至关重要的,过度和不适当地使用它们可能导致代码冗余、可读性差和封装不当,在本文中,我们将深入探讨如何识别滥用getter和setter的情况,以及如何采取最佳实践来避免这些常见的Go错误
    2024-01-01
  • Golang 探索对Goroutine的控制方法(详解)

    Golang 探索对Goroutine的控制方法(详解)

    下面小编就为大家分享一篇Golang 探索对Goroutine的控制方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2017-12-12
  • Golang中context库的高级应用

    Golang中context库的高级应用

    context库不仅对于提升代码的效率和性能至关重要,而且还帮助开发者在复杂的系统中保持代码的清晰和可维护性,下面我们就来看看context库的高级应用吧
    2024-01-01
  • go依赖注入库samber/do使用示例讲解

    go依赖注入库samber/do使用示例讲解

    这篇文章主要介绍了go依赖注入库samber/do使用,在本文中,我们学习了如何使用samber/do在 Go 中提供依赖注入,需要的朋友可以参考下
    2024-02-02

最新评论