7.1 数据库相关名词
- database/sql
- Go 标准库的数据库抽象层,定义
DB/Rows/Tx接口,通过 driver 连接具体数据库。 - GORM
- Go 最流行的 ORM,提供模型映射、链式查询、关联、迁移、钩子;底层仍走 database/sql。
- 连接池(Connection Pool)
*sql.DB内部维护的一组复用连接,用 MaxOpenConns/MaxIdleConns/ConnMaxLifetime 调优。- 依赖注入(Dependency Injection)
- 把
*gorm.DB等依赖显式传入 repository/service,而非全局变量,利于测试与解耦。 - Repository(仓储层)
- 封装数据访问细节的一层,对上暴露领域方法(如
Create/FindByID),对下操作 GORM。 - 事务(Transaction)
- 一组要么全成功要么全回滚的操作,GORM 用
db.Transaction(fn)或手动 Begin/Commit/Rollback。 - 迁移(Migration)
- 把模型结构同步到数据库表,GORM 提供
AutoMigrate,生产建议用独立迁移工具管理。
7.2 连接 GORM
// internal/db/db.go
import (
"time"
"gorm.io/driver/postgres"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
func New(dsn string) (*gorm.DB, error) {
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
Logger: logger.Default.LogMode(logger.Warn),
})
if err != nil {
return nil, err
}
// 连接池调优
sqlDB, _ := db.DB()
sqlDB.SetMaxOpenConns(50) // 最大打开连接
sqlDB.SetMaxIdleConns(10) // 最大空闲连接
sqlDB.SetConnMaxLifetime(time.Hour) // 连接最长存活
return db, nil
}
连接池要匹配数据库上限
PostgreSQL 默认 max_connections=100。若 4 个服务实例各开 50 连接就爆了。规划公式约为:单实例 MaxOpenConns × 实例数 ≤ DB 上限 × 0.8,留余量给运维连接。
7.3 定义模型
type User struct {
ID uint `gorm:"primaryKey" json:"id"`
Name string `gorm:"size:64;not null" json:"name"`
Email string `gorm:"uniqueIndex;size:128" json:"email"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// 开发期自动建表(生产用迁移工具)
db.AutoMigrate(&User{})
7.4 依赖注入:Repository 模式
// internal/repository/user.go
type UserRepo struct { db *gorm.DB }
func NewUserRepo(db *gorm.DB) *UserRepo { return &UserRepo{db: db} }
func (r *UserRepo) Create(ctx context.Context, u *User) error {
return r.db.WithContext(ctx).Create(u).Error
}
func (r *UserRepo) FindByID(ctx context.Context, id uint) (*User, error) {
var u User
err := r.db.WithContext(ctx).First(&u, id).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errcode.ErrUserNotFound
}
return &u, err
}
func (r *UserRepo) List(ctx context.Context, offset, limit int) ([]User, int64, error) {
var users []User
var total int64
q := r.db.WithContext(ctx).Model(&User{})
q.Count(&total)
err := q.Offset(offset).Limit(limit).Find(&users).Error
return users, total, err
}
始终传 context
WithContext(ctx) 把 Gin 请求的 c.Request.Context() 一路带下去,客户端断开或超时能自动取消 SQL 查询,避免慢请求堆积拖垮数据库。
7.5 装配到 Gin
func main() {
db, err := dbpkg.New(os.Getenv("DATABASE_URL"))
if err != nil {
log.Fatal(err)
}
userRepo := repository.NewUserRepo(db)
userSvc := service.NewUserService(userRepo)
userHdl := handler.NewUserHandler(userSvc)
r := gin.Default()
api := r.Group("/api/v1")
{
api.POST("/users", userHdl.Create)
api.GET("/users/:id", userHdl.Get)
api.GET("/users", userHdl.List)
}
r.Run(":8080")
}
7.6 事务处理
// 转账:扣款与入账必须同成功同失败
func (r *AccountRepo) Transfer(ctx context.Context, from, to uint, amount int64) error {
return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
// 行级锁,防并发超扣
var acc Account
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
First(&acc, from).Error; err != nil {
return err
}
if acc.Balance < amount {
return errors.New("余额不足") // 返回 error 即自动回滚
}
if err := tx.Model(&Account{}).Where("id = ?", from).
Update("balance", gorm.Expr("balance - ?", amount)).Error; err != nil {
return err
}
return tx.Model(&Account{}).Where("id = ?", to).
Update("balance", gorm.Expr("balance + ?", amount)).Error
}) // fn 返回 nil 自动 Commit,返回 error 自动 Rollback
}
事务中别用外层 db
事务闭包里所有操作必须用参数 tx,误用外层 r.db 会走池里另一条连接,不在事务内,导致部分提交、数据不一致。这是新手最常见的坑。
7.7 小结
- GORM 底层仍是 database/sql,务必调好连接池并匹配数据库上限。
- 用 Repository 模式 + 依赖注入传
*gorm.DB,别用全局变量。 - 查询一路带
WithContext,支持超时/取消。 - 事务用
db.Transaction(fn),闭包内只用tx;写操作配行锁防并发。 - 下一章讲 认证与安全——JWT、CORS、限流与密码哈希。