泛型使用指南
泛型使用指南
Section titled “泛型使用指南”Pie 基于 Go 泛型构建,提供类型安全的 MongoDB 操作体验。本指南将详细介绍泛型在 Pie 中的使用方式、优势和最佳实践。
什么是泛型?
Section titled “什么是泛型?”泛型是 Go 1.18 引入的特性,允许编写可重用的代码,同时保持类型安全。在 Pie 中,泛型确保了编译时的类型检查,避免了运行时的类型错误。
Session[T] - 最常用的泛型类型
Section titled “Session[T] - 最常用的泛型类型”Session[T] 是 Pie 中最核心的泛型类型,提供了类型安全的数据库操作:
// 创建类型安全的会话session := pie.Table[User](engine)
// 查询操作返回 []User,而不是 []anyusers, err := session.Find(context.Background())
// 插入操作接受 *User 类型user := &User{Name: "张三", Email: "zhangsan@example.com"}result, err := session.Insert(context.Background(), user)
// 更新操作的类型安全updateResult, err := session. Where("email", "zhangsan@example.com"). Update(context.Background(), bson.D{{"$set", bson.D{{"age", 25}}}})Aggregate[T] - 聚合查询的泛型支持
Section titled “Aggregate[T] - 聚合查询的泛型支持”// 创建类型安全的聚合构建器aggregate := pie.NewAggregate[User](engine)
// 聚合结果返回 []Uservar results []Usererr := aggregate. MatchStage().Where("status", "active").Done(). GroupStage().By("department", "$department").Count("total").Done(). Execute(context.Background(), &results)Cursor[T] - 游标的泛型支持
Section titled “Cursor[T] - 游标的泛型支持”// 创建游标cursor := pie.NewCursor[User](context.Background(), mongoCursor)
// 迭代时获得类型安全的 User 对象for cursor.Next(context.Background()) { var user User if err := cursor.Decode(&user); err != nil { // 处理错误 } // user 是 User 类型,不是 any}Pie 提供了多个泛型函数来创建类型安全的操作:
// 基础函数session := pie.Table[User](engine) // 创建会话aggregate := pie.NewAggregate[User](engine) // 创建聚合
// Must 版本(失败时 panic)session := pie.MustTable[User](engine) // 必须成功创建会话aggregate := pie.MustAggregate[User](engine) // 必须成功创建聚合
// 使用默认引擎session, err := pie.TableWithDefault[User]() // 使用默认引擎创建会话session := pie.MustTableWithDefault[User]() // 必须成功创建会话类型安全的好处
Section titled “类型安全的好处”泛型提供了编译时类型检查,避免了运行时的类型错误:
// ❌ 传统方式 - 运行时可能出错var users []anyerr := session.Find(context.Background(), &users)// 需要手动类型断言for _, u := range users { user := u.(User) // 可能 panic}
// ✅ 泛型方式 - 编译时类型安全var users []Usererr := session.Find(context.Background(), &users)// 直接使用,无需类型断言for _, user := range users { fmt.Println(user.Name) // 类型安全}类型推断和显式类型声明
Section titled “类型推断和显式类型声明”Go 的类型推断让泛型使用更加简洁:
// 类型推断session := pie.Table[User](engine) // T 被推断为 User
// 显式类型声明(通常不需要)var session *pie.Session[User] = pie.Table[User](engine)泛型与接口的配合使用
Section titled “泛型与接口的配合使用”// 定义通用接口type Model interface { GetID() string GetCreatedAt() time.Time}
// 使用约束func ProcessModel[T Model](session *pie.Session[T], ctx context.Context) error { models, err := session.Find(ctx) if err != nil { return err }
for _, model := range models { fmt.Printf("ID: %s, Created: %v\n", model.GetID(), model.GetCreatedAt()) } return nil}泛型返回类型
Section titled “泛型返回类型”Pie 提供了多种泛型返回类型:
// Result[T] - 通用结果类型func GetUser[T any](session *pie.Session[T], id string) *pie.Result[*T] { user, err := session.FindByID(context.Background(), id) return &pie.Result[*T]{ Data: user, Error: err, }}
// PaginationResult[T] - 分页结果func GetUsersWithPagination[T any](session *pie.Session[T], page, size int) *pie.PaginationResult[T] { var users []T total, err := session.Paginate(context.Background(), page, size, &users) return &pie.PaginationResult[T]{ Data: users, Total: total, Page: int64(page), Size: int64(size), Error: err, }}
// AggregateResult[T] - 聚合结果func GetUserStats[T any](aggregate *pie.Aggregate[T]) *pie.AggregateResult[T] { var results []T err := aggregate.Execute(context.Background(), &results) return &pie.AggregateResult[T]{ Data: results, Error: err, }}泛型在事务中的使用
Section titled “泛型在事务中的使用”// TransactionWithResult[T] - 带结果的事务func TransferMoney[T any](tx *pie.Transaction, fromID, toID string, amount float64) *pie.TransactionResult[T] { return pie.TransactionWithResult[T](tx, context.Background(), func(ctx context.Context) (T, error) { // 在事务中执行操作 fromSession := pie.Table[Account](tx.engine) toSession := pie.Table[Account](tx.engine)
// 扣除发送方余额 err := fromSession.Where("id", fromID).Update(ctx, bson.D{{"$inc", bson.D{{"balance", -amount}}}}) if err != nil { return zero, err }
// 增加接收方余额 err = toSession.Where("id", toID).Update(ctx, bson.D{{"$inc", bson.D{{"balance", amount}}}}) if err != nil { return zero, err }
return zero, nil })}泛型在 Change Streams 中的使用
Section titled “泛型在 Change Streams 中的使用”// ChangeStreamWatcher[T] - 变更流监听器func WatchUserChanges[T any](engine *pie.Engine) { watcher := pie.NewWatcher[T](engine)
watcher.OnChange(func(event *pie.ChangeEvent[T]) { switch event.OperationType { case "insert": fmt.Printf("新用户创建: %+v\n", event.FullDocument) case "update": fmt.Printf("用户更新: %+v\n", event.FullDocument) case "delete": fmt.Printf("用户删除: %+v\n", event.DocumentKey) } })
watcher.Start(context.Background())}何时使用泛型 vs 何时使用 any
Section titled “何时使用泛型 vs 何时使用 any”// ✅ 推荐:使用泛型,类型安全func ProcessUsers[T any](session *pie.Session[T], ctx context.Context) ([]T, error) { users, err := session.Find(ctx) return users, err}
// ❌ 不推荐:使用 any,失去类型安全func ProcessUsersGeneric(session *pie.Session[any], ctx context.Context) ([]any, error) { users, err := session.Find(ctx) return users, err}模型定义的最佳实践
Section titled “模型定义的最佳实践”// ✅ 推荐:定义清晰的模型type User struct { ID bson.ObjectID `bson:"_id,omitempty"` Name string `bson:"name"` Email string `bson:"email"` Age int `bson:"age"` CreatedAt time.Time `bson:"created_at"` UpdatedAt time.Time `bson:"updated_at"`}
// ✅ 推荐:实现接口方法func (u *User) GetID() string { return u.ID.Hex()}
func (u *User) GetCreatedAt() time.Time { return u.CreatedAt}避免类型转换的技巧
Section titled “避免类型转换的技巧”// ✅ 推荐:使用泛型避免类型转换func GetUserByEmail[T any](session *pie.Session[T], email string) (*T, error) { var user T err := session.Where("email", email).First(context.Background(), &user) if err != nil { return nil, err } return &user, nil}
// ❌ 不推荐:需要类型转换func GetUserByEmailGeneric(session *pie.Session[any], email string) (any, error) { var user any err := session.Where("email", email).First(context.Background(), &user) if err != nil { return nil, err } // 需要类型断言 return user.(User), nil}泛型在 Go 中提供了零成本抽象,编译后的代码与手写代码性能相同:
// 泛型代码func FindByID[T any](session *pie.Session[T], id string) (*T, error) { var result T err := session.FindByID(context.Background(), id, &result) return &result, err}
// 编译后等同于手写的非泛型代码func FindUserByID(session *pie.Session[User], id string) (*User, error) { var result User err := session.FindByID(context.Background(), id, &result) return &result, err}泛型类型不匹配的错误处理
Section titled “泛型类型不匹配的错误处理”// 问题:类型不匹配var users []Usererr := session.Find(context.Background(), &users) // ✅ 正确
// ❌ 错误:类型不匹配var users []stringerr := session.Find(context.Background(), &users) // 编译错误
// 解决方案:确保类型一致var users []Usererr := session.Find(context.Background(), &users)如何在不同泛型类型之间转换
Section titled “如何在不同泛型类型之间转换”// 如果需要转换,使用中间变量func ConvertUsersToDTOs(users []User) []UserDTO { var dtos []UserDTO for _, user := range users { dtos = append(dtos, UserDTO{ ID: user.ID.Hex(), Name: user.Name, Email: user.Email, }) } return dtos}
// 或者使用泛型转换函数func ConvertSlice[T, U any](slice []T, converter func(T) U) []U { result := make([]U, len(slice)) for i, item := range slice { result[i] = converter(item) } return result}泛型与 BSON 序列化的配合
Section titled “泛型与 BSON 序列化的配合”// BSON 标签与泛型配合使用type User struct { ID bson.ObjectID `bson:"_id,omitempty"` Name string `bson:"name"` Email string `bson:"email"` Age int `bson:"age"` CreatedAt time.Time `bson:"created_at"` UpdatedAt time.Time `bson:"updated_at"`}
// 泛型确保类型安全,BSON 标签处理序列化session := pie.Table[User](engine)user := &User{Name: "张三", Email: "zhangsan@example.com"}result, err := session.Insert(context.Background(), user)常见编译错误及解决方案
Section titled “常见编译错误及解决方案”// 错误 1:类型参数缺失// ❌ 编译错误session := pie.Table(engine)
// ✅ 正确session := pie.Table[User](engine)
// 错误 2:类型不匹配// ❌ 编译错误var users []stringerr := session.Find(context.Background(), &users)
// ✅ 正确var users []Usererr := session.Find(context.Background(), &users)
// 错误 3:泛型约束不满足// ❌ 如果 User 没有实现 Model 接口func ProcessModel[T Model](session *pie.Session[T], ctx context.Context) error { // ...}
// ✅ 确保 User 实现了 Model 接口type User struct { // ...}
func (u *User) GetID() string { return u.ID.Hex() }func (u *User) GetCreatedAt() time.Time { return u.CreatedAt }泛型是 Pie 的核心特性,提供了:
- 类型安全:编译时检查,避免运行时错误
- 代码复用:一套代码处理多种类型
- 性能优化:零成本抽象,无运行时开销
- 开发体验:更好的 IDE 支持和代码提示
通过合理使用泛型,您可以编写更安全、更高效、更易维护的 MongoDB 操作代码。