结果集映射 ============================== 结果集映射是 Juice 框架中一个核心功能,它负责将数据库查询结果转换为 Go 语言中的结构化数据。Juice 提供了多种灵活的映射方式,从简单的单行映射到复杂的嵌套对象映射都能优雅处理。 原生SQL.Rows支持 ---------------- Juice 通过自己的轻量 ``Rows`` 接口兼容 ``*database/sql.Rows``: .. code-block:: go type Rows interface { Next() bool Scan(dest ...any) error Close() error Err() error Columns() ([]string, error) } 下文函数签名中的 ``sql.Rows`` 指的是 ``github.com/go-juicedev/juice/sql.Rows``。``*database/sql.Rows`` 实现了这个接口。 基本用法示例: .. code-block:: go rows, err := engine.Object("main.SelectUser").QueryContext(context.TODO(), nil) if err != nil { panic(err) } defer rows.Close() // 确保资源释放 for rows.Next() { var user User if err := rows.Scan(&user.Id, &user.Name, &user.Age); err != nil { panic(err) } fmt.Println(user) } if err = rows.Err(); err != nil { panic(err) } Object方法说明 """""""""""""" ``Object`` 方法支持多种参数类型: 1. **字符串类型**: .. code-block:: go engine.Object("main.SelectUser") 2. **函数类型**: 使用函数在代码中的位置作为ID .. note:: Object方法的ID生成规则: - 包名.函数名(普通函数) - 包名.类型名.方法名(结构体方法) - 注意区分interface和struct 泛型结果集映射 -------------- Juice提供了强大的泛型支持,使结果集映射更加类型安全: 映射场景示例 """""""""""" 1. **单字段单行**: .. code-block:: go // 查询单个计数 count, err := juice.NewGenericManager[int](engine). Object("CountUsers").QueryContext(context.TODO(), nil) 2. **多字段单行**: .. code-block:: go type User struct { ID int64 `column:"id"` Name string `column:"name"` } // 查询单个用户 user, err := juice.NewGenericManager[User](engine). Object("GetUser").QueryContext(context.TODO(), nil) 3. **单字段多行**: .. code-block:: go // 查询多个ID ids, err := juice.NewGenericManager[[]int64](engine). Object("GetUserIDs").QueryContext(context.TODO(), nil) 4. **多字段多行**: .. code-block:: go // 查询用户列表 users, err := juice.NewGenericManager[[]User](engine). Object("GetUsers").QueryContext(context.TODO(), nil) .. attention:: - 结构体必须使用 ``column`` 标签指定数据库字段映射 - 多行结果必须使用切片类型接收 - 不支持使用map接收结果(设计选择) 自定义结果集映射 ---------------- Juice 提供了四个核心的结果集映射函数:``Bind``、``List``、``List2`` 和 ``Iter``,它们各自适用于不同的场景。 Bind 函数 """"""""""" ``Bind`` 是最灵活的映射函数,可以处理任意类型的结果集映射: .. code-block:: go func Bind[T any](rows sql.Rows) (result T, err error) 特点: - 支持任意类型的映射(结构体、切片、基本类型等) - 灵活性最高 - 可以处理单行或多行数据 使用示例: .. code-block:: go type User struct { ID int `column:"id"` Name string `column:"name"` } rows, _ := db.Query("SELECT id, name FROM users") defer rows.Close() // 映射到切片 users, err := juice.Bind[[]User](rows) // 映射到单个结构体 user, err := juice.Bind[User](rows) List 函数 """"""""""" ``List`` 专门用于将结果集映射为切片类型: .. code-block:: go func List[T any](rows sql.Rows) (result []T, err error) 特点: - 始终返回切片类型 ``[]T`` - 性能优于 ``Bind`` (针对切片场景) - 空结果返回空切片而不是 nil - 对非指针类型做了特殊优化 使用示例: .. code-block:: go rows, _ := db.Query("SELECT id, name FROM users") defer rows.Close() users, err := juice.List[User](rows) List2 函数 """"""""""" ``List2`` 是 ``List`` 的变体,专门返回指针切片: .. code-block:: go func List2[T any](rows sql.Rows) ([]*T, error) List2 主要是为了做一些指针类型的泛型结果集的返回。 特点: - 返回指针切片 ``[]*T`` - 适合需要修改切片元素的场景 - 适合处理大型结构体 - 避免了值拷贝开销 使用示例: .. code-block:: go rows, _ := db.Query("SELECT id, name FROM users") defer rows.Close() users, err := juice.List2[User](rows) // users 类型为 []*User Iter 函数 """"""""""" ``Iter`` 专门用于将结果集转换为迭代器返回,避免一次性加载所有数据到内存中。 .. code-block:: go rows, _ := db.Query("SELECT id, name FROM users") defer rows.Close() iterator, err := juice.Iter[User](rows) if err != nil { return err } for user, err := range iterator { if err != nil { return err } fmt.Println(user) } ``Iter`` 返回 ``iter.Seq2[T, error]``。迭代期间需要保持 rows 打开,并在使用结束后关闭。 Context 快捷函数 """""""""""""""" 当 context 通过 ``juice.ContextWithManager`` 携带 manager 时,可以使用快捷函数,避免手动创建 executor。 .. code-block:: go ctx := juice.ContextWithManager(context.Background(), engine) user, err := juice.QueryContext[User](ctx, "GetUser", juice.H{"id": 1}) users, err := juice.QueryListContext[User](ctx, "ListUsers", nil) userPtrs, err := juice.QueryList2Context[User](ctx, "ListUsers", nil) iter, err := juice.QueryIterContext[User](ctx, "ListUsers", nil) 迭代器快捷函数会在迭代完成或提前停止时关闭底层 rows。返回的迭代器仍然需要被消费,否则包装器无法代为关闭 rows。 选择指南 """"""""""" 1. 使用 ``Bind`` 当: - 需要最大的灵活性 - 不确定返回类型 - 需要处理单行数据 2. 使用 ``List`` 当: - 确定返回切片类型 - 追求更好的性能 - 处理值类型切片 3. 使用 ``List2`` 当: - 需要修改切片元素 - 处理大型结构体 - 想避免值拷贝 4. 使用 ``Iter`` 当: - 需要迭代处理大量数据 .. note:: 性能提示: - ``List`` 对非指针类型做了特殊优化 - ``List2`` 虽然多一次转换,在代码生成方面多了一种选择 - ``Bind`` 最灵活但可能不是最快的选择 - ``Iter`` 当需要迭代大量数据时,性能最好,但是如果处理数据需要很长时间的话,会持续占用一个连接 自增主键映射 ------------ 支持自动获取自增主键值: .. code-block:: xml INSERT INTO users (name, age) VALUES (#{name}, #{age}) 使用条件: 1. 数据库驱动支持 ``LastInsertId`` 2. 参数必须是结构体指针 3. ``useGeneratedKeys="true"`` 4. 指定 ``keyProperty`` 或使用 ``autoincr:"true"`` 标签 5. 主键字段类型必须支持整数赋值 批量插入优化 ------------ 高效的批量数据插入支持: .. code-block:: xml INSERT INTO users (name, age) VALUES (#{user.name}, #{user.age}) 注意:批量插入的参数类型必须是切片、数组或者是有且仅有一个 key 的 map,并且 map 的 value 类型必须是切片或者数组 优化特性: 1. **智能批次处理**: - 自动分批处理大量数据 - 可配置批次大小(batchSize) - 默认单次执行 2. **预编译优化**: - 预编译语句复用 - 最多生成两个预编译语句 - 有效减少数据库压力 3. **性能建议**: - 建议批次大小:50-1000 - 根据数据量和数据库性能调整 - 避免过大批次造成数据库压力 .. tip:: 批量插入最佳实践: 1. 合理设置批次大小 2. 注意监控数据库性能 3. 考虑事务管理 4. 做好错误处理