golang的巢狀事務管理

Alber發表於2018-06-18

golang 的事務管理是一件很麻煩的事,,能不能像 Java 那樣,通過 Spring 管理事務,最近琢磨了一下,寫了一個 demo,用來管理 golang 的事務,使其支援 golang 事務的巢狀呼叫。

其思想很簡單,對於所有的寫資料庫操作,用一個標記來標記事務的開啟和關閉 下面是一個演示示例:

我只是寫了一個簡單 demo,這裡貼出實現程式碼:

package session

import (
    "database/sql"
)

const beginStatus = 1

// SessionFactory 會話工廠
type SessionFactory struct {
    *sql.DB
}

// Session 會話
type Session struct {
    db           *sql.DB // 原生db
    tx           *sql.Tx // 原生事務
    commitSign   int8    // 提交標記,控制是否提交事務
    rollbackSign bool    // 回滾標記,控制是否回滾事務
}

// NewSessionFactory 建立一個會話工廠
func NewSessionFactory(driverName, dataSourseName string) (*SessionFactory, error) {
    db, err := sql.Open(driverName, dataSourseName)
    if err != nil {
        panic(err)
    }
    factory := new(SessionFactory)
    factory.DB = db
    return factory, nil
}

// GetSession 獲取一個Session
func (sf *SessionFactory) GetSession() *Session {
    session := new(Session)
    session.db = sf.DB
    return session
}

// Begin 開啟事務
func (s *Session) Begin() error {
    s.rollbackSign = true
    if s.tx == nil {
        tx, err := s.db.Begin()
        if err != nil {
            return err
        }
        s.tx = tx
        s.commitSign = beginStatus
        return nil
    }
    s.commitSign++
    return nil
}

// Rollback 回滾事務
func (s *Session) Rollback() error {
    if s.tx != nil && s.rollbackSign == true {
        err := s.tx.Rollback()
        if err != nil {
            return err
        }
        s.tx = nil
        return nil
    }
    return nil
}

// Commit 提交事務
func (s *Session) Commit() error {
    s.rollbackSign = false
    if s.tx != nil {
        if s.commitSign == beginStatus {
            err := s.tx.Commit()
            if err != nil {
                return err
            }
            s.tx = nil
            return nil
        } else {
            s.commitSign--
        }
        return nil
    }
    return nil
}

// Exec 執行sql語句,如果已經開啟事務,就以事務方式執行,如果沒有開啟事務,就以非事務方式執行
func (s *Session) Exec(query string, args ...interface{}) (sql.Result, error) {
    if s.tx != nil {
        return s.tx.Exec(query, args...)
    }
    return s.db.Exec(query, args...)
}

// QueryRow 如果已經開啟事務,就以事務方式執行,如果沒有開啟事務,就以非事務方式執行
func (s *Session) QueryRow(query string, args ...interface{}) *sql.Row {
    if s.tx != nil {
        return s.tx.QueryRow(query, args...)
    }
    return s.db.QueryRow(query, args...)
}

// Query 查詢資料,如果已經開啟事務,就以事務方式執行,如果沒有開啟事務,就以非事務方式執行
func (s *Session) Query(query string, args ...interface{}) (*sql.Rows, error) {
    if s.tx != nil {
        return s.tx.Query(query, args...)
    }
    return s.db.Query(query, args...)
}

// Prepare 預執行,如果已經開啟事務,就以事務方式執行,如果沒有開啟事務,就以非事務方式執行
func (s *Session) Prepare(query string) (*sql.Stmt, error) {
    if s.tx != nil {
        return s.tx.Prepare(query)
    }
    return s.db.Prepare(query)
}

測試用例:

package session

import (
    _ "github.com/go-sql-driver/mysql"
    "testing"
    "fmt"
)

var sf *SessionFactory

func init() {
    var err error
    sf, err = NewSessionFactory("mysql", "root:Liu123456@tcp(localhost:3306)/test?charset=utf8")
    if err != nil {
        fmt.Println(err)
        panic(err)
    }
}

type User struct {
    mobile string
    name   string
    age    int
    sex    int
}

type UserService struct {
    session *Session
}

func NewUserService() *UserService {
    return &UserService{sf.GetSession()}
}

func (s *UserService) Insert(user User) error {
    _, err := s.session.Exec("insert into user(mobile,name,age,sex) values(?,?,?,?)",
        user.mobile, user.name, user.age, user.sex)
    return err
}

func (s *UserService) AddInTx(user1, user2 User) error {
    err := s.session.Begin()
    if err != nil {
        return err
    }
    defer s.session.Rollback()

    err = s.Insert(user1)
    if err != nil {
        fmt.Println(err)
        return err
    }

    // return errors.New("err") 回滾測試

    err = s.Insert(user2)
    if err != nil {
        fmt.Println(err)
        return err
    }

    s.session.Commit()
    return nil
}

// DoNestingTx 巢狀事務
func (s *UserService) DoNestingTx() {
    err := s.session.Begin()
    if err != nil {
        fmt.Println(err)
    }
    defer s.session.Rollback()

    err = s.Insert(User{mobile: "1", name: "1", age: 1, sex: 1})
    if err != nil {
        fmt.Println(err)
        return
    }

    err = s.AddInTx(User{mobile: "1", name: "1", age: 1, sex: 1}, User{mobile: "1", name: "1", age: 1, sex: 1})
    if err != nil {
        fmt.Println(err)
        return
    }

    err = s.session.Commit()
    if err != nil {
        fmt.Println(err)
        return
    }
}

// TestDoNestingTx 測試巢狀事務
func TestDoNestingTx(t *testing.T) {
    userService := NewUserService()
    userService.DoNestingTx()
}

GitHub:https://github.com/alberliu/session

更多原創文章乾貨分享,請關注公眾號
  • golang的巢狀事務管理
  • 加微信實戰群請加微信(註明:實戰群):gocnio

相關文章