Database
PrismGo 的数据库组件基于 GORM 提供数据库连接管理、迁移注册和索引维护等能力。框架内置 MySQL 驱动;SQLite 由独立的 github.com/prismgo/sqlite 扩展提供。组件采用 Laravel 风格的配置结构,通过 ServiceProvider 实现延迟连接和自动关闭。
迁移和填充命令的完整参数见 命令行:数据库迁移命令。
简介
数据库组件围绕以下几个核心能力构建:
- 连接管理:通过
OpenDefaultConnection/OpenConnection按配置创建 GORM 数据库连接,自动应用连接池参数。 - Facade 解析:
database.Resolve()从应用容器获取默认*gorm.DB实例,业务代码无需关心连接构造细节。 - 迁移注册:
RegisterMigration/RegisterSeeder在init阶段声明迁移和填充函数,命令运行时按名称查找执行。 - Migrator 辅助:提供
EnsureInnoDB、EnsureCompositeIndexes、EnsureCompositeUniqueIndexes、DropObsoleteIndexes等幂等索引维护工具,弥补 GORM AutoMigrate 在联合索引和引擎切换方面的不足。
配置
配置文件
数据库配置集中注册在 config/database.go,结构对齐 Laravel 的 config/database.php。你可以使用环境变量覆盖各项参数:
// config/database.go
func init() {
config.Add("database", func() map[string]interface{} {
return map[string]interface{}{
"default": config.Env("DATABASE_CONNECTION", "mysql"),
"connections": map[string]interface{}{
"sqlite": map[string]interface{}{
"driver": "sqlite",
"database": config.Env("SQLITE_DATABASE", "storage/database.sqlite"),
},
"mysql": map[string]interface{}{
"driver": config.Env("DATABASE_DRIVER", "mysql"),
"host": config.Env("DATABASE_HOST", "127.0.0.1"),
"port": config.Env("DATABASE_PORT", 3306),
"database": config.Env("DATABASE_NAME", "workorder"),
"username": config.Env("DATABASE_USER", "root"),
"password": config.Env("DATABASE_PASSWORD", "root"),
"charset": config.Env("DATABASE_CHARSET", "utf8mb4"),
"parse_time": config.Env("DATABASE_PARSE_TIME", true),
"loc": config.Env("DATABASE_LOC", "Local"),
"dsn": config.Env("DATABASE_DSN", ""),
"max_open_conns": config.Env("DATABASE_MAX_OPEN_CONNS", 30),
"max_idle_conns": config.Env("DATABASE_MAX_IDLE_CONNS", 10),
"conn_max_lifetime": config.Env("DATABASE_CONN_MAX_LIFETIME", "1h"),
"conn_max_idle_time": config.Env("DATABASE_CONN_MAX_IDLE_TIME", "10m"),
},
},
}
})
}驱动前置条件
MySQL
底层使用 go-sql-driver/mysql 和 gorm.io/driver/mysql。无需额外配置,只要 MySQL 服务可达且账号密码正确即可连接。
SQLite
SQLite 已从框架核心拆分为独立扩展。安装模块并把扩展 Provider 放在框架默认 Provider 与业务 Provider 之间:
go get github.com/prismgo/sqliteimport sqliteext "github.com/prismgo/sqlite"
app := foundation.Configure().
WithExtensionProviders(sqliteext.ServiceProvider{}).
WithProviders(applicationProviders...).
Create()扩展底层使用纯 Go SQLite 驱动,无需运行外部数据库服务。database 可填写数据库文件路径,也可使用 file::memory:?cache=shared 这类 SQLite DSN;适合本地开发和 Hermetic 测试。未注册扩展时使用 sqlite 或 sqlite3 会返回 database: driver "sqlite" is not registered。
扩展不仅注册连接驱动,也承载 SQLite 的 Schema 方言能力,包括 Blueprint 编译、Schema/表/视图/外键元数据、外键检查开关、复合索引以及 migrate:fresh 所需的对象清理。安装扩展后仍使用统一的 database.Open、schema.New(db) / schema.Bind(db) 和迁移命令;应用代码不需要改用扩展专属 facade。框架核心的模块依赖图不包含 SQLite 驱动。
PostgreSQL、SQL Server 等其他驱动尚未内置,传入未知驱动名会立即返回错误。
配置参数说明
顶层配置
| 参数路径 | 环境变量 | 默认值 | 说明 |
|---|---|---|---|
database.default | DATABASE_CONNECTION | "mysql" | 默认使用的数据库连接名,对应 connections 下的键名 |
MySQL 连接配置
| 参数路径 | 环境变量 | 默认值 | 说明 |
|---|---|---|---|
database.connections.mysql.driver | DATABASE_DRIVER | "mysql" | 数据库驱动类型 |
database.connections.mysql.host | DATABASE_HOST | "127.0.0.1" | 数据库主机地址 |
database.connections.mysql.port | DATABASE_PORT | 3306 | 数据库端口号 |
database.connections.mysql.database | DATABASE_NAME | "workorder" | 数据库名称 |
database.connections.mysql.username | DATABASE_USER | "root" | 数据库用户名 |
database.connections.mysql.password | DATABASE_PASSWORD | "root" | 数据库密码 |
database.connections.mysql.charset | DATABASE_CHARSET | "utf8mb4" | 字符集,拼接到 DSN 的 charset 参数 |
database.connections.mysql.parse_time | DATABASE_PARSE_TIME | true | 是否将 DATE/DATETIME 自动扫描为 time.Time |
database.connections.mysql.loc | DATABASE_LOC | "Local" | 时区,拼接到 DSN 的 loc 参数 |
database.connections.mysql.dsn | DATABASE_DSN | "" | 完整 DSN 连接串。非空时优先使用,其余字段被忽略 |
database.connections.mysql.max_open_conns | DATABASE_MAX_OPEN_CONNS | 30 | 最大打开连接数 |
database.connections.mysql.max_idle_conns | DATABASE_MAX_IDLE_CONNS | 10 | 最大空闲连接数 |
database.connections.mysql.conn_max_lifetime | DATABASE_CONN_MAX_LIFETIME | "1h" | 连接最大存活时间,支持持续时间文本(如 "1h"、"30m")或秒数字符串(如 "3600") |
database.connections.mysql.conn_max_idle_time | DATABASE_CONN_MAX_IDLE_TIME | "10m" | 连接最大空闲时间,格式同上 |
SQLite 连接配置
| 参数路径 | 环境变量 | 默认值 | 说明 |
|---|---|---|---|
database.connections.sqlite.driver | — | "sqlite" | SQLite 驱动名,也接受 "sqlite3" |
database.connections.sqlite.database | SQLITE_DATABASE | "storage/database.sqlite" | SQLite 文件路径或 DSN |
使用 DSN 连接
类似 Laravel 的 DB_URL 支持,PrismGo 允许通过 dsn 字段直接提供完整的 MySQL 连接串。当 dsn 非空时,host、port、username、password、database、charset、parse_time、loc 等字段将被忽略:
DATABASE_DSN=root:secret@tcp(127.0.0.1:3306)/workorder?charset=utf8mb4&parseTime=true&loc=Local当你使用托管数据库服务(如 AWS RDS、阿里云 RDS)时,通常只需要配置一个 DSN 即可。
获取数据库连接
使用 Facade 解析
业务代码中最常用的方式,从应用容器获取默认 *gorm.DB 实例:
import "github.com/prismgo/framework/database"
db := database.Resolve()Resolve() 返回的 *gorm.DB 是单例,首次调用时由 ServiceProvider 注册的延迟工厂触发连接创建,后续调用直接复用。
手动打开连接
在独立程序或测试中,可以绕过容器直接创建连接:
// 打开默认连接(读取 config/database 配置)
db, err := database.OpenDefaultConnection()
if err != nil {
return err
}
// 打开指定名称的连接
db, err := database.OpenConnection("mysql")
if err != nil {
return err
}也可以使用 Manager.Open 直接指定已注册的驱动和 DSN;应用内的 database.Open facade 会使用当前应用持有的 Manager:
manager := database.NewManager()
db, err := manager.Open(
"mysql",
"root:secret@tcp(127.0.0.1:3306)/app?charset=utf8mb4&parseTime=true&loc=Local",
database.MySQLConfig{},
)
if err != nil {
return err
}
sqliteDB, err := database.Open("sqlite", "storage/testing.sqlite", database.MySQLConfig{}) // 需先注册 SQLite 扩展
if err != nil {
return err
}核心 Manager 默认只注册
"mysql"。扩展可以通过Manager.Extend注册或替换应用局部的驱动;github.com/prismgo/sqlite注册"sqlite"和"sqlite3"。
连接池管理
OpenConnection 内部自动从配置读取连接池参数并应用。如需手动调整,可以使用 applyConnectionPoolConfig(包级私有)或直接操作 *sql.DB:
sqlDB, err := db.DB()
if err != nil {
return err
}
sqlDB.SetMaxOpenConns(50)
sqlDB.SetMaxIdleConns(20)
sqlDB.SetConnMaxLifetime(time.Hour)
sqlDB.SetConnMaxIdleTime(10 * time.Minute)时间格式说明
conn_max_lifetime 和 conn_max_idle_time 支持两种格式:
| 格式 | 示例 | 说明 |
|---|---|---|
| 持续时间文本 | "1h"、"30m"、"1h30m" | Go 标准库 time.ParseDuration 格式 |
| 秒数字符串 | "3600"、"600" | 纯数字表示秒数,支持小数如 "1.5" |
空白或无效值会回退到默认值(conn_max_lifetime 默认 1h,conn_max_idle_time 默认 10m)。
模型定义与使用
PrismGo 使用 GORM 作为 ORM 层,模型通过 Go struct 定义。以下是一个简单的模型示例:
type User struct {
ID uint `gorm:"primaryKey"`
Name string `gorm:"size:100;not null"`
Email string `gorm:"uniqueIndex;size:255"`
Age int `gorm:"default:0"`
CreatedAt time.Time
UpdatedAt time.Time
}获取 *gorm.DB 实例后,即可进行 CRUD 操作:
db := database.Resolve()
// 自动迁移(建表)
if err := db.AutoMigrate(&User{}); err != nil {
return err
}
// 创建记录
user := User{Name: "张三", Email: "zhangsan@example.com", Age: 25}
if err := db.Create(&user).Error; err != nil {
return err
}
// 查询单条
var u User
if err := db.First(&u, user.ID).Error; err != nil {
return err
}
// 条件查询
var users []User
if err := db.Where("age > ?", 18).Find(&users).Error; err != nil {
return err
}
// 更新
if err := db.Model(&u).Update("name", "李四").Error; err != nil {
return err
}
// 删除
if err := db.Delete(&u).Error; err != nil {
return err
}GORM 提供了丰富的功能:模型关联(BelongsTo/HasMany/ManyToMany)、预加载、事务、Scopes、Hooks 等。详见 GORM 官方文档:https://gorm.io/zh_CN/docs/
迁移
PrismGo 的迁移系统采用 Go 函数注册模式,而非 Laravel 的迁移文件类。业务迁移文件在 init() 阶段通过 RegisterMigration 注册 up/down 处理器,命令运行时按文件名查找执行。
注册迁移
自动推导名称
RegisterMigration 自动从 up 函数的源码位置推导迁移名称(取 .go 文件名去掉扩展名),与目录扫描结果保持一致:
// 文件:database/migration/202601010000_create_users_table.go
func init() {
database.RegisterMigration(UpCreateUsersTable, DownCreateUsersTable)
}
func UpCreateUsersTable(db *gorm.DB) error {
return db.AutoMigrate(&User{})
}
func DownCreateUsersTable(db *gorm.DB) error {
return db.Migrator().DropTable("users")
}注册名自动推导为 "202601010000_create_users_table"。
命名约定:up 函数必须定义在对应的迁移文件中,注册名取 up 函数所在文件名(去掉
.go)。down 可为匿名函数,注册名仍然只取 up 函数来源。
显式指定名称
在测试、临时工具或非常规文件名场景下,可以使用 RegisterMigrationAs 显式指定迁移名:
database.RegisterMigrationAs("custom_migration_name", upFunc, downFunc)迁移名不能为空,否则会 panic。
查询已注册迁移
entry, ok := database.MigrationByName("202601010000_create_users_table")
if ok {
if err := entry.Up(db); err != nil {
return err
}
}注册 Seeder
自动推导 Class 名
RegisterSeeder 自动从 Seed 函数的源码位置推导 class 名。短 class 名来自文件名去掉时间前缀后的 PascalCase 形式,同时注册包路径命名空间别名,兼容 Laravel 风格的 --class 参数:
// 文件:database/seeders/202604280001_database_seeder.go
func init() {
database.RegisterSeeder(SeedDatabase)
}
func SeedDatabase(db *gorm.DB) error {
return db.Create(&User{Name: "Admin"}).Error
}自动注册的 class 名:
- 短名:
"DatabaseSeeder" - 命名空间别名:
"Prismgo\\Database\\Seeders\\DatabaseSeeder"
显式指定 Class 名
database.RegisterSeederAs("CustomSeeder", seedFunc)查询与校验
// 查询 seeder 执行函数
fn, ok := database.SeederByClass("DatabaseSeeder")
// 获取所有已注册 class 名
names := database.SeederClassNames()
// 校验 seeder 已注册,未注册时返回错误
if err := database.EnsureSeederRegistered("DatabaseSeeder"); err != nil {
return err
}默认 Seeder
当 db:seed 命令未指定 --class 参数时,默认使用 "DatabaseSeeder" 作为 seeder class:
const DefaultSeederClass = "DatabaseSeeder"Migrator 辅助工具
GORM 的 AutoMigrate 无法处理联合索引创建、引擎切换和废弃索引删除等场景。PrismGo 提供了一组幂等的 Migrator 辅助函数,在迁移后执行这些补充操作。
建表选项
TableOptions 返回指定 dialect 的建表选项,用于 AutoMigrate 前注入:
opts := database.TableOptions("mysql")
// 返回 "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"
db.Set("gorm:table_options", opts).AutoMigrate(&User{})非 MySQL dialect 返回空串。
引擎管理
EnsureInnoDB
将所有受管 MySQL 表强制切换为 InnoDB 引擎。GORM AutoMigrate 无法改变已有表的 ENGINE,因此需要此辅助完成引擎切换。非 MySQL dialect 直接跳过:
if err := database.EnsureInnoDB(db, []any{&User{}, &Order{}}); err != nil {
return err
}ShouldAlterEngine
判断给定引擎是否需要切换为 InnoDB。空字符串或 InnoDB 以外的引擎均返回 true:
if database.ShouldAlterEngine("MyISAM") {
// 需要切换引擎
}ManagedTableNames
通过 GORM 解析给定模型的实际表名列表,用于迁移后的 ALTER 操作:
names, err := database.ManagedTableNames(db, []any{&User{}, &Order{}})
// names = []string{"users", "orders"}联合索引
CompositeIndex 描述一条普通(非唯一)联合索引声明。GORM AutoMigrate 无法跨嵌入 struct 字段自动建立联合索引,因此需要通过 SQL 显式创建:
indexes := []database.CompositeIndex{
{
Table: "users",
Name: "idx_users_tenant_status",
Columns: "`tenant_id`, `status`",
},
{
Table: "orders",
Name: "idx_orders_tenant_created",
Columns: "`tenant_id`, `created_at`",
},
}
if err := database.EnsureCompositeIndexes(db, indexes); err != nil {
return err
}EnsureCompositeIndexes 是幂等操作:
- MySQL:先通过
IndexExists检查索引是否已存在,已存在则跳过。 - SQLite:使用
CREATE INDEX IF NOT EXISTS语法。 - 其他 dialect 直接忽略。
联合唯一索引
CompositeUniqueIndex 描述一条联合唯一索引声明:
uniqueIndexes := []database.CompositeUniqueIndex{
{
Table: "users",
Name: "idx_users_tenant_phone",
Columns: "`tenant_id`, `phone`",
},
}
if err := database.EnsureCompositeUniqueIndexes(db, uniqueIndexes); err != nil {
return err
}EnsureCompositeUniqueIndexes 同样是幂等操作,行为与 EnsureCompositeIndexes 一致,区别在于创建的是 UNIQUE INDEX。
删除废弃索引
DropIndex 描述一条需要删除的废弃索引。DropObsoleteIndexes 仅在 MySQL 下执行,SQLite 跳过(测试库每次重建无需清理):
obsoleteIndexes := []database.DropIndex{
{Table: "users", Name: "idx_users_legacy"},
{Table: "orders", Name: "idx_orders_legacy"},
}
if err := database.DropObsoleteIndexes(db, obsoleteIndexes); err != nil {
return err
}操作幂等:索引不存在时跳过。
索引存在性检查
IndexExists 查询 MySQL information_schema.statistics,判断指定表的指定索引是否已存在:
exists, err := database.IndexExists(db, "users", "idx_users_tenant_status")
if err != nil {
return err
}
if exists {
// 索引已存在
}主键列查询
PrimaryKeyColumns 返回指定 MySQL 表当前主键列(按顺序)。表不存在或无主键时返回空切片:
columns, err := database.PrimaryKeyColumns(db, "users")
// columns = []string{"id"}SameColumns 判断两组列名是否完全一致且顺序相同,用于识别目标联合主键是否已就绪:
if database.SameColumns(currentPK, targetPK) {
// 主键结构一致,无需 ALTER
}服务提供者
ServiceProvider 把应用局部的 database.manager 和默认数据库连接以延迟工厂的形式注册到应用容器。Register 阶段只声明工厂,不提前打开 GORM 连接,配置或连接错误由后续严格 Resolve 暴露。每个 Application 的驱动注册与连接缓存彼此隔离:
// 在 bootstrap/app.go 中注册
app.Register(&database.ServiceProvider{})Boot 阶段无副作用,数据库连接只在严格 Resolve 或真实入口使用时打开。
如果容器中已存在 "database.default" 绑定(如测试中注入了自定义 *gorm.DB),Register 会保留现有绑定不做覆盖。
资源生命周期
ServiceProvider 在注册时已通过 container.WithCloser 绑定了关闭回调。应用正常退出时,框架会调用 *gorm.DB 底层 sql.DB.Close() 释放连接池,开发者无需手动处理。
独立程序或测试中手动创建连接时,应确保关闭:
db, err := database.Open("mysql", dsn)
if err != nil {
return err
}
sqlDB, _ := db.DB()
defer sqlDB.Close()或通过 facade 的 DBCloseOption 在 bootstrap 注册时绑定关闭行为:
app.Instance("database.default", db, database.DBCloseOption())与 Laravel Database 的对应关系
| Laravel 概念 | PrismGo 等价 |
|---|---|
config/database.php | config/database.go |
DB_CONNECTION 环境变量 | DATABASE_CONNECTION 环境变量 |
DB::connection('mysql') | database.OpenConnection("mysql") |
DB::connection()->getPdo() | db.DB() (返回 *sql.DB) |
| 迁移文件类 | database.RegisterMigration(up, down) |
php artisan migrate | go run ./ migrate |
php artisan db:seed | go run ./ seed |
php artisan db:seed --class=UserSeeder | database.SeederByClass("UserSeeder") |
DatabaseSeeder 默认类 | database.DefaultSeederClass 常量 |
| Schema Builder | github.com/prismgo/framework/database/schema 子包(独立文档) |