gorm事务回滚

544 阅读4分钟

前言

在使用gorm持久化框架中,有时为了保持数据一致性,要开启事务进行管理,有效处理错误和回滚的策略,gorm提供了几种事务回滚策略供我们使用

gorm事务回滚

数据一致性

备注:本文使用的是MySQL数据库

1、创建一个user数据库表

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `sex` int(11) NULL DEFAULT NULL,
  `userName` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 68 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

2、创建一条数据为69的数据,如下图

image.png 使用gorm编写删除69数据和和插入一条数据,并且异常

package main

import (
    "database/sql"
    "fmt"
    _ "github.com/go-sql-driver/mysql"
    "gorm.io/driver/mysql"
    "gorm.io/gorm"
    "log"
)

type User struct {
    Id   int64          `gorm:"column:id"`
    Name sql.NullString `gorm:"column:userName"`
    Sex  int            `gorm:"column:sex"`
}

func main() {

    dsn := "root:123456@tcp(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=True&loc=Local"
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
       fmt.Println("连接数据库错误:", err)
       return
    }

    user := User{
       Id: 69,
    }

    result := db.Table("user").Delete(&user)

    user1 := User{
       Name: sql.NullString{
          "aaa",
          true,
       },
       Sex: 1,
    }

    result = db.Table("user").Create(&user1)
    log.Println("保存结果为:", user1)
    fmt.Println(result)
    var (
       a = 1
       b = 0
    )
    c := a / b
    log.Println("结果为:", c)

}

结果为

image.png 这个时候,可以看到,数据库中69数据已经被删除了,创建了一条70的数据

image.png 在程序执行中,发生了异常,事务没有回滚,这个时候,我们需要使用gorm事务保持数据一致性和原子性

gorm事务操作

自动使用事务

1、gorm提供了一个闭包函数,在这个函数中,不需要手动commit提交事务或者Rollback回滚事务,Transaction开启的自动事务是不需要你手动执行Commit() 提交事务 和Rollback()回滚事务的, Commit()方法会在闭包函数执行完毕后自动执行, Rollback()方法会在闭包中有异常时自动执行,例如

package main

import (
    "database/sql"
    "fmt"
    _ "github.com/go-sql-driver/mysql"
    "gorm.io/driver/mysql"
    "gorm.io/gorm"
    "log"
)

type User struct {
    Id   int64          `gorm:"column:id"`
    Name sql.NullString `gorm:"column:userName"`
    Sex  int            `gorm:"column:sex"`
}

func main() {

    dsn := "root:123456@tcp(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=True&loc=Local"
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
       fmt.Println("连接数据库错误:", err)
       return
    }

    err3 := db.Transaction(func(tx *gorm.DB) error {
       user := User{
          Name: sql.NullString{
             "aaa",
             true,
          },
          Sex: 1,
       }

       err1 := tx.Model(&User{}).Table("user").Create(&user).Error
       if err1 != nil {
          return err1
       }
       log.Println("数据为:", user)

       user1 := User{

          Id: 70,
       }

       err2 := tx.Model(&User{}).Table("user").Delete(&user1).Error
       if err2 != nil {
          return err2
       }

       return nil
    })
    log.Println("错误信息为:", err3)

}

我们只要在事务方法中,返回Error就行,gorm会自动回滚事务

备注: 在操作数据库中,要用tx,否则事务不生效

2、有时候要在函数中执行数据,我们也可以手动回滚数据,如创建一条70数据,

image.png 执行以下代码

package main

import (
    "database/sql"
    "fmt"
    _ "github.com/go-sql-driver/mysql"
    "gorm.io/driver/mysql"
    "gorm.io/gorm"
    "log"
)

type User struct {
    Id   int64          `gorm:"column:id"`
    Name sql.NullString `gorm:"column:userName"`
    Sex  int            `gorm:"column:sex"`
}

func main() {

    dsn := "root:123456@tcp(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=True&loc=Local"
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
       fmt.Println("连接数据库错误:", err)
       return
    }

    err2 := db.Transaction(func(tx *gorm.DB) error {
       defer func() {
          if err := recover(); err != nil {
             tx.Rollback()
          }
       }()
       user := User{
          Name: sql.NullString{
             "aaa",
             true,
          },
          Sex: 1,
       }

       err1 := tx.Model(&User{}).Table("user").Create(&user).Error
       if err1 != nil {
          return err1
       }
       log.Println("数据为:", user)

       user1 := User{

          Id: 70,
       }

       result1 := tx.Model(&User{}).Table("user").Delete(&user1).Error
       if result1 != nil {
          return result1
       }

       var (
          a = 1
          b = 0
       )
       c := a / b
       log.Println("数据为:", c)
       return nil
    })
    log.Println("错误信息为:", err2)

}

结果为

image.png 可以看到,事务进行回滚了

image.png

手动开启事务

gorm框架也提供手动开启事务方式

package main

import (
    "database/sql"
    "fmt"
    _ "github.com/go-sql-driver/mysql"
    "gorm.io/driver/mysql"
    "gorm.io/gorm"
    "log"
)

type User struct {
    Id   int64          `gorm:"column:id"`
    Name sql.NullString `gorm:"column:userName"`
    Sex  int            `gorm:"column:sex"`
}

func main() {

    dsn := "root:123456@tcp(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=True&loc=Local"
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
       fmt.Println("连接数据库错误:", err)
       return
    }
    say(db)
    log.Println("===============执行结束")

}

func say(db *gorm.DB) {
    tx := db.Begin()

    defer func() {
       if err := recover(); err != nil {
          log.Println("异常信息为:", err)
          tx.Rollback()
       }
    }()

    user := User{
       Name: sql.NullString{
          "aaa",
          true,
       },
       Sex: 1,
    }

    result := tx.Model(&User{}).Table("user").Create(&user)

    log.Println("数据为:", user)
    log.Println("数据为:", result)

    user1 := User{

       Id: 74,
    }

    result1 := tx.Model(&User{}).Table("user").Delete(&user1)
    log.Println("数据为:", result1)

    tx.Commit()
    var (
       a = 1
       b = 0
    )
    c := a / b
    log.Println("数据为:", c)
    log.Println("===============执行结束")
    tx.Commit()
}

总结

合理使用事务,能帮住保持数据一致性