All skills
gogf avatar

/goframe-v2

@911705d
by GoFramegogf/skills81 stars
3

GoFrame v2 development skill. Use only when the target Go project uses or is explicitly adopting GoFrame v2: the nearest go.mod requires github.com/gogf/gf/v2, existing Go files import github.com/gogf/gf/v2 or any github.com/gogf/gf/v2/... component package, or the user asks to scaffold, migrate, or build with GoFrame. Trigger for GoFrame-backed Go work such as APIs/controllers/services, middleware, routing/config, ORM/DAO/DO/entity/database operations, gf CLI/codegen, HTTP/gRPC services, and microservice conventions. Do not trigger for generic Go projects without GoFrame evidence, frontend-only work, shell scripts, or unrelated infrastructure tasks.

Use this Skill: https://skilld.dev/gh/gogf/skills/goframe-v2

This session only. Nothing lands on disk.

references核心组件数据库ORMORM事务处理ORM事务处理-事务传播.md

事务传播(Transaction Propagation)是GoFrame ORM框架提供的一种高级事务管理机制,用于控制嵌套事务的行为。在复杂的业务场景中,一个事务方法可能会调用其他事务方法,此时需要明确定义事务的传播行为,以确保数据的一致性和完整性。

基本介绍

事务传播定义了事务方法被另一个事务方法调用时应该如何表现。GoFrame ORM支持多种事务传播行为,开发者可以根据业务需求选择合适的传播类型。

GoFrame ORM支持以下事务传播类型:

  • PropagationNested (默认):如果当前存在事务,则创建嵌套事务(使用保存点);如果不存在事务,则创建新事务。
  • PropagationRequired:如果当前存在事务,则加入该事务;如果不存在事务,则创建新事务。
  • PropagationSupports:如果当前存在事务,则加入该事务;如果不存在事务,则以非事务方式执行。
  • PropagationRequiresNew:创建新事务,如果当前存在事务,则挂起当前事务。
  • PropagationNotSupported:以非事务方式执行,如果当前存在事务,则挂起当前事务。
  • PropagationMandatory:如果当前存在事务,则加入该事务;如果不存在事务,则抛出异常。
  • PropagationNever:以非事务方式执行,如果当前存在事务,则抛出异常。

:::info 注意事项:为保证和旧版本事务处理的兼容性,GoFrame ORM默认的事务传播类型为PropagationNested,而不是PropagationRequired。 :::

使用优势

  1. 灵活的事务管理:根据业务需求选择不同的事务传播行为,实现更精细的事务控制。
  2. 提高代码复用性:服务层方法可以独立定义事务行为,无需关心调用环境是否已有事务。
  3. 降低事务冲突:通过适当的传播行为,减少长事务导致的锁定和冲突问题。
  4. 增强错误隔离:使用独立事务(如PropagationRequiresNew)可以隔离错误影响,避免整体事务回滚。
  5. 简化复杂业务逻辑:在复杂业务流程中,不同步骤可以使用不同的事务传播策略,使代码更清晰。

应用场景

  1. 服务组合:当一个服务方法调用多个其他服务方法时,可以根据业务需求选择是共享同一事务还是使用独立事务。
  2. 日志记录:业务操作需要记录日志,但日志写入失败不应影响主要业务逻辑,可以使用PropagationRequiresNew。
  3. 批量处理:在批量处理中,可能希望每个项目使用独立事务,以便某个项目失败不影响其他项目。
  4. 事务补偿:在分布式系统中,可以使用不同的传播行为实现事务补偿机制。
  5. 嵌套业务逻辑:复杂业务逻辑中的某些步骤可能需要作为一个独立的事务单元,可以使用PropagationNested。

使用示例

在开始示例之前,我们先创建一个用于测试的数据表:

CREATE TABLE IF NOT EXISTS `user` (
    id INT PRIMARY KEY,
    username VARCHAR(50)
);

:::tip 如果您想逐步运行以下示例,您需要在每次运行新示例之前清空一下user表数据。 :::

PropagationNested (嵌套事务,默认)

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

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"
)

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 (保证事务)

事务传播类型PropagationRequired与PropagationNested的主要区别在于,PropagationRequired在当前存在事务时加入该事务, 而在没有事务时创建新事务,并不会创建嵌套事务。

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"
)

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 (创建新事务)

创建新事务,如果当前存在事务,则挂起当前事务。

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"
)

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 (支持当前事务)

如果当前存在事务,则加入该事务;如果不存在事务,则以非事务方式执行。

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"
)

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 (强制使用事务)

如果当前存在事务,则加入该事务;如果不存在事务,则抛出异常。

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"
)

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 (不允许在事务中执行)

以非事务方式执行,如果当前存在事务,则抛出异常。

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"
)

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 (非事务方式执行)

以非事务方式执行,如果当前存在事务,则挂起当前事务。

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"
)

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;

总结

GoFrame ORM的事务传播功能为开发者提供了灵活的事务管理机制,能够满足复杂业务场景下的事务处理需求。通过合理选择事务传播行为,可以实现更精细的事务控制,提高系统的可靠性和性能。

在实际应用中,建议根据业务特点选择合适的传播行为:

  1. 对于大多数场景,默认的PropagationRequired已经能够满足需求。
  2. 当需要独立事务以隔离错误影响时,可以使用PropagationRequiresNew。
  3. 对于需要保存点功能的场景,可以使用PropagationNested。
  4. 当某些操作不需要事务保护时,可以使用PropagationNotSupported。

通过合理使用事务传播机制,可以构建更加健壮和可维护的应用程序。

Source: SKILL.md on GitHub

1 alert16d5 checks · Risk MEDIUM
  • Gen Agent Trust Hub16d

    The skill provides a comprehensive development guide and code examples for the GoFrame v2 framework. While highly informative for Go developers, it contains several examples with hardcoded placeholder secrets, tokens, and specific local file paths to sensitive configuration files (like Kubernetes credentials). These elements are provided for demonstration purposes but represent insecure practices if used in production without modification.

  • Socket16d

    5 alerts: gptSecurity, gptAnomaly

  • Snyk16d

    Risk: LOW · No issues

  • Runlayer6mo

    227/949 files flagged

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at 911705d. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 4 months ago.

Steadyupdated 4 months ago
  • Go
  • Database
  • goframe
  • orm
  • http
  • grpc
  • microservices
  • crud
  • scaffolding
  • middleware

README badge

README badge for gogf/skills

Provides instruction and conventions for developing with GoFrame v2, a modular Go framework for building HTTP and gRPC services, microservices, and database operations using its ORM, CLI tooling, and project scaffolding. Use this skill when the target project has GoFrame v2 as a dependency or when the user requests scaffolding, migrations, or service development with GoFrame conventions.

Generated from the current SKILL.md.

When should I use this skill?
Use this skill only when your Go project uses GoFrame v2, indicated by github.com/gogf/gf/v2 in go.mod or imports. Do not use for generic Go projects, frontend work, or infrastructure tasks unrelated to GoFrame.
Should I manually set created_at and updated_at fields?
No. GoFrame automatically writes created_at on insert and updated_at on insert/update/save. Manually setting these fields is redundant and violates project conventions.
What should I use for database operations instead of g.Map?
Always use DO objects from internal/model/do/. DO struct fields are interface{}, unset fields remain nil and are automatically ignored by the ORM.
How does soft delete work in GoFrame?
Call Delete() on the DAO; GoFrame automatically converts it to UPDATE SET deleted_at = NOW(). Queries automatically filter out soft-deleted rows without requiring manual WhereNull conditions.
Where should I implement business logic?
Implement business logic directly in the service/ directory. Do not use the logic/ directory unless explicitly requested.

Generated from the current SKILL.md. These answers refresh after source changes.