database - 由于这种模式,在同一个结构中使用事务和简单的数据库连接我该怎么办?

标签 database go transactions go-gorm go-pg

我找到了a great example of transactions between Repositories使用清洁架构方法。
这家伙正在使用 Gorm .
Gorm 具有相同的数据库连接和事务类型,例如:

var db *gorm.DB
var tx *gorm.DB
我是 go-pg 的粉丝.但是这里的类型是不同的(也许它甚至更好),例如:
var db *pg.DB
var tx *pg.Tx
当然错误是:Cannot use 'tx' (type *Tx) as type *pg.DB一个小复制:
package main

import (
    "github.com/go-pg/pg/v10"
)

type Player struct {
    ID   int
    Name string
}

type PlayerRepo struct {
    db       *pg.DB
    teamRepo *TeamRepo
}

type TeamRepo struct {
    db *pg.DB
}

func NewPlayerRepo(db *pg.DB) *PlayerRepo {
    return &PlayerRepo{
        db:       db,
        teamRepo: NewTeamRepo(db),
    }
}

func NewTeamRepo(db *pg.DB) *TeamRepo {
    return &TeamRepo{db: db}
}

func (r *PlayerRepo) Find(id int) (*Player, error) {
    var player Player
    err := r.db.Model(&player).Where("id = ?", id).Select()
    if err != nil {
        return nil, err
    }
    return &player, nil
}

func (r *PlayerRepo) All() ([]*Player, error) {
    // Long code
    return nil, nil
}

func (r *PlayerRepo) Insert() (*Player, error) {
    // Long code
    return nil, nil
}

func (r *PlayerRepo) Update() (*Player, error) {
    // Long code
    return nil, nil
}

func (r *PlayerRepo) Delete() (*Player, error) {
    // Long code
    return nil, nil
}

func (r *PlayerRepo) WithTransaction(txFunc func(*PlayerRepo) error) (err error) {
    tx, _ := r.db.Begin()
    manager := NewPlayerRepo(tx) // <<<--- here the problem! tx is not good here, it's `pg.Tx` not `pg.DB`
    err = txFunc(manager)
    return
}
我能做些什么来解决这个问题?
提前致谢。 ❤️

最佳答案

您可以定义一个已经由两者隐式实现的接口(interface):

type DB interface {
    Begin() (*Tx, error)
    Close() error
    Context() context.Context
    CopyFrom(r io.Reader, query interface{}, params ...interface{}) (res Result, err error)
    CopyTo(w io.Writer, query interface{}, params ...interface{}) (res Result, err error)
    Exec(query interface{}, params ...interface{}) (Result, error)
    ExecContext(c context.Context, query interface{}, params ...interface{}) (Result, error)
    ExecOne(query interface{}, params ...interface{}) (Result, error)
    ExecOneContext(c context.Context, query interface{}, params ...interface{}) (Result, error)
    Formatter() orm.QueryFormatter
    Model(model ...interface{}) *orm.Query
    ModelContext(c context.Context, model ...interface{}) *orm.Query
    Prepare(q string) (*Stmt, error)
    Query(model interface{}, query interface{}, params ...interface{}) (Result, error)
    QueryContext(c context.Context, model interface{}, query interface{}, params ...interface{}) (Result, error)
    QueryOne(model interface{}, query interface{}, params ...interface{}) (Result, error)
    QueryOneContext(c context.Context, model interface{}, query interface{}, params ...interface{}) (Result, error)
    RunInTransaction(ctx context.Context, fn func(*Tx) error) error
}
注意:我只知道方法名称匹配,我没有检查签名是否也匹配,如果不匹配,则需要相应地编辑接口(interface)。
您可以添加一个简单的“编译器检查”:
var _ DB = (*pg.DB)(nil)
var _ DB = (*pg.Tx)(nil)
然后您可以更改 PlayerRepo.db 的类型来自 *pg.DB 的字段到您的新 DB界面。
type PlayerRepo struct {
    db       DB
    teamRepo *TeamRepo
}

type TeamRepo struct {
    db DB
}

func NewPlayerRepo(db DB) *PlayerRepo {
    return &PlayerRepo{
        db:       db,
        teamRepo: NewTeamRepo(db),
    }
}

func NewTeamRepo(db DB) *TeamRepo {
    return &TeamRepo{db: db}
}


func (r *PlayerRepo) WithTransaction(txFunc func(*PlayerRepo) error) (err error) {
    tx, err := r.db.Begin()
    if err != nil {
        return err
    }
    defer func() {
        // rollback if err; commit if no err
    }()
    manager := NewPlayerRepo(tx)
    err = txFunc(manager)
    return
}

如果您的 repo 类型需要能够调用一些对两者都不通用的方法 pg.DBpg.Tx ,因此未由新的 DB 定义接口(interface),那么,一种方法是保留原始类型以供此类使用,例如:
type PlayerRepo struct {
    db       DB
    pg       *pg.DB
    teamRepo *TeamRepo
}

type TeamRepo struct {
    db DB
    pg *pg.DB
}

func NewPlayerRepo(db DB, pg *pg.DB) *PlayerRepo {
    return &PlayerRepo{
        db:       db,
        pg:       pg,
        teamRepo: NewTeamRepo(db, pg),
    }
}

func NewTeamRepo(db DB, pg *pg.DB) *TeamRepo {
    return &TeamRepo{db: db, pg: pg}
}

func (r *PlayerRepo) WithTransaction(txFunc func(*PlayerRepo) error) (err error) {
    tx, err := r.db.Begin()
    if err != nil {
        return err
    }
    defer func() {
        // rollback if err; commit if no err
    }()
    manager := NewPlayerRepo(tx, r.pg)
    err = txFunc(manager)
    return
}

请注意,如果您决定使用 orm.DB ,这是合理的,但它缺少一些您需要并且已经由 pg.DB 实现的方法和 pg.Tx ,那么您可以嵌入 orm.DB进入您的自定义界面并仅添加那些缺少的方法。
type DB interface {
    Begin() (*Tx, error)
    orm.DB
}

关于database - 由于这种模式,在同一个结构中使用事务和简单的数据库连接我该怎么办?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64627575/

相关文章:

grails - 如何在 Grails 中的现有事务中创建新事务

php - 使用 php 和 mysqli 有多少行包含特定值

sql - 带 Hibernate 的组合框

pointers - 'myVariable.(type)' 究竟返回什么?

json - Golang 解析带有嵌入式 XML 的 JSON

java - 有状态 EJB 生命周期问题

database - 关于 GAE 数据存储的索引

sql-server - 为什么要将主键存储在另一个表中,而不是使用自增?

go - channel 中缺少数据

c# - 将 SqlTransaction 与 SqlDataReader 一起使用