Golang中SQLC DBTX接口的使用与解析

Golang中SQLC DBTX接口的使用与解析 我开始使用SQLC,遇到了一个小问题。 自动生成的代码如下所示:

type DBTX interface {
	ExecContext(context.Context, string, ...interface{}) (sql.Result, error)
	PrepareContext(context.Context, string) (*sql.Stmt, error)
	QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error)
	QueryRowContext(context.Context, string, ...interface{}) *sql.Row
}

func New(db DBTX) *Queries {
	return &Queries{db: db}
}

type Queries struct {
	db DBTX
}

我们使用函数 New(db DBTX) 来获取接收器方法的访问权限,但我们必须向该函数传递一个实现了 DBTX 接口的对象。我注意到,我们可以直接传递数据库连接 *sql.DB,即使 *sql.DB 没有实现该接口,它也能正常工作。这是如何实现的?


更多关于Golang中SQLC DBTX接口的使用与解析的实战教程也可以访问 https://www.itying.com/category-94-b0.html

4 回复

好的,我想我明白了。db 包已经实现了这些方法,因此当我创建一个具有相同方法(相同签名)的接口时,就意味着 db 包也实现了我的接口。是这样吗?

更多关于Golang中SQLC DBTX接口的使用与解析的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


mfechner:

这就是Go中继承的工作方式。

虽然你回复的其余部分完全正确,但我确实想吹毛求疵地讨论一下这个说法。

事实上,Go 并不支持继承;相反,它通过类型嵌入支持组合。实际上,组合和类型嵌入与接口是正交的。

这实际上是一个 “隐式接口” 的例子。

是的,这是正确的。 如果你的结构体附加了具有相同签名(函数名、参数和返回值)的函数,它也会自动成为一个 DBTX。 这就是 Go 中继承的工作方式。

一个可能经常被使用的很好的例子是 io.Writer (io package - io - Go Packages)。 因此,如果你的类(结构体)实现了函数 Write(p []byte) (n int, err error),它将自动成为一个 io.Writer,并且可以传递给任何期望 io.Writer 的调用。

func main() {
    fmt.Println("hello world")
}

*sql.DB 确实实现了 DBTX 接口,因为 *sql.DB 包含了接口中定义的所有方法。以下是验证代码:

package main

import (
    "context"
    "database/sql"
    "fmt"
    _ "github.com/lib/pq"
)

// 检查 *sql.DB 是否实现 DBTX 接口
var _ DBTX = (*sql.DB)(nil)

type DBTX interface {
    ExecContext(context.Context, string, ...interface{}) (sql.Result, error)
    PrepareContext(context.Context, string) (*sql.Stmt, error)
    QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error)
    QueryRowContext(context.Context, string, ...interface{}) *sql.Row
}

func main() {
    // 实际使用示例
    db, err := sql.Open("postgres", "connection_string")
    if err != nil {
        panic(err)
    }
    defer db.Close()
    
    // 验证类型转换
    var dbTx DBTX = db
    fmt.Printf("类型断言成功: %T\n", dbTx)
    
    // 使用示例
    ctx := context.Background()
    result, err := dbTx.ExecContext(ctx, "INSERT INTO users(name) VALUES($1)", "John")
    if err != nil {
        panic(err)
    }
    fmt.Printf("插入结果: %v\n", result)
}

*sql.DB 自动满足 DBTX 接口是因为 Go 的隐式接口实现机制。只要类型拥有接口定义的所有方法,就自动实现了该接口。*sql.DB 的方法签名与 DBTX 完全匹配:

// database/sql 包中的实际方法签名
func (db *DB) ExecContext(ctx context.Context, query string, args ...interface{}) (Result, error)
func (db *DB) PrepareContext(ctx context.Context, query string) (*Stmt, error)
func (db *DB) QueryContext(ctx context.Context, query string, args ...interface{}) (*Rows, error)
func (db *DB) QueryRowContext(ctx context.Context, query string, args ...interface{}) *Row

这种设计使得 SQLC 生成的代码可以同时兼容 *sql.DB*sql.Tx(事务对象),因为两者都实现了相同的接口:

// 事务示例
func processTransaction(db *sql.DB) error {
    tx, err := db.Begin()
    if err != nil {
        return err
    }
    
    queries := New(tx) // *sql.Tx 也实现了 DBTX
    ctx := context.Background()
    
    // 使用事务执行查询
    err = queries.UpdateUser(ctx, UpdateUserParams{
        ID:   1,
        Name: "Updated Name",
    })
    if err != nil {
        tx.Rollback()
        return err
    }
    
    return tx.Commit()
}

这种接口设计提供了灵活性,允许在数据库连接和事务之间无缝切换,而不需要修改业务逻辑代码。

回到顶部