orm.go - 資料庫操作

概述

orm.go 提供資料庫操作工具,用於在 Go 結構體和資料庫值之間進行便捷的資料轉換。它實現了 driver.Valuersql.Scanner 介面以實現無縫資料庫集成。

函數

Scan()

將資料庫結果掃描到結構體中。

func Scan(src interface{}, dst interface{}) error

參數:

  • src - 源資料([]byte 或 string)
  • dst - 目標結構體指針

返回值:

  • 如果掃描失敗,返回錯誤

支援的格式:

  • JSON 對象:{...}
  • JSON 陣列:[...]
  • 空值:應用結構體標籤中的預設值

示例:

type User struct {
    Name  string `json:"name" default:"Anonymous"`
    Email string `json:"email"`
    Age   int    `json:"age" default:"0"`
}

var user User
err := utils.Scan(dbResult, &user)
if err != nil {
    log.Errorf("掃描使用者失敗: %v", err)
}

注意:

  • 自動從源檢測 JSON 格式
  • 當源為空時,應用結構體標籤中的預設值
  • 在掃描過程中記錄錯誤
  • 支援 []byte 和 string 輸入

Value()

將結構體轉換為資料庫值。

func Value(m interface{}) (value driver.Value, err error)

參數:

  • m - 結構體或結構體指針

返回值:

  • value - 資料庫值([]byte)
  • err - 如果轉換失敗則返回錯誤

行為:

  • 如果輸入為 nil,返回 []byte("null")
  • 在轉換前為非 nil 結構體應用預設值
  • 將結構體轉換為 JSON 格式

示例:

type User struct {
    Name  string `json:"name" default:"John"`
    Email string `json:"email" default:"john@example.com"`
}

user := User{Name: "Jane", Email: "jane@example.com"}
value, err := utils.Value(&user)
if err != nil {
    log.Errorf("轉換使用者失敗: %v", err)
}
// value 是 []byte(`{"name":"Jane","email":"jane@example.com"}`)

注意:

  • 實現 driver.Valuer 介面以實現資料庫相容性
  • 在轉換前自動應用預設值
  • 返回 JSON 編碼的位元組
  • 優雅地處理 nil 值

使用模式

資料庫模型定義

type User struct {
    ID        int64     `json:"id" default:"0"`
    Name      string    `json:"name" default:""`
    Email     string    `json:"email" default:""`
    Age       int       `json:"age" default:"0"`
    CreatedAt time.Time `json:"created_at"`
    UpdatedAt time.Time `json:"updated_at"`
}

func (u *User) Scan(value interface{}) error {
    return utils.Scan(value, u)
}

func (u *User) Value() (driver.Value, error) {
    return utils.Value(u)
}

CRUD 操作

// 建立資料
func CreateUser(db *sql.DB, user *User) error {
    value := utils.Must(utils.Value(user))
    _, err := db.Exec(
        "INSERT INTO users (data) VALUES (?)",
        value,
    )
    return err
}

// 讀取資料
func GetUser(db *sql.DB, id int64) (*User, error) {
    var data []byte
    err := db.QueryRow(
        "SELECT data FROM users WHERE id = ?",
        id,
    ).Scan(&data)
    if err != nil {
        return nil, err
    }
    
    var user User
    err = utils.Scan(data, &user)
    return &user, err
}

// 更新資料
func UpdateUser(db *sql.DB, user *User) error {
    value := utils.Must(utils.Value(user))
    _, err := db.Exec(
        "UPDATE users SET data = ? WHERE id = ?",
        value,
        user.ID,
    )
    return err
}

// 刪除資料
func DeleteUser(db *sql.DB, id int64) error {
    _, err := db.Exec(
        "DELETE FROM users WHERE id = ?",
        id,
    )
    return err
}

與 GORM 集成

type User struct {
    gorm.Model
    Profile Profile `gorm:"type:json"`
}

type Profile struct {
    Name  string `json:"name" default:""`
    Email string `json:"email" default:""`
}

// Scan 實現 sql.Scanner 介面
func (p *Profile) Scan(value interface{}) error {
    return utils.Scan(value, p)
}

// Value 實現 driver.Valuer 介面
func (p Profile) Value() (driver.Value, error) {
    return utils.Value(p)
}

批量操作

func BatchInsertUsers(db *sql.DB, users []*User) error {
    tx, err := db.Begin()
    if err != nil {
        return err
    }
    defer tx.Rollback()
    
    stmt, err := tx.Prepare(
        "INSERT INTO users (data) VALUES (?)",
    )
    if err != nil {
        return err
    }
    defer stmt.Close()
    
    for _, user := range users {
        value := utils.Must(utils.Value(user))
        _, err := stmt.Exec(value)
        if err != nil {
            return err
        }
    }
    
    return tx.Commit()
}

最佳實踐

預設值

type Config struct {
    Host     string `json:"host" default:"localhost"`
    Port     int    `json:"port" default:"8080"`
    Debug    bool   `json:"debug" default:"false"`
    Timeout  int    `json:"timeout" default:"30"`
}

// 掃描空資料時,應用預設值
var cfg Config
utils.Scan([]byte{}, &cfg)
// cfg.Host == "localhost"
// cfg.Port == 8080
// cfg.Debug == false
// cfg.Timeout == 30

錯誤處理

func SafeScan(data []byte, dst interface{}) error {
    if err := utils.Scan(data, dst); err != nil {
        // 記錄錯誤以便除錯
        log.Errorf("掃描失敗: %v, 資料: %s", err, string(data))
        
        // 返回錯誤或優雅處理
        return fmt.Errorf("掃描資料失敗: %w", err)
    }
    return nil
}

效能考慮

// 好:重用結構體進行掃描
var user User
rows, _ := db.Query("SELECT data FROM users")
for rows.Next() {
    // 重用同一個結構體
    rows.Scan(&data)
    utils.Scan(data, &user)
    // 處理使用者
}

// 避免:在循環中建立新結構體
for rows.Next() {
    var user User  // 每次迭代建立新結構體
    rows.Scan(&data)
    utils.Scan(data, &user)
}

集成示例

與 SQL 資料庫

db, err := sql.Open("postgres", connStr)
if err != nil {
    log.Fatal(err)
}
defer db.Close()

// 建立表
_, err = db.Exec(`
    CREATE TABLE IF NOT EXISTS users (
        id SERIAL PRIMARY KEY,
        data JSONB NOT NULL
    )
`)

// 插入使用者
user := &User{Name: "John", Email: "john@example.com"}
value := utils.Must(utils.Value(user))
_, err = db.Exec("INSERT INTO users (data) VALUES ($1)", value)

與 Redis

func SaveUserToRedis(client *redis.Client, key string, user *User) error {
    value := utils.Must(utils.Value(user))
    return client.Set(ctx, key, value, 0).Err()
}

func LoadUserFromRedis(client *redis.Client, key string) (*User, error) {
    data, err := client.Get(ctx, key).Bytes()
    if err != nil {
        return nil, err
    }
    
    var user User
    err = utils.Scan(data, &user)
    return &user, err
}

與 HTTP API

func CreateUserHandler(w http.ResponseWriter, r *http.Request) {
    var user User
    if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    
    value := utils.Must(utils.Value(user))
    if _, err := db.Exec("INSERT INTO users (data) VALUES (?)", value); err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    
    w.WriteHeader(http.StatusCreated)
}

func GetUserHandler(w http.ResponseWriter, r *http.Request) {
    id := r.URL.Query().Get("id")
    
    var data []byte
    if err := db.QueryRow("SELECT data FROM users WHERE id = ?", id).Scan(&data); err != nil {
        http.Error(w, err.Error(), http.StatusNotFound)
        return
    }
    
    var user User
    if err := utils.Scan(data, &user); err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    
    json.NewEncoder(w).Encode(user)
}

相關文檔