(本文内容基于 Bun ORM 官方文档 翻译整理)

1. 简介:什么是 Bun ORM?

Bun 是一个 SQL 优先的 Go 语言 ORM(对象关系映射)框架,支持 PostgreSQL、MySQL、MSSQL 和 SQLite。它旨在提供一种简单高效的方式来操作数据库,同时利用 Go 的类型安全特性并减少样板代码。

核心特性

  • 基于标准库构建:构建在 Go 标准 database/sql 包之上
  • 类型安全:提供类型安全的查询构建器,性能卓越
  • 复杂关系支持:支持复杂的关系和连接操作
  • 迁移支持:提供迁移和架构管理功能
  • 强大的扫描能力:全面的数据扫描功能
  • 钩子和中间件:支持钩子和中间件
  • 生产就绪:经过广泛测试,可用于生产环境

为什么选择 Bun?

Bun 通过 SQL 优先 的理念区别于其他 Go ORM,不试图对开发者隐藏 SQL。这种方法具有以下优势:

  • 可预测的查询:你确切知道生成的 SQL 是什么
  • 高性能:对原始 SQL 的开销最小
  • 渐进式采用:易于集成到现有代码库
  • 灵活性:需要时可降级到原始 SQL
  • 类型安全:大多数操作的编译时检查

2. 安装与配置

安装 Bun

要安装 Bun 和所需的数据库驱动:

# 核心 Bun 包
go get github.com/uptrace/bun@latest

# 数据库驱动(选择一个或多个)
go get github.com/uptrace/bun/driver/pgdriver        # PostgreSQL
go get github.com/uptrace/bun/driver/sqliteshim     # SQLite
go get github.com/go-sql-driver/mysql               # MySQL
go get github.com/denisenkom/go-mssqldb             # SQL Server

快速开始示例

package main

import (
    "context"
    "database/sql"
    "fmt"
    "log"

    "github.com/uptrace/bun"
    "github.com/uptrace/bun/dialect/sqlitedialect"
    "github.com/uptrace/bun/driver/sqliteshim"
    "github.com/uptrace/bun/extra/bundebug"
)

// User 模型
type User struct {
    bun.BaseModel `bun:"table:users,alias:u"`

    ID    int64  `bun:",pk,autoincrement"`
    Name  string `bun:",notnull"`
    Email string `bun:",unique"`
}

func main() {
    ctx := context.Background()

    // 打开数据库连接
    sqldb, err := sql.Open(sqliteshim.ShimName, "file::memory:?cache=shared")
    if err != nil {
        panic(err)
    }
    defer sqldb.Close()

    // 创建 Bun 数据库实例
    db := bun.NewDB(sqldb, sqlitedialect.New())

    // 添加查询调试(可选)
    db.AddQueryHook(bundebug.NewQueryHook(
        bundebug.WithVerbose(true),
    ))

    // 创建表
    _, err = db.NewCreateTable().Model((*User)(nil)).IfNotExists().Exec(ctx)
    if err != nil {
        panic(err)
    }

    // 插入用户
    user := &User{Name: "张三", Email: "zhangsan@example.com"}
    _, err = db.NewInsert().Model(user).Exec(ctx)
    if err != nil {
        panic(err)
    }

    // 查询用户
    var selectedUser User
    err = db.NewSelect().Model(&selectedUser).Where("email = ?", "zhangsan@example.com").Scan(ctx)
    if err != nil {
        panic(err)
    }

    fmt.Printf("用户: %+v\n", selectedUser)
}

3. 数据库连接配置

PostgreSQL 连接

import (
    "database/sql"
    "github.com/uptrace/bun"
    "github.com/uptrace/bun/dialect/pgdialect"
    "github.com/uptrace/bun/driver/pgdriver"
)

// 使用 pgdriver(推荐)
sqldb := sql.OpenDB(pgdriver.NewConnector(
    pgdriver.WithDSN("postgres://user:password@localhost:5432/dbname?sslmode=disable"),
))
db := bun.NewDB(sqldb, pgdialect.New())

// 或者使用 lib/pq
import _ "github.com/lib/pq"
sqldb, err := sql.Open("postgres", "postgres://user:password@localhost/dbname?sslmode=disable")
if err != nil {
    log.Fatal(err)
}
db := bun.NewDB(sqldb, pgdialect.New())

MySQL 连接

import (
    "database/sql"
    "github.com/uptrace/bun/dialect/mysqldialect"
    _ "github.com/go-sql-driver/mysql"
)

sqldb, err := sql.Open("mysql", "user:password@tcp(localhost:3306)/dbname?parseTime=true")
if err != nil {
    panic(err)
}
db := bun.NewDB(sqldb, mysqldialect.New())

SQLite 连接

import (
    "database/sql"
    "github.com/uptrace/bun/dialect/sqlitedialect"
    "github.com/uptrace/bun/driver/sqliteshim"
)

sqldb, err := sql.Open(sqliteshim.ShimName, "file:test.db?cache=shared&mode=rwc")
if err != nil {
    panic(err)
}
db := bun.NewDB(sqldb, sqlitedialect.New())

连接池配置

为了获得最佳性能,配置数据库连接池:

// 配置连接池
sqldb.SetMaxOpenConns(25)                 // 最大打开连接数
sqldb.SetMaxIdleConns(10)                 // 最大空闲连接数
sqldb.SetConnMaxLifetime(5 * time.Minute) // 连接生命周期
sqldb.SetConnMaxIdleTime(5 * time.Minute) // 空闲连接超时

// 测试连接
if err := sqldb.Ping(); err != nil {
    log.Fatal("连接数据库失败:", err)
}

4. 模型定义与结构体标签

基本模型结构

Bun 使用基于结构体的模型来构建查询和扫描结果。模型使用 Go 结构体和结构体标签定义数据库架构:

type User struct {
    bun.BaseModel `bun:"table:users,alias:u"`

    ID        int64     `bun:",pk,autoincrement"`
    Name      string    `bun:",notnull"`
    Email     string    `bun:",unique,notnull"`
    CreatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"`
    UpdatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"`
}

常用结构体标签

标签 描述 示例
pk 主键 bun:",pk"
autoincrement 自增字段 bun:",pk,autoincrement"
notnull NOT NULL 约束 bun:",notnull"
unique UNIQUE 约束 bun:",unique"
default:value 默认值 bun:",default:0"
type:varchar(100) 自定义列类型 bun:",type:varchar(100)"
nullzero 将零值视为 NULL bun:",nullzero"
- 忽略字段 bun:"-"

高级模型示例

// 包含 JSON 字段和自定义类型的用户
type User struct {
    bun.BaseModel `bun:"table:users"`

    ID       int64                  `bun:",pk,autoincrement"`
    Name     string                 `bun:",notnull"`
    Email    string                 `bun:",unique,notnull"`
    Settings map[string]interface{} `bun:",type:jsonb"` // PostgreSQL JSONB
    Status   UserStatus             `bun:",type:varchar(20),default:'active'"`

    CreatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"`
    UpdatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"`
    DeletedAt time.Time `bun:",soft_delete,nullzero"` // 软删除支持
}

type UserStatus string

const (
    UserStatusActive   UserStatus = "active"
    UserStatusInactive UserStatus = "inactive"
    UserStatusBanned   UserStatus = "banned"
)

// 带外键的用户资料
type Profile struct {
    bun.BaseModel `bun:"table:profiles"`

    ID     int64  `bun:",pk,autoincrement"`
    UserID int64  `bun:",notnull"`
    Bio    string
    Avatar string

    // 关系定义
    User *User `bun:"rel:belongs-to,join:user_id=id"`
}

5. CRUD 操作

插入操作

// 插入单个用户
user := &User{Name: "李四", Email: "lisi@example.com"}
_, err := db.NewInsert().Model(user).Exec(ctx)
// user.ID 现在已被填充

// 批量插入用户
users := []*User{
    {Name: "王五", Email: "wangwu@example.com"},
    {Name: "赵六", Email: "zhaoliu@example.com"},
}
_, err := db.NewInsert().Model(&users).Exec(ctx)

// 处理冲突的插入
_, err = db.NewInsert().
    Model(user).
    On("CONFLICT (email) DO UPDATE").
    Set("name = EXCLUDED.name").
    Exec(ctx)

// 插入并返回特定列
var ids []int64
_, err = db.NewInsert().
    Model(&users).
    Returning("id").
    Exec(ctx, &ids)

更新操作

// 根据主键更新
user := &User{ID: 1, Name: "更新的名字"}
_, err := db.NewUpdate().
    Model(user).
    Column("name").
    WherePK().
    Exec(ctx)

// 使用 WHERE 子句更新
_, err = db.NewUpdate().
    Model((*User)(nil)).
    Set("last_login = ?", time.Now()).
    Where("status = ?", "active").
    Exec(ctx)

// 使用子查询更新
_, err = db.NewUpdate().
    Model((*User)(nil)).
    Set("post_count = (SELECT COUNT(*) FROM posts WHERE user_id = users.id)").
    Exec(ctx)

// 批量更新(使用 CASE)
_, err = db.NewUpdate().
    Model((*User)(nil)).
    Set("status = CASE WHEN last_login < ? THEN 'inactive' ELSE 'active' END",
        time.Now().AddDate(0, -3, 0)).
    Exec(ctx)

删除操作

// 根据主键删除
user := &User{ID: 1}
_, err := db.NewDelete().
    Model(user).
    WherePK().
    Exec(ctx)

// 使用 WHERE 子句删除
_, err = db.NewDelete().
    Model((*User)(nil)).
    Where("created_at < ?", time.Now().AddDate(-1, 0, 0)).
    Exec(ctx)

// 软删除(需要 soft_delete 标签)
_, err = db.NewDelete().
    Model(user).
    WherePK().
    Exec(ctx) // 设置 deleted_at 时间戳

// 强制删除(绕过软删除)
_, err = db.NewDelete().
    Model(user).
    WherePK().
    ForceDelete().
    Exec(ctx)

查询操作

// 根据主键查询
user := new(User)
err := db.NewSelect().
    Model(user).
    Where("id = ?", 1).
    Scan(ctx)

// 查询多个用户
var users []User
err := db.NewSelect().
    Model(&users).
    Where("status = ?", "active").
    Order("created_at DESC").
    Limit(10).
    Scan(ctx)

// 复杂条件查询
err = db.NewSelect().
    Model(&users).
    Where("name ILIKE ?", "%张%").
    WhereOr("email ILIKE ?", "%admin%").
    WhereGroup(" AND ", func(q *bun.SelectQuery) *bun.SelectQuery {
        return q.Where("created_at > ?", time.Now().AddDate(0, -1, 0)).
               Where("status != ?", "banned")
    }).
    Scan(ctx)

// 查询特定列
var names []string
err = db.NewSelect().
    Model((*User)(nil)).
    Column("name").
    Where("status = ?", "active").
    Scan(ctx, &names)

// 计数
count, err := db.NewSelect().
    Model((*User)(nil)).
    Where("status = ?", "active").
    Count(ctx)

6. 高级查询与关系

Belongs-To 关系

type Post struct {
    bun.BaseModel `bun:"table:posts"`

    ID       int64  `bun:",pk,autoincrement"`
    Title    string `bun:",notnull"`
    Content  string
    AuthorID int64  `bun:",notnull"`

    // Belongs-to 关系
    Author *User `bun:"rel:belongs-to,join:author_id=id"`
}

// 查询时包含关系
var posts []Post
err := db.NewSelect().
    Model(&posts).
    Relation("Author").
    Where("post.status = ?", "published").
    Scan(ctx)

Has-One 关系

type User struct {
    bun.BaseModel `bun:"table:users"`

    ID   int64  `bun:",pk,autoincrement"`
    Name string `bun:",notnull"`

    // Has-one 关系
    Profile *Profile `bun:"rel:has-one,join:id=user_id"`
}

// 查询包含 has-one
var users []User
err := db.NewSelect().
    Model(&users).
    Relation("Profile").
    Scan(ctx)

Has-Many 关系

type User struct {
    bun.BaseModel `bun:"table:users"`

    ID   int64  `bun:",pk,autoincrement"`
    Name string

    // Has-many 关系
    Posts []Post `bun:"rel:has-many,join:id=author_id"`
}

// 查询包含 has-many
var users []User
err := db.NewSelect().
    Model(&users).
    Relation("Posts", func(q *bun.SelectQuery) *bun.SelectQuery {
        return q.Where("status = ?", "published").Order("created_at DESC")
    }).
    Scan(ctx)

Many-to-Many 关系

type User struct {
    bun.BaseModel `bun:"table:users"`

    ID   int64  `bun:",pk,autoincrement"`
    Name string

    // Many-to-many 关系
    Roles []Role `bun:"m2m:user_roles,join:User=Role"`
}

type Role struct {
    bun.BaseModel `bun:"table:roles"`

    ID   int64  `bun:",pk,autoincrement"`
    Name string `bun:",unique,notnull"`
}

type UserRole struct {
    bun.BaseModel `bun:"table:user_roles"`

    UserID int64 `bun:",pk"`
    RoleID int64 `bun:",pk"`
    User   *User `bun:"rel:belongs-to,join:user_id=id"`
    Role   *Role `bun:"rel:belongs-to,join:role_id=id"`
}

// 查询 many-to-many
var users []User
err := db.NewSelect().
    Model(&users).
    Relation("Roles").
    Scan(ctx)

子查询

// WHERE 中的子查询
subq := db.NewSelect().
    Model((*Post)(nil)).
    Column("author_id").
    Where("status = ?", "published").
    Group("author_id").
    Having("COUNT(*) > ?", 5)

var users []User
err := db.NewSelect().
    Model(&users).
    Where("id IN (?)", subq).
    Scan(ctx)

// SELECT 中的子查询
err = db.NewSelect().
    Model(&users).
    ColumnExpr("(SELECT COUNT(*) FROM posts WHERE author_id = users.id) as post_count").
    Scan(ctx)

窗口函数

// 带分区的行号
var results []struct {
    User     `bun:",embed"`
    RowNum   int `bun:"row_num"`
    PostRank int `bun:"post_rank"`
}

err := db.NewSelect().
    Model(&results).
    ColumnExpr("*, ROW_NUMBER() OVER (PARTITION BY status ORDER BY created_at) as row_num").
    ColumnExpr("RANK() OVER (ORDER BY post_count DESC) as post_rank").
    Scan(ctx)

公共表表达式(CTE)

// 递归 CTE
cte := db.NewSelect().
    With("RECURSIVE user_hierarchy", db.NewSelect().
        ColumnExpr("id, name, manager_id, 0 as level").
        Model((*User)(nil)).
        Where("manager_id IS NULL").
        UnionAll(
            db.NewSelect().
                ColumnExpr("u.id, u.name, u.manager_id, uh.level + 1").
                TableExpr("users u").
                Join("JOIN user_hierarchy uh ON u.manager_id = uh.id"),
        ),
    ).
    Table("user_hierarchy").
    Column("*")

var hierarchy []struct {
    ID        int64  `bun:"id"`
    Name      string `bun:"name"`
    ManagerID *int64 `bun:"manager_id"`
    Level     int    `bun:"level"`
}

err := cte.Scan(ctx, &hierarchy)

7. 错误处理与调试

常见错误模式

import (
    "database/sql"
    "errors"
)

// 检查无行错误
user := new(User)
err := db.NewSelect().Model(user).Where("id = ?", 999).Scan(ctx)
if err != nil {
    if errors.Is(err, sql.ErrNoRows) {
        // 处理未找到
        return fmt.Errorf("用户未找到")
    }
    return err
}

// 检查唯一约束冲突
_, err = db.NewInsert().Model(user).Exec(ctx)
if err != nil {
    if strings.Contains(err.Error(), "duplicate key") ||
       strings.Contains(err.Error(), "UNIQUE constraint") {
        return fmt.Errorf("用户已存在")
    }
    return err
}

查询调试

import "github.com/uptrace/bun/extra/bundebug"

// 添加调试钩子
db.AddQueryHook(bundebug.NewQueryHook(
    bundebug.WithVerbose(true),
    bundebug.FromEnv("BUNDEBUG"), // 使用 BUNDEBUG=1 启用
))

// 或创建自定义调试钩子
type DebugHook struct{}

func (h *DebugHook) BeforeQuery(ctx context.Context, event *bun.QueryEvent) context.Context {
    return ctx
}

func (h *DebugHook) AfterQuery(ctx context.Context, event *bun.QueryEvent) {
    fmt.Printf("查询: %s\n持续时间: %s\n", event.Query, event.Dur)
}

db.AddQueryHook(&DebugHook{})

8. 性能优化技巧

查询优化

// 有效使用索引
_, err := db.NewCreateIndex().
    Model((*User)(nil)).
    Index("idx_users_email_status").
    Column("email", "status").
    Exec(ctx)

// 适当使用 LIMIT
var users []User
err := db.NewSelect().
    Model(&users).
    Where("status = ?", "active").
    Order("created_at DESC").
    Limit(100). // 总是限制大查询
    Scan(ctx)

// 使用特定列而不是 *
var userSummaries []struct {
    ID   int64  `bun:"id"`
    Name string `bun:"name"`
}
err = db.NewSelect().
    Model((*User)(nil)).
    Column("id", "name"). // 只选择需要的列
    Scan(ctx, &userSummaries)

批量操作

// 批量插入(使用批次大小)
const batchSize = 1000
users := make([]*User, 10000) // 大切片

for i := 0; i < len(users); i += batchSize {
    end := i + batchSize
    if end > len(users) {
        end = len(users)
    }

    batch := users[i:end]
    _, err := db.NewInsert().Model(&batch).Exec(ctx)
    if err != nil {
        return err
    }
}

事务处理

// 简单事务
err := db.RunInTx(ctx, &sql.TxOptions{}, func(ctx context.Context, tx bun.Tx) error {
    user := &User{Name: "张三"}
    if _, err := tx.NewInsert().Model(user).Exec(ctx); err != nil {
        return err
    }

    profile := &Profile{UserID: user.ID, Bio: "你好"}
    if _, err := tx.NewInsert().Model(profile).Exec(ctx); err != nil {
        return err
    }

    return nil // 提交
})

// 手动事务控制
tx, err := db.BeginTx(ctx, &sql.TxOptions{})
if err != nil {
    return err
}
defer tx.Rollback()

// 使用 tx 代替 db 进行操作
_, err = tx.NewInsert().Model(user).Exec(ctx)
if err != nil {
    return err
}

return tx.Commit()

9. 测试策略

单元测试

import (
    "testing"
    "github.com/uptrace/bun"
    "github.com/uptrace/bun/dbfixture"
)

func TestUserOperations(t *testing.T) {
    // 设置测试数据库
    db := setupTestDB(t)

    // 加载测试数据
    fixture := dbfixture.New(db)
    if err := fixture.Load(ctx, "testdata/users.yml"); err != nil {
        t.Fatal(err)
    }

    // 测试操作
    var count int
    count, err := db.NewSelect().Model((*User)(nil)).Count(ctx)
    if err != nil {
        t.Fatal(err)
    }

    if count != 3 {
        t.Errorf("期望 3 个用户,得到 %d", count)
    }
}

测试数据(testdata/users.yml)

model: User
rows:
  - id: 1
    name: 张三
    email: zhangsan@example.com
  - id: 2
    name: 李四
    email: lisi@example.com
  - id: 3
    name: 王五
    email: wangwu@example.com

集成测试

func TestUserRepository(t *testing.T) {
    // 使用内存 SQLite 进行集成测试
    sqldb, err := sql.Open(sqliteshim.ShimName, ":memory:")
    if err != nil {
        t.Fatal(err)
    }
    defer sqldb.Close()

    db := bun.NewDB(sqldb, sqlitedialect.New())
    
    // 创建表
    _, err = db.NewCreateTable().Model((*User)(nil)).Exec(ctx)
    if err != nil {
        t.Fatal(err)
    }

    repo := NewUserRepository(db)
    
    // 测试创建
    user := &User{Name: "测试用户", Email: "test@example.com"}
    err = repo.Create(ctx, user)
    if err != nil {
        t.Fatal(err)
    }

    // 测试查询
    found, err := repo.GetByID(ctx, user.ID)
    if err != nil {
        t.Fatal(err)
    }

    if found.Name != user.Name {
        t.Errorf("期望名称 %s,得到 %s", user.Name, found.Name)
    }
}

10. 最佳实践与常见陷阱

✅ 推荐做法

  • 使用参数化查询:始终使用占位符(?)防止 SQL 注入
  • 使用事务:对必须一起成功或失败的操作使用事务
  • 添加适当索引:为频繁查询的列添加索引
  • 限制结果集:对可能返回大结果集的查询使用 LIMIT
  • 验证输入:在查询前验证和清理输入
  • 使用连接池:在生产环境中使用连接池
// ✅ 好的示例
err := db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
    // 使用事务确保数据一致性
    user := &User{Name: validatedName, Email: validatedEmail}
    if _, err := tx.NewInsert().Model(user).Exec(ctx); err != nil {
        return err
    }
    
    profile := &Profile{UserID: user.ID, Bio: validatedBio}
    if _, err := tx.NewInsert().Model(profile).Exec(ctx); err != nil {
        return err
    }
    
    return nil
})

❌ 避免的做法

  • 忽略错误:不要忽略数据库操作的错误
  • 字符串拼接:不要使用字符串拼接构建查询
  • 忘记关闭:不要忘记关闭数据库连接和事务
  • **SELECT ***:只需要特定列时不要使用 SELECT *
  • 循环中的数据库操作:不要在没有批处理的循环中执行数据库操作
// ❌ 错误示例
for _, user := range users {
    // 低效:在循环中逐个插入
    _, err := db.NewInsert().Model(&user).Exec(ctx)
    if err != nil {
        log.Println(err) // 错误被忽略了
    }
}

// ✅ 正确示例
// 高效:批量插入
_, err := db.NewInsert().Model(&users).Exec(ctx)
if err != nil {
    return fmt.Errorf("批量插入用户失败: %w", err)
}

常见问题解答

Q: 如何处理 NULL 值?

A: 使用指针类型或 sql.Null* 类型:

type User struct {
    ID    int64        `bun:",pk,autoincrement"`
    Name  string       `bun:",notnull"`
    Email *string      // 可为 NULL 的字符串
    Age   sql.NullInt64 // 替代方法
}

Q: Bun 与原始 SQL 相比的性能差异是什么?

A: Bun 对原始 SQL 的开销很小。在大多数情况下,性能差异可以忽略不计(< 5%),同时提供了类型安全和开发效率方面的显著优势。

Q: 如何处理复杂的 WHERE 条件?

A: 使用 WhereGroup 处理复杂逻辑:

err := db.NewSelect().
    Model(&users).
    Where("status = ?", "active").
    WhereGroup(" AND ", func(q *bun.SelectQuery) *bun.SelectQuery {
        return q.WhereOr("name LIKE ?", "%admin%").
                 WhereOr("email LIKE ?", "%admin%")
    }).
    Scan(ctx)

Q: 如何处理数据库迁移?

A: Bun 通过 bun/migrate 包提供迁移支持:

import "github.com/uptrace/bun/migrate"

migrations := migrate.NewMigrations()
migrations.MustRegister(func(ctx context.Context, db *bun.DB) error {
    // 迁移 up
    _, err := db.NewCreateTable().Model((*User)(nil)).Exec(ctx)
    return err
}, func(ctx context.Context, db *bun.DB) error {
    // 迁移 down
    _, err := db.NewDropTable().Model((*User)(nil)).Exec(ctx)
    return err
})

migrator := migrate.NewMigrator(db, migrations)
if err := migrator.Init(ctx); err != nil {
    return err
}

if err := migrator.Migrate(ctx); err != nil {
    return err
}

总结

Bun ORM 是一个强大而高效的 Go 语言数据库操作库,它通过 SQL 优先的设计理念,在提供类型安全和开发便利性的同时,保持了与原生 SQL 相当的性能表现。

核心优势

  1. 高性能:最小化开销,接近原生 SQL 性能
  2. 类型安全:编译时检查,减少运行时错误
  3. 灵活性强:可以随时降级到原始 SQL
  4. 渐进式采用:易于集成到现有项目
  5. 功能完整:支持复杂查询、关系、迁移等

适用场景

  • 需要高性能数据库操作的应用
  • 复杂的企业级应用
  • 需要类型安全的数据库操作
  • 微服务架构中的数据访问层
  • 现有 database/sql 项目的升级

通过本文的全面介绍,你应该已经掌握了 Bun ORM 的核心概念和实际应用方法。建议在实际项目中逐步应用这些技术,并根据具体需求进行相应的优化和调整。

延伸阅读

希望这篇指南能帮助你更好地理解和使用 Bun ORM!