# 配置文件

  • 不同数据类型对应的 link 及驱动如下:
mysql
# mysql:root:12345678@tcp(127.0.0.1:3306)/test?loc=Local&parseTime=true
# import _ "github.com/gogf/gf/contrib/drivers/mysql/v2"

mariadb
# mariadb:root:12345678@tcp(127.0.0.1:3306)/test?loc=Local&parseTime=true
# import _ "github.com/gogf/gf/contrib/drivers/mariadb/v2"

tidb
# tidb:root:12345678@tcp(127.0.0.1:3306)/test?loc=Local&parseTime=true
# import _ "github.com/gogf/gf/contrib/drivers/tidb/v2"

pgsql
# pgsql:root:12345678@tcp(127.0.0.1:5432)/test
# import _ "github.com/gogf/gf/contrib/drivers/pgsql/v2"

mssql
# mssql:root:12345678@tcp(127.0.0.1:1433)/test?encrypt=disable
# import _ "github.com/gogf/gf/contrib/drivers/mssql/v2"

sqlite
# sqlite::@file(/var/data/db.sqlite3)
# import _ "github.com/gogf/gf/contrib/drivers/sqlite/v2"

# oracle
# oracle:root:12345678@tcp(127.0.0.1:5432)/test
# import _ "github.com/gogf/gf/contrib/drivers/oracle/v2"

clickhouse
# clickhouse:root:12345678@tcp(127.0.0.1:9000)/test
# import _ "github.com/gogf/gf/contrib/drivers/clickhouse/v2"

dm
# dm:root:12345678@tcp(127.0.0.1:5236)/test
# import _ "github.com/gogf/gf/contrib/drivers/dm/v2"

gaussdb
# gaussdb:root:12345678@tcp(127.0.0.1:5432)/test
# import _ "github.com/gogf/gf/contrib/drivers/gaussdb/v2"
  • 时区处理:建议在配置中统一加上 locl 配置,例如: loc=Local&parseTime=true
database:
  logger:
    level:  "all"
    stdout: true
  default:
    link:  "mysql:root:12345678@tcp(192.168.1.10:3306)/mydb?loc=Local&parseTime=true"
    debug: true
  order:
    link:  "mysql:root:12345678@tcp(192.168.1.20:3306)/order?loc=Local&parseTime=true"
    debug: true
  • 完整的 config.yaml 数据库配置项的数据格式形如下:
database:
  default:                      # 分组名称,可自定义,默认为default
    host: "127.0.0.1"           # 地址
    port: "3306"                # 端口
    user: "root"                # 账号
    pass: "your_password"       # 密码
    name: "your_database"       # 数据库名称
    type: "mysql"               # 数据库类型(如:mariadb/tidb/mysql/pgsql/mssql/sqlite/oracle/clickhouse/dm)
    link: ""                    # (可选)自定义数据库链接信息,当该字段被设置值时,以上链接字段(Host,Port,User,Pass,Name,Type)将失效
    extra: ""                   # (可选)不同数据库的额外特性配置,由底层数据库driver定义,具体有哪些配置请查看具体的数据库driver介绍
    role: "master"              # (可选)数据库主从角色(master/slave),默认为master。如果不使用应用主从机制请不配置或留空即可。
    debug: false                # (可选)开启调试模式
    prefix: "gf_"               # (可选)表名前缀
    dryRun: false               # (可选)ORM空跑(只读不写)
    charset: "utf8"             # (可选)数据库编码(如: utf8mb4/utf8/gbk/gb2312),一般设置为utf8mb4。默认为utf8。
    protocol: "tcp"             # (可选)数据库连接协议,默认为TCP
    weight: 100                 # (可选)负载均衡权重,用于负载均衡控制,不使用应用层的负载均衡机制请置空
    timezone: "Local"           # (可选)时区配置,例如:Local
    namespace: ""               # (可选)用以支持个别数据库服务Catalog&Schema区分的问题,原有的Schema代表数据库名称,而NameSpace代表个别数据库服务的Schema
    maxIdle: 10                 # (可选)连接池最大闲置的连接数(默认10)
    maxOpen: 100                # (可选)连接池最大打开的连接数(默认无限制)
    maxLifetime: "30s"          # (可选)连接对象可重复使用的时间长度(默认30秒)
    maxIdleConnTime: "30s"      # (可选,v2.10新增)连接池中空闲连接的最大生存时间(默认30秒)。可以通过配置文件或SetConnMaxIdleTime方法设置,避免长时间空闲连接占用资源。
    queryTimeout: "0"           # (可选)查询语句超时时长(默认无限制,同时受ctx超时时间影响)。值为time.Parse支持的格式,如30s, 1m。
    execTimeout: "0"            # (可选)写入语句超时时长(默认无限制,同时受ctx超时时间影响)。值为time.Parse支持的格式,如30s, 1m。
    tranTimeout: "0"            # (可选)事务处理超时时长(默认无限制,同时受ctx超时时间影响)。值为time.Parse支持的格式,如30s, 1m。
    prepareTimeout: "0"         # (可选)预准备SQL语句执行超时时长(默认无限制,同时受ctx超时时间影响)。值为time.Parse支持的格式,如30s, 1m。
    createdAt: "created_at"     # (可选)自动创建时间字段名称
    updatedAt: "updated_at"     # (可选)自动更新时间字段名称
    deletedAt: "deleted_at"     # (可选)软删除时间字段名称
    timeMaintainDisabled: false # (可选)是否完全关闭时间更新特性,为true时CreatedAt/UpdatedAt/DeletedAt都将失效
  • gdb 的配置支持集群模式,数据库配置中每一项分组配置均可以是多个节点,支持负载均衡权重策略,例如:
# 可以通过 g.DB() 和 g.DB("user") 获取对应的数据库连接对象
database:
  # default 分组包含一主一从
  default: 
  - link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test"
    role: "master"
  - link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test"
    role: "slave"
	
  # user 分组包含一主两从
  user:
  - link: "mysql:root:12345678@tcp(127.0.0.1:3306)/user"
    role: "master"
  - link: "mysql:root:12345678@tcp(127.0.0.1:3306)/user"
    role: "slave"
  - link: "mysql:root:12345678@tcp(127.0.0.1:3306)/user"
    role: "slave"
  • GetAllConfig 方法用于获取所有数据库配置信息,方便进行配置验证、配置导出等业务操作
import (
    "fmt"
    "github.com/gogf/gf/v2/database/gdb"
)

func main() {
    // 获取所有数据库配置
    allConfig := gdb.GetAllConfig()
    
    // 遍历所有配置分组
    for groupName, configGroup := range allConfig {
        fmt.Printf("数据库分组: %s\n", groupName)
        
        // 遍历分组中的所有节点
        for i, node := range configGroup {
            fmt.Printf("  节点 %d:\n", i+1)
            fmt.Printf("    Host: %s\n", node.Host)
            fmt.Printf("    Port: %s\n", node.Port)
            fmt.Printf("    Name: %s\n", node.Name)
            fmt.Printf("    Type: %s\n", node.Type)
            fmt.Printf("    Role: %s\n", node.Role)
        }
    }
}

# 增删改查

ORM的增删改查操作 (opens new window)

# 事务处理

# 常规操作

常规的事务操作方法为 Begin/Commit/Rollback

db := g.DB()

// db.Begin 开启事务
if tx, err := db.Begin(ctx); err == nil {
    r, err := tx.Save("user", g.Map{
        "id"   :  1,
        "name" : "john",
    })
	if err != nil {
		# 事务回滚
		tx.Rollback()
	}
    if err == nil {
	   # 事务提交
       tx.Commit()
    }
    fmt.Println(r)
}

# 闭包操作

通过常规的事务方法来管理事务有一些问题:冗余代码较多、操作风险较大等,为方便安全执行事务操作, ORM 组件同样提供了事务的闭包操作,通过 Transaction 方法实现,该方法定义如下:

// 当给定的闭包方法返回的 error 为 nil 时,那么闭包执行结束后当前事务自动执行 Commit 提交操作
// 否则自动执行 Rollback 回滚操作
// 如果闭包内部操作产生 panic 中断,该事务也将自动进行回滚,以保证操作安全
func (db DB) Transaction(ctx context.Context, f func(ctx context.Context, tx TX) error) (err error)
g.DB().Transaction(context.TODO(), func(ctx context.Context, tx gdb.TX) error {
    result, err := tx.Ctx(ctx).Insert("user", g.Map{
        "passport": "john",
        "password": "12345678",
        "nickname": "JohnGuo",
    })
    if err != nil {
        return err
    }
    id, err := result.LastInsertId()
    if err != nil {
        return err
    }
    _, err = tx.Ctx(ctx).Insert("user_detail", g.Map{
        "uid":       id,
        "site":      "https://johng.cn",
        "true_name": "GuoQiang",
    })
    if err != nil {
        return err
    }
    return nil
})

# 事务传播

在复杂的业务场景中,一个事务方法可能会调用其他事务方法,此时需要明确定义事务的传播行为,以确保数据的一致性和完整性。 事务传播定义了事务方法被另一个事务方法调用时应该如何表现。
GoFrame ORM支持以下事务传播类型:

PropagationNested (嵌套事务,默认)
# 如果当前存在事务,则创建嵌套事务(使用保存点);如果不存在事务,则创建新事务

PropagationRequired (保证事务)
# 在当前存在事务时加入该事务,在没有事务时创建新事务,并不会创建嵌套事务

PropagationRequiresNew (创建新事务)
# 创建新事务,如果当前存在事务,则挂起当前事务

PropagationSupports  (支持当前事务)
# 如果当前存在事务,则加入该事务;如果不存在事务,则以非事务方式执行

PropagationMandatory (强制使用事务)
# 如果当前存在事务,则加入该事务;如果不存在事务,则抛出异常

PropagationNever (不允许在事务中执行)
# 以非事务方式执行,如果当前存在事务,则抛出异常

PropagationNotSupported
# 以非事务方式执行,如果当前存在事务,则挂起当前事务
  • PropagationNested (嵌套事务,默认)
import (
	_ "github.com/gogf/gf/contrib/drivers/mysql/v2"

	"context"
	"fmt"

	"github.com/gogf/gf/v2/database/gdb"
	"github.com/gogf/gf/v2/frame/g"
)

func main() {
	var (
		ctx = context.Background()
		db  = g.DB()
	)
	db.SetDebug(true)

	// 执行事务
	err := db.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
		// 在外层事务中插入数据
		_, err := tx.Insert("user", g.Map{
			"id":       1,
			"username": "outer_user",
		})
		if err != nil {
			return err
		}

		// 嵌套事务 - 使用PropagationNested创建嵌套事务(使用保存点)
		err = tx.Transaction(ctx, func(ctx context.Context, tx2 gdb.TX) error {
			// 在嵌套事务中插入数据
			_, err = tx2.Insert("user", g.Map{
				"id":       2,
				"username": "nested_user",
			})
			if err != nil {
				return err
			}

			// 模拟错误,导致嵌套事务回滚到保存点
			return fmt.Errorf("嵌套事务故意失败")
		})

		// 嵌套事务失败,但外层事务可以继续
		fmt.Println("嵌套事务错误:", err)

		// 继续在外层事务中插入数据
		_, err = tx.Insert("user", g.Map{
			"id":       3,
			"username": "outer_after_nested",
		})
		// 外层事务正常提交
		return nil
	})

	if err != nil {
		fmt.Println("事务执行失败:", err)
		return
	}

	// 查询结果
	result, err := db.Model("user").All()
	if err != nil {
		fmt.Println("查询失败:", err)
		return
	}

	// 数据中只有id为1和3的数据,没有2
	fmt.Println("查询结果:", result)
}

// 上述代码执行的SQL语句类似于:
-- 开始外层事务
BEGIN;
-- 插入外层数据
INSERT INTO user(id,username) VALUES(1,'outer_user');

-- 创建保存点
SAVEPOINT sp1;
-- 插入嵌套事务数据
INSERT INTO user(id,username) VALUES(2,'nested_user');
-- 嵌套事务失败,回滚到保存点
ROLLBACK TO SAVEPOINT sp1;

-- 继续外层事务
INSERT INTO user(id,username) VALUES(3,'outer_after_nested');
-- 提交外层事务
COMMIT;
  • PropagationRequired (保证事务)
import (
	_ "github.com/gogf/gf/contrib/drivers/mysql/v2"

	"context"
	"fmt"

	"github.com/gogf/gf/v2/database/gdb"
	"github.com/gogf/gf/v2/frame/g"
)

func main() {
	var (
		ctx = context.Background()
		db  = g.DB()
	)
	db.SetDebug(true)

	// 执行事务
	err := db.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
		// 在外层事务中插入数据
		// 在事务闭包中使用tx对象或者db对象来操作数据表都是等价的
		_, err := tx.Insert("user", g.Map{
			"id":       1,
			"username": "outer_user",
		})
		if err != nil {
			return err
		}

		// 嵌套事务 - 默认使用PropagationRequired
		err = tx.TransactionWithOptions(ctx, gdb.TxOptions{
			Propagation: gdb.PropagationRequired,
		}, func(ctx context.Context, tx2 gdb.TX) error {
			// 在嵌套事务中插入数据(使用相同的事务)
			// 在事务闭包中使用tx2对象或者db对象来操作数据表都是等价的
			_, err = tx2.Insert("user", g.Map{
				"id":       2,
				"username": "inner_user",
			})
			return err
		})

		return err
	})
	if err != nil {
		fmt.Println("事务执行失败:", err)
		return
	}

	// 查询结果
	result, err := db.Model("user").All()
	if err != nil {
		fmt.Println("查询失败:", err)
		return
	}

	fmt.Println("查询结果:", result)
}

// 上述代码执行的SQL语句类似于
-- 开始外层事务
BEGIN;
-- 插入外层数据
INSERT INTO user(id,username) VALUES(1,'outer_user');
-- 插入内层数据(使用相同事务)
INSERT INTO user(id,username) VALUES(2,'inner_user');
-- 提交事务
COMMIT;
  • PropagationRequiresNew (创建新事务)
import (
	_ "github.com/gogf/gf/contrib/drivers/mysql/v2"

	"context"
	"fmt"

	"github.com/gogf/gf/v2/database/gdb"
	"github.com/gogf/gf/v2/frame/g"
)

func main() {
	var (
		ctx = context.Background()
		db  = g.DB()
	)
	db.SetDebug(true)

	// 执行事务
	err := db.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
		// 在外层事务中插入数据
		_, err := tx.Insert("user", g.Map{
			"id":       1,
			"username": "outer_user",
		})
		if err != nil {
			return err
		}

		// 嵌套事务 - 使用PropagationRequiresNew创建新事务
		err = tx.TransactionWithOptions(ctx, gdb.TxOptions{
			Propagation: gdb.PropagationRequiresNew,
		}, func(ctx context.Context, tx2 gdb.TX) error {
			// 在新事务中插入数据
			_, err = tx2.Insert("user", g.Map{
				"id":       2,
				"username": "new_tx_user",
			})
			// 模拟错误,导致内层事务回滚
			return fmt.Errorf("内层事务故意失败")
		})

		// 内层事务失败不影响外层事务
		fmt.Println("内层事务错误:", err)

		// 继续在外层事务中插入数据
		_, err = tx.Insert("user", g.Map{
			"id":       3,
			"username": "outer_after_error",
		})
		// 外层事务正常提交
		return nil
	})

	if err != nil {
		fmt.Println("事务执行失败:", err)
		return
	}

	// 查询结果
	result, err := db.Model("user").All()
	if err != nil {
		fmt.Println("查询失败:", err)
		return
	}

	fmt.Println("查询结果:", result)
}
// 上述代码执行的SQL语句类似于:
-- 开始外层事务
BEGIN;
-- 插入外层数据
INSERT INTO user(id,username) VALUES(1,'outer_user');

-- 开始新的独立事务
BEGIN;
-- 插入内层数据(使用新事务)
INSERT INTO user(id,username) VALUES(2,'new_tx_user');
-- 内层事务回滚
ROLLBACK;

-- 继续外层事务
INSERT INTO user(id,username) VALUES(3,'outer_after_error');
-- 提交外层事务
COMMIT;
  • PropagationSupports (支持当前事务)
import (
	_ "github.com/gogf/gf/contrib/drivers/mysql/v2"

	"context"
	"fmt"

	"github.com/gogf/gf/v2/database/gdb"
	"github.com/gogf/gf/v2/frame/g"
)

func main() {
	var (
		ctx = context.Background()
		db  = g.DB()
	)
	db.SetDebug(true)

	// 场景1: 有外部事务时,加入外部事务
	err := db.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
		// 在外层事务中插入数据
		_, err := tx.Insert("user", g.Map{
			"id":       1,
			"username": "outer_user",
		})
		if err != nil {
			return err
		}

		// 嵌套事务 - 使用PropagationSupports
		err = tx.TransactionWithOptions(ctx, gdb.TxOptions{
			Propagation: gdb.PropagationSupports,
		}, func(ctx context.Context, tx2 gdb.TX) error {
			// 在支持当前事务的方式下插入数据(使用外层事务)
			_, err = tx2.Insert("user", g.Map{
				"id":       2,
				"username": "supports_user",
			})
			return err
		})

		return err
	})
	if err != nil {
		fmt.Println("场景1执行失败:", err)
		return
	}

	// 查询结果
	result, err := db.Model("user").All()
	if err != nil {
		fmt.Println("查询失败:", err)
		return
	}

	fmt.Println("场景1查询结果:", result)

	// 清空数据表
	_, err = db.Exec(ctx, `TRUNCATE TABLE user`)
	if err != nil {
		fmt.Println("执行失败:", err)
		return
	}

	// 场景2: 没有外部事务时,非事务方式执行
	err = db.TransactionWithOptions(ctx, gdb.TxOptions{
		Propagation: gdb.PropagationSupports,
	}, func(ctx context.Context, tx gdb.TX) error {
		// 以非事务方式插入数据
		_, err = tx.Insert("user", g.Map{
			"id":       3,
			"username": "non_tx_user",
		})
		return err
	})
	if err != nil {
		fmt.Println("场景2执行失败:", err)
		return
	}

	// 查询结果
	result, err = db.Model("user").All()
	if err != nil {
		fmt.Println("查询失败:", err)
		return
	}

	fmt.Println("场景2查询结果:", result)
}

// 上述代码执行的SQL语句类似于:
-- 场景1: 有外部事务
-- 开始外层事务
BEGIN;
-- 插入外层数据
INSERT INTO user(id,username) VALUES(1,'outer_user');
-- 插入内层数据(使用外层事务)
INSERT INTO user(id,username) VALUES(2,'supports_user');
-- 提交事务
COMMIT;

-- 场景2: 没有外部事务
-- 非事务方式直接插入数据
INSERT INTO user(id,username) VALUES(3,'non_tx_user');
  • PropagationMandatory (强制使用事务)
import (
    _ "github.com/gogf/gf/contrib/drivers/mysql/v2"

    "context"
    "fmt"

    "github.com/gogf/gf/v2/database/gdb"
    "github.com/gogf/gf/v2/frame/g"
)

func main() {
    var (
        ctx = context.Background()
        db  = g.DB()
    )
    db.SetDebug(true)

    // 场景1: 有外部事务时,加入外部事务
    err := db.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
        // 在外层事务中插入数据
        _, err := tx.Insert("user", g.Map{
            "id":       1,
            "username": "outer_user",
        })
        if err != nil {
            return err
        }

        // 嵌套事务 - 使用PropagationMandatory
        err = tx.TransactionWithOptions(ctx, gdb.TxOptions{
            Propagation: gdb.PropagationMandatory,
        }, func(ctx context.Context, tx2 gdb.TX) error {
            // 在强制使用当前事务的方式下插入数据(使用外层事务)
            _, err = tx2.Insert("user", g.Map{
                "id":       2,
                "username": "mandatory_user",
            })
            return err
        })

        return err
    })

    if err != nil {
        fmt.Println("场景1执行失败:", err)
        return
    }

    // 查询结果
    result, err := db.Model("user").All()
    if err != nil {
        fmt.Println("查询失败:", err)
        return
    }

    fmt.Println("场景1查询结果:", result)

    // 清空数据表
    _, err = db.Exec(ctx, `TRUNCATE TABLE user`)
    if err != nil {
        fmt.Println("执行失败:", err)
        return
    }

    // 场景2: 没有外部事务时,将抛出异常
    fmt.Println("场景2: 没有外部事务时使用PropagationMandatory")
    err = db.TransactionWithOptions(ctx, gdb.TxOptions{
        Propagation: gdb.PropagationMandatory,
    }, func(ctx context.Context, tx gdb.TX) error {
        // 这里的代码不会执行,因为没有外部事务时会抛出异常
        _, err = tx.Insert("user", g.Map{
            "id":       3,
            "username": "will_not_insert",
        })
        return err
    })

    // 应该有错误,因为没有外部事务
    fmt.Println("场景2错误:", err)
}

// 上述代码执行的SQL语句类似于:
-- 场景1: 有外部事务
-- 开始外层事务
BEGIN;
-- 插入外层数据
INSERT INTO user(id,username) VALUES(1,'outer_user');
-- 插入内层数据(使用外层事务)
INSERT INTO user(id,username) VALUES(2,'mandatory_user');
-- 提交事务
COMMIT;

-- 场景2: 没有外部事务
-- 抛出异常: "mandatory transaction is required, but none exists"
  • PropagationNever (不允许在事务中执行)
import (
	_ "github.com/gogf/gf/contrib/drivers/mysql/v2"

	"context"
	"fmt"

	"github.com/gogf/gf/v2/database/gdb"
	"github.com/gogf/gf/v2/frame/g"
)

func main() {
	var (
		ctx = context.Background()
		db  = g.DB()
	)
	db.SetDebug(true)

	// 场景1: 有外部事务时,将抛出异常
	fmt.Println("场景1: 有外部事务时使用PropagationNever")
	err := db.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
		// 在外层事务中插入数据
		_, err := tx.Insert("user", g.Map{
			"id":       1,
			"username": "outer_user",
		})
		if err != nil {
			return err
		}

		// 嵌套事务 - 使用PropagationNever
		err = tx.TransactionWithOptions(ctx, gdb.TxOptions{
			Propagation: gdb.PropagationNever,
		}, func(ctx context.Context, tx2 gdb.TX) error {
			// 这里的代码不会执行,因为在外部事务中使用PropagationNever会抛出异常
			_, err := tx2.Insert("user", g.Map{
				"id":       2,
				"username": "will_not_insert",
			})
			return err
		})

		// 应该有错误
		fmt.Println("嵌套事务错误:", err)

		// 继续外层事务
		return nil
	})
	if err != nil {
		fmt.Println("场景1执行失败:", err)
		return
	}

	// 查询结果
	result, err := db.Model("user").All()
	if err != nil {
		fmt.Println("查询失败:", err)
		return
	}
	// 应该只有id=1的记录
	fmt.Println("场景1查询结果:", result)

	// 清空数据表
	_, err = db.Exec(ctx, `TRUNCATE TABLE user`)
	if err != nil {
		fmt.Println("执行失败:", err)
		return
	}

	// 场景2: 没有外部事务时,非事务方式执行
	err = db.TransactionWithOptions(ctx, gdb.TxOptions{
		Propagation: gdb.PropagationNever,
	}, func(ctx context.Context, tx gdb.TX) error {
		// 以非事务方式插入数据
		_, err = tx.Insert("user", g.Map{
			"id":       3,
			"username": "non_tx_user",
		})
		return err
	})
	if err != nil {
		fmt.Println("场景2执行失败:", err)
		return
	}

	// 查询结果
	result, err = db.Model("user").All()
	if err != nil {
		fmt.Println("查询失败:", err)
		return
	}
	// 应该有id=3的记录
	fmt.Println("场景2查询结果:", result)
}

// 上述代码执行的SQL语句类似于:
-- 场景1: 有外部事务
-- 开始外层事务
BEGIN;
-- 插入外层数据
INSERT INTO user(id,username) VALUES(1,'outer_user');
-- 抛出异常: "transaction is existing, but never transaction is required"
-- 提交外层事务
COMMIT;

-- 场景2: 没有外部事务
-- 非事务方式直接插入数据
INSERT INTO user(id,username) VALUES(3,'non_tx_user');
  • PropagationNotSupported (非事务方式执行)
import (
	_ "github.com/gogf/gf/contrib/drivers/mysql/v2"

	"context"
	"fmt"

	"github.com/gogf/gf/v2/database/gdb"
	"github.com/gogf/gf/v2/frame/g"
)

func main() {
	var (
		ctx = context.Background()
		db  = g.DB()
	)
	db.SetDebug(true)

	// 执行事务
	err := db.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
		// 在事务中插入数据
		_, err := tx.Insert("user", g.Map{
			"id":       1,
			"username": "tx_user",
		})
		if err != nil {
			return err
		}

		// 使用PropagationNotSupported挂起当前事务
		err = tx.TransactionWithOptions(ctx, gdb.TxOptions{
			Propagation: gdb.PropagationNotSupported,
		}, func(ctx context.Context, tx2 gdb.TX) error {
			// 非事务方式写入数据
			_, err = tx2.Insert("user", g.Map{
				"id":       2,
				"username": "non_tx_user",
			})
			return err
		})
		if err != nil {
			return err
		}

		// 模拟错误,导致外层事务回滚
		return fmt.Errorf("外层事务故意失败")
	})

	fmt.Println("事务执行结果:", err)

	// 查询结果
	result, err := db.Model("user").All()
	if err != nil {
		fmt.Println("查询失败:", err)
		return
	}

	// 应该只看到id=2的记录,因为id=1的记录在事务回滚时被撤销
	fmt.Println("查询结果:", result)
}

// 上述代码执行的SQL语句类似于:
-- 开始事务
BEGIN;
-- 插入事务内数据
INSERT INTO user(id,username) VALUES(1,'tx_user');

-- 非事务方式执行(直接提交)
INSERT INTO user(id,username) VALUES(2,'non_tx_user');

-- 外层事务回滚
ROLLBACK;

# 隔离级别

  • 事务隔离级别主要解决并发事务执行时可能出现的以下问题:
脏读(Dirty Read)# 一个事务读取了另一个未提交事务修改过的数据
不可重复读(Non-repeatable Read)# 在同一事务内,多次读取同一数据返回的结果有所不同
幻读(Phantom Read)# 在同一事务内,多次执行同一查询返回不同的数据集(行数变化)
  • 不同的隔离级别适用于不同的应用场景:
读未提交(Read Uncommitted)
# 性能最好,没有锁开销,并发度最高
# 对数据一致性要求极低的场景

读已提交(Read Committed)
# 避免脏读问题,较好的并发性能,大多数数据库的默认级别
# 需要避免脏读但可以接受不可重复读的场景

可重复读(Repeatable Read)
# 避免脏读和不可重复读问题,MySQL 的默认隔离级别
# 对数据一致性要求较高的业务逻辑,财务类应用

串行化(Serializable)
# 完全避免并发问题,提供最高级别的数据一致性保证
# 对数据一致性要求极高的关键业务,银行转账等金融核心交易
  • 读已提交(Read Committed)
// 使用读已提交隔离级别
tx, err := db.BeginWithOptions(ctx, gdb.TxOptions{
    Isolation: sql.LevelReadCommitted,
})
if err != nil {
    return
}
defer tx.Rollback()

// 查询用户余额
balance, err := tx.Model("account").Where("user_id", 1).Value("balance")
// SQL: SELECT balance FROM account WHERE user_id=1

// 更新余额
_, err = tx.Model("account").Where("user_id", 1).Update(g.Map{"balance": balance.Int() + 100})
// SQL: UPDATE account SET balance=balance+100 WHERE user_id=1

if err = tx.Commit(); err != nil {
    return
}
  • 可重复读(Repeatable Read)
// 使用可重复读隔离级别(MySQL默认)
tx, err := db.BeginWithOptions(ctx, gdb.TxOptions{
    Isolation: sql.LevelRepeatableRead,
})
if err != nil {
    return
}
defer tx.Rollback()

// 第一次查询用户数据
user1, err := tx.Model("user").Where("id", 1).One()
// SQL: SELECT * FROM user WHERE id=1

// ... 其他操作

// 第二次查询相同用户数据,在可重复读级别下,即使其他事务修改了该数据,这里读取的结果仍与第一次相同
user2, err := tx.Model("user").Where("id", 1).One()
// SQL: SELECT * FROM user WHERE id=1

if err = tx.Commit(); err != nil {
    return
}
  • 串行化(Serializable)
// 使用串行化隔离级别
tx, err := db.BeginWithOptions(ctx, gdb.TxOptions{
    Isolation: sql.LevelSerializable,
})
if err != nil {
    return
}
defer tx.Rollback()

// 查询满足条件的所有用户
users, err := tx.Model("user").Where("status", "active").All()
// SQL: SELECT * FROM user WHERE status='active'

// 在串行化级别下,其他事务无法同时修改或添加符合此条件的记录
// 这确保了在事务执行期间,查询结果的一致性

if err = tx.Commit(); err != nil {
    return
}
  • 使用事务闭包函数,可以同时指定隔离级别
// 使用可重复读隔离级别处理订单
err := db.TransactionWithOptions(ctx, gdb.TxOptions{
    Isolation: sql.LevelRepeatableRead,
}, func(ctx context.Context, tx gdb.TX) error {
    // 1. 查询商品库存
    stock, err := tx.Model("product").Where("id", productId).Value("stock")
    // SQL: SELECT stock FROM product WHERE id=?
    
    if err != nil {
        return err
    }
    
    // 2. 检查库存是否充足
    if stock.Int() < quantity {
        return errors.New("库存不足")
    }
    
    // 3. 创建订单
    orderId, err := tx.Model("order").InsertAndGetId(g.Map{
        "user_id": userId,
        "product_id": productId,
        "quantity": quantity,
        "status": "pending",
    })
    // SQL: INSERT INTO order(user_id,product_id,quantity,status) VALUES(?,?,?,'pending')
    
    if err != nil {
        return err
    }
    
    // 4. 减少库存
    _, err = tx.Model("product").Where("id", productId).
        Update(g.Map{"stock": gdb.Raw("stock - ?", quantity)})
    // SQL: UPDATE product SET stock=stock-? WHERE id=?
    
    return err
})

# 结果处理

  • 结果类型
type Value  = *gvar.Var          # 返回数据表记录值
type Record   map[string]Value   # 返回数据表记录键值对
type Result   []Record           # 返回数据表记录列表
  • 为空判断
// 数据集合(多条)
r, err := g.Model("order").Where("status", 1).All()
if err != nil {
    return err
}
if len(r) == 0 {
    // 结果为空
}
if r.IsEmpty() {
    // 结果为空
}

// 数据记录(单条)
r, err := g.Model("order").Where("status", 1).One()
if err != nil {
    return err
}
if len(r) == 0 {
    // 结果为空
}
if r.IsEmpty() {
    // 结果为空
}

// 数据字段值,返回的是一个"泛型"变量,这个只能使用 IsEmpty 来判断是否为空了
r, err := g.Model("order").Where("status", 1).Value()
if err != nil {
    return err
}
if r.IsEmpty() {
    // 结果为空
}

// 字段值数组,返回字段值数组本身类型为 []gdb.Value 类型,因此直接判断长度是否为 0 即可
r, err := g.Model("order").Fields("id").Where("status", 1).Array()
if err != nil {
    return err
}
if len(r) == 0 {
    // 结果为空
}

// Struct 对象
// 传递的对象是一个空指针时
var user *User
err := g.Model("order").Where("status", 1).Scan(&user)
if err != nil {
    return err
}
if user == nil {
    // 结果为空
}
// 传递的对象是一个初始化的对象
var user = new(User)
err := g.Model("order").Where("status", 1).Scan(&user)
if err != nil && err != sql.ErrNoRows {
    return err
}
if err == sql.ErrNoRows {
    // 结果为空
}

// Struct 数组
// 当传递的对象数组本身是一个空数组
var users []*User
err := g.Model("order").Where("status", 1).Scan(&users)
if err != nil {
    return err
}
if len(users) == 0 {
    // 结果为空
}
// 当传递的对象数组本身不是空数组
var users = make([]*User, 100)
err := g.Model("order").Where("status", 1).Scan(&users)
if err != nil {
    return err
}
if err == sql.ErrNoRows {
    // 结果为空
}
  • 空数组结构返回
// 大部分场景下,查询的数据需要展示在浏览器页面上,也就意味着返回的数据需要给前端 JS 进行处理
// 为了前端处理返回数据时更加友好,在后端查询不到数据时,期望返回一个空的数组结构,而不是返回 null
import (
    _ "github.com/gogf/gf/contrib/drivers/mysql/v2"

    "fmt"

    "github.com/gogf/gf/v2/encoding/gjson"
    "github.com/gogf/gf/v2/frame/g"
    "github.com/gogf/gf/v2/os/gtime"
)

func main() {
    type User struct {
        Id        uint64      // 主键
        Passport  string      // 账号
        Password  string      // 密码
        NickName  string      // 昵称
        CreatedAt *gtime.Time // 创建时间
        UpdatedAt *gtime.Time // 更新时间
    }
    type Response struct {
        Users []User
    }
    var res = &Response{
        Users: make([]User, 0),
    }
    err := g.Model("user").WhereGT("id", 10).Scan(&res.Users)
    fmt.Println(err)
    fmt.Println(gjson.MustEncodeString(res))
}

# 分库分表

# ORM分表(Table Sharding)

解决单表数据量过大的问题,通过将数据分散到不同的表,可以显著提高查询性能

import (
    _ "github.com/gogf/gf/contrib/drivers/mysql/v2"

    "github.com/gogf/gf/v2/database/gdb"
    "github.com/gogf/gf/v2/frame/g"
)

// User 用户结构体
type User struct {
    Id   int    `json:"id"`
    Name string `json:"name"`
}

func main() {
    // 创建分表配置
    shardingConfig := gdb.ShardingConfig{
        Table: gdb.ShardingTableConfig{
            Enable: true,    // 启用分表
            Prefix: "user_", // 分表前缀
            Rule: &gdb.DefaultShardingRule{
                TableCount: 4, // 分表数量
            },
        },
    }

    // 准备测试数据
    user := User{
        Id:   1,
        Name: "John",
    }

    // 创建分表模型
    db := g.DB()
    db.SetDebug(true)
    model := db.Model("user").
        Sharding(shardingConfig).
        ShardingValue(user.Id) // 使用用户ID作为分片值

    // 插入数据
    _, err := model.Data(user).Insert()
    if err != nil {
        panic(err)
    }
    // INSERT INTO `user_1`(`id`,`name`) VALUES(1,'John')

    // 查询数据
    var result User
    err = model.Where("id", user.Id).Scan(&result)
    if err != nil {
        panic(err)
    }
    // SELECT * FROM `user_1` WHERE `id`=1 LIMIT 1
    g.DumpJson(result)

    // 更新数据
    _, err = model.Data(g.Map{"name": "John Doe"}).
        Where("id", user.Id).
        Update()
    if err != nil {
        panic(err)
    }
    // UPDATE `user_1` SET `name`='John Doe' WHERE `id`=1

    // 删除数据
    _, err = model.Where("id", user.Id).Delete()
    if err != nil {
        panic(err)
    }
    // DELETE FROM `user_1` WHERE `id`=1
}
  • 自定义分表规则,可以通过实现ShardingRule接口来自定义分表规则:
package main

import (
    _ "github.com/gogf/gf/contrib/drivers/mysql/v2"

    "context"
    "fmt"
    "time"

    "github.com/gogf/gf/v2/database/gdb"
    "github.com/gogf/gf/v2/frame/g"
    "github.com/gogf/gf/v2/os/gtime"
)

// TimeShardingRule 按时间分表的规则
type TimeShardingRule struct{}

// TableName 实现按月份分表的规则
func (r *TimeShardingRule) TableName(ctx context.Context, config gdb.ShardingTableConfig, value any) (string, error) {
    // 将分片值转换为时间
    t, ok := value.(time.Time)
    if !ok {
        return "", fmt.Errorf("sharding value must be time.Time for TimeShardingRule")
    }

    // 按年月生成表名,例如: log_202501
    return fmt.Sprintf("%s%04d%02d", config.Prefix, t.Year(), t.Month()), nil
}

// SchemaName 实现分库规则接口
func (r *TimeShardingRule) SchemaName(ctx context.Context, config gdb.ShardingSchemaConfig, value any) (string, error) {
    // 这里不实现分库,返回空字符串
    return "", nil
}

func main() {
    // 创建按时间分表的配置
    shardingConfig := gdb.ShardingConfig{
        Table: gdb.ShardingTableConfig{
            Enable: true,                // 启用分表
            Prefix: "log_",              // 分表前缀
            Rule:   &TimeShardingRule{}, // 自定义分表规则
        },
    }

    // 当前时间作为分片值
    now := gtime.Now().Time

    // 创建分表模型
    db := g.DB()
    db.SetDebug(true)
    model := db.Model("log").
        Sharding(shardingConfig).
        ShardingValue(now) // 使用时间作为分片值

    // 插入日志数据
    _, err := model.Data(g.Map{
        "content": "系统启动",
        "level":   "info",
        "time":    now,
    }).Insert()
    if err != nil {
        panic(err)
    }
    // INSERT INTO `log_202503`(`content`,`level`,`time`) 
	// VALUES('系统启动','info','2025-03-13 12:02:54')
}

# ORM分库(Schema Sharding)

是实现数据库水平扩展的重要手段,通过将数据分散到不同的数据库节点,可以显著提高系统的处理能力

import (
    _ "github.com/gogf/gf/contrib/drivers/mysql/v2"

    "github.com/gogf/gf/v2/database/gdb"
    "github.com/gogf/gf/v2/frame/g"
)

// User 用户结构体
type User struct {
    Id   int    `json:"id"`
    Name string `json:"name"`
}

func main() {
    // 创建分库配置
    shardingConfig := gdb.ShardingConfig{
        Schema: gdb.ShardingSchemaConfig{
            Enable: true,  // 启用分库
            Prefix: "db_", // 分库前缀
            Rule: &gdb.DefaultShardingRule{
                SchemaCount: 2, // 分库数量
            },
        },
    }

    // 准备测试数据
    user := User{
        Id:   1,
        Name: "John",
    }

    // 创建分库模型
    db := g.DB()
    db.SetDebug(true)
    model := db.Model("user").
        Sharding(shardingConfig).
        ShardingValue(user.Id) // 使用用户ID作为分片值

    // 插入数据
    _, err := model.Data(user).Insert()
    if err != nil {
        panic(err)
    }
    // INSERT INTO `user`(`id`,`name`) VALUES(1,'John')
    // 注意:实际操作的是 db_1 数据库中的 user 表

    // 查询数据
    var result User
    err = model.Where("id", user.Id).Scan(&result)
    if err != nil {
        panic(err)
    }
    // SELECT * FROM `user` WHERE `id`=1 LIMIT 1
    // 注意:实际查询的是 db_1 数据库中的 user 表
    g.DumpJson(result)

    // 更新数据
    _, err = model.Data(g.Map{"name": "John Doe"}).
        Where("id", user.Id).
        Update()
    if err != nil {
        panic(err)
    }
    // UPDATE `user` SET `name`='John Doe' WHERE `id`=1
    // 注意:实际更新的是 db_1 数据库中的 user 表

    // 删除数据
    _, err = model.Where("id", user.Id).Delete()
    if err != nil {
        panic(err)
    }
    // DELETE FROM `user` WHERE `id`=1
    // 注意:实际删除的是 db_1 数据库中的 user 表
}
  • 自定义分库规则,可以通过实现ShardingRule接口来自定义分库规则:
package main

import (
    _ "github.com/gogf/gf/contrib/drivers/mysql/v2"

    "context"
    "fmt"

    "github.com/gogf/gf/v2/database/gdb"
    "github.com/gogf/gf/v2/frame/g"
)

// RegionShardingRule 按地区分库的规则
type RegionShardingRule struct {
    // 地区到数据库的映射
    RegionMapping map[string]string
}

// SchemaName 实现按地区分库的规则
func (r *RegionShardingRule) SchemaName(ctx context.Context, config gdb.ShardingSchemaConfig, value any) (string, error) {
    // 将分片值转换为地区信息
    region, ok := value.(string)
    if !ok {
        return "", fmt.Errorf("sharding value must be string for RegionShardingRule")
    }

    // 获取地区对应的数据库名
    if dbName, exists := r.RegionMapping[region]; exists {
        return dbName, nil
    }

    // 如果没有找到对应的地区,使用默认数据库
    return config.Prefix + "default", nil
}

// TableName 实现分表规则接口
func (r *RegionShardingRule) TableName(ctx context.Context, config gdb.ShardingTableConfig, value any) (string, error) {
    // 这里不实现分表,返回空字符串
    return "", nil
}

func main() {
    // 创建地区到数据库的映射
    regionMapping := map[string]string{
        "east":  "db_east",
        "west":  "db_west",
        "north": "db_north",
        "south": "db_south",
    }

    // 创建按地区分库的配置
    shardingConfig := gdb.ShardingConfig{
        Schema: gdb.ShardingSchemaConfig{
            Enable: true,                                              // 启用分库
            Prefix: "db_",                                             // 分库前缀
            Rule:   &RegionShardingRule{RegionMapping: regionMapping}, // 自定义分库规则
        },
    }

    // 分片值为用户所在地区
    region := "east"

    // 创建分库模型
    db := g.DB()
    db.SetDebug(true)
    model := g.DB().Model("user").
        Sharding(shardingConfig).
        ShardingValue(region) // 使用地区作为分片值

    // 插入用户数据
    _, err := model.Data(g.Map{
        "id":     1001,
        "name":   "John",
        "region": region,
    }).Insert()
    if err != nil {
        panic(err)
    }
    // INSERT INTO `user`(`id`,`name`,`region`) VALUES(1001,'John','east')
    // 注意:实际操作的是 db_east 数据库中的 user 表
}

# 结合分库分表

GoFrame ORM支持同时配置分库和分表,实现更精细的数据分片:

// 同时配置分库和分表
shardingConfig := gdb.ShardingConfig{
    Schema: gdb.ShardingSchemaConfig{
        Enable: true,     // 启用分库
        Prefix: "db_",    // 分库前缀
        Rule: &gdb.DefaultShardingRule{
            SchemaCount: 2, // 分库数量
        },
    },
    Table: gdb.ShardingTableConfig{
        Enable: true,     // 启用分表
        Prefix: "user_",  // 分表前缀
        Rule: &gdb.DefaultShardingRule{
            TableCount: 4, // 分表数量
        },
    },
}

// 使用分库分表配置
model := g.DB().Model("user").
    Sharding(shardingConfig).
    ShardingValue(10001)  // 同一个分片值用于计算分库和分表