geektutu / blog

极客兔兔的博客,Coding Coding 创建有趣的开源项目。
https://geektutu.com
Apache License 2.0
166 stars 21 forks source link

动手写ORM框架 - GeeORM第六天 支持事务(Transaction) | 极客兔兔 #78

Open geektutu opened 4 years ago

geektutu commented 4 years ago

https://geektutu.com/post/geeorm-day6.html

7天用 Go语言/golang 从零实现 ORM 框架 GeeORM 教程(7 days implement golang object relational mapping framework from scratch tutorial),动手写 ORM 框架,参照 gorm, xorm 的实现。介绍数据库中的事务(transaction);封装事务,用户自定义回调函数实现原子操作。

xiaoxfan commented 4 years ago

事务这个操作六 我得赶紧把我项目改成这种

geektutu commented 4 years ago

@xiaoxfan 事务参考了 stackoverflow 的实现,这个实现感觉蛮精巧的,代码很优雅。

bxclib2 commented 3 years ago

hello 我想问下您rollback 的错误为什么忽略掉了呢 _ = s.Rollback() 谢谢!

liangjfblue commented 3 years ago
func transactionRollback(t *testing.T) {
    engine := OpenDB(t)
    defer engine.Close()
    s := engine.NewSession()
    _ = s.Model(&User{}).DropTable()
    _, err := engine.Transaction(func(s *session.Session) (result interface{}, err error) {
        _ = s.Model(&User{}).CreateTable()
        _, err = s.Insert(&User{"Tom", 18})
        return nil, errors.New("Error")
    })
    if err == nil || s.HasTable() {
        t.Fatal("failed to rollback")
    }
}

这里 err == nil || s.HasTable() 的判断是不是错了? 自定义的errors.New("Error")确实让err == nil 不通过, 但是确实是创建了表, 所以 s.HasTable() 是true的, 从而导致单元测试失败了

liangjfblue commented 3 years ago

@bxclib2 hello 我想问下您rollback 的错误为什么忽略掉了呢 _ = s.Rollback() 谢谢!

回滚不覆盖err, 是因为回滚就是因为有业务的报错, 所以不应该被这条语句覆盖掉业务的err, 业务err比回滚失败的err更重要

furthergo commented 3 years ago

@liangjfblue

func transactionRollback(t *testing.T) {
  engine := OpenDB(t)
  defer engine.Close()
  s := engine.NewSession()
  _ = s.Model(&User{}).DropTable()
  _, err := engine.Transaction(func(s *session.Session) (result interface{}, err error) {
      _ = s.Model(&User{}).CreateTable()
      _, err = s.Insert(&User{"Tom", 18})
      return nil, errors.New("Error")
  })
  if err == nil || s.HasTable() {
      t.Fatal("failed to rollback")
  }
}

这里 err == nil || s.HasTable() 的判断是不是错了? 自定义的errors.New("Error")确实让err == nil 不通过, 但是确实是创建了表, 所以 s.HasTable() 是true的, 从而导致单元测试失败了

这里就是为了测试rollback的,先Droptable,然后在事务里创建了User表,如果最后有User表,说明rollback失败了

furthergo commented 3 years ago
func (engine *Engine) Transaction(f TxFunc) (result interface{}, err error) {
    s := engine.NewSession()
    if err := s.Begin(); err != nil {
        return nil, err
    }
    defer func() {
        if p := recover(); p != nil {
            _ = s.Rollback()
            panic(p) // re-throw panic after Rollback
        } else if err != nil {
            _ = s.Rollback() // err is non-nil; don't change it
        } else {
            err = s.Commit() // err is nil; if Commit returns error update err
        }
    }()

    return f(s)
}

请教一下err = s.Commit()这里err不为nil时是否还需要Rollback?我看gorm把这个err也加进了需要Rollback的判断里

geektutu commented 3 years ago

@furthergo 感谢指出,Commit 失败再回滚一次,应该是需要的。参考 gorm,改成这样子会安全一些:

} else {
    defer func ()  {
        if err != nil {
            _ = s.Rollback()
        }
    }
    err = s.Commit() // err is nil; if Commit returns error update err
}
cwww3 commented 3 years ago

对mysql来说 测试回滚的例子是不是不对 mysql当执行到DDL语句时,会隐式的将当前回话的事务进行一次“COMMIT”操作

nc-77 commented 2 years ago

@cwww3 对mysql来说 测试回滚的例子是不是不对 mysql当执行到DDL语句时,会隐式的将当前回话的事务进行一次“COMMIT”操作

确实,测试中第7行CreateTable后回隐式commit导致无法全部回滚

liangjfblue commented 2 years ago

@liangjfblue

func transactionRollback(t *testing.T) {
    engine := OpenDB(t)
    defer engine.Close()
    s := engine.NewSession()
    _ = s.Model(&User{}).DropTable()
    _, err := engine.Transaction(func(s *session.Session) (result interface{}, err error) {
        _ = s.Model(&User{}).CreateTable()
        _, err = s.Insert(&User{"Tom", 18})
        return nil, errors.New("Error")
    })
    if err == nil || s.HasTable() {
        t.Fatal("failed to rollback")
    }
}

这里 err == nil || s.HasTable() 的判断是不是错了? 自定义的errors.New("Error")确实让err == nil 不通过, 但是确实是创建了表, 所以 s.HasTable() 是true的, 从而导致单元测试失败了

这里就是为了测试rollback的,先Droptable,然后在事务里创建了User表,如果最后有User表,说明rollback失败了

是的, 感谢指出

bowenddd commented 1 year ago

是不是在执行完成Commit和Rollback方法最后中要将s.tx设置为nil呢,否则一旦开启事务后。之后所有的sql语句都将通过s.tx来执行了

callmePicacho commented 1 year ago

@bowenddd 是不是在执行完成Commit和Rollback方法最后中要将s.tx设置为nil呢,否则一旦开启事务后。之后所有的sql语句都将通过s.tx来执行了

不用啊,Transaction 里的 session 在方法中 New 出来的,生命周期都在这个方法里