关于 encore-go-database
介绍在 Encore Go 项目中使用 SQL 数据库 API 的参考技能,面向 Go 开发者,涵盖查询、迁移、事务及参数化查询等实践。
主要功能
- 01使用 sqldb 设置数据库
- 02Query、QueryRow 和 Exec 用法
- 03迁移文件命名与结构
- 04事务和 Scan 指引
- 05参数化查询与错误处理
适用场景
- 01为 Encore Go 服务实现数据库访问
- 02添加并整理 SQL 迁移
- 03检查查询和事务写法
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ encoredev/ skills --skill encore-go-database安装后在 Claude Code 中输入/encore-go-database确认已加载。
详细介绍
Encore Go 数据库操作
说明
数据库设置
package user
import "encore.dev/storage/sqldb"
var db = sqldb.NewDatabase("userdb", sqldb.DatabaseConfig{
Migrations: "./migrations",
})
查询方法
Encore 的数据库 API 与 Go 标准库的 database/sql 包类似。使用 .Scan() 将查询结果读入变量。
Query - 多行
type User struct {
ID string
Email string
Name string
}
func listActiveUsers(ctx context.Context) ([]*User, error) {
rows, err := db.Query(ctx, `
SELECT id, email, name FROM users WHERE active = true
`)
if err != nil {
return nil, err
}
defer rows.Close()
var users []*User
for rows.Next() {
var u User
if err := rows.Scan(&u.ID, &u.Email, &u.Name); err != nil {
return nil, err
}
users = append(users, &u)
}
return users, rows.Err()
}
QueryRow - 单行
func getUser(ctx context.Context, id string) (*User, error) {
var u User
err := db.QueryRow(ctx, `
SELECT id, email, name FROM users WHERE id = $1
`, id).Scan(&u.ID, &u.Email, &u.Name)
if errors.Is(err, sqldb.ErrNoRows) {
return nil, &errs.Error{
Code: errs.NotFound,
Message: "user not found",
}
}
if err != nil {
return nil, err
}
return &u, nil
}
Exec - 无返回值
用于 INSERT、UPDATE、DELETE 操作:
func createUser(ctx context.Context, email, name string) error {
_, err := db.Exec(ctx, `
INSERT INTO users (id, email, name)
VALUES ($1, $2, $3)
`, generateID(), email, name)
return err
}
func updateUser(ctx context.Context, id, name string) error {
_, err := db.Exec(ctx, `
UPDATE users SET name = $1 WHERE id = $2
`, name, id)
return err
}
func deleteUser(ctx context.Context, id string) error {
_, err := db.Exec(ctx, `
DELETE FROM users WHERE id = $1
`, id)
return err
}
迁移
文件结构
user/
└── migrations/
├── 1_create_users.up.sql
├── 2_add_posts.up.sql
└── 3_add_indexes.up.sql
命名约定
- 以数字开头(1、2 等)
- 后接下划线和描述
- 以
.up.sql结尾 - 数字必须连续
迁移示例
-- migrations/1_create_users.up.sql
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
email TEXT UNIQUE NOT NULL,
name TEXT NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
CREATE INDEX idx_users_email ON users(email);
事务
func transferFunds(ctx context.Context, fromID, toID string, amount int) error {
tx, err := db.Begin(ctx)
if err != nil {
return err
}
defer tx.Rollback() // No-op if committed
_, err = tx.Exec(ctx, `
UPDATE accounts SET balance = balance - $1 WHERE id = $2
`, amount, fromID)
if err != nil {
return err
}
_, err = tx.Exec(ctx, `
UPDATE accounts SET balance = balance + $1 WHERE id = $2
`, amount, toID)
if err != nil {
return err
}
return tx.Commit()
}
使用 Scan
Scan 方法会将查询结果中的列读入变量。列按位置映射,而非按名称映射——Scan 的参数顺序必须与 SELECT 语句中列的顺序一致。
type User struct {
ID string
Email string
Name string
CreatedAt time.Time
}
// Single row with QueryRow
func getUser(ctx context.Context, id string) (*User, error) {
var u User
err := db.QueryRow(ctx, `
SELECT id, email, name, created_at FROM users WHERE id = $1
`, id).Scan(&u.ID, &u.Email, &u.Name, &u.CreatedAt)
if err != nil {
return nil, err
}
return &u, nil
}
// You can also scan into an inline struct
func getItem(ctx context.Context, id int64) error {
var item struct {
ID int64
Title string
Done bool
}
err := db.QueryRow(ctx, `
SELECT id, title, done FROM items WHERE id = $1
`, id).Scan(&item.ID, &item.Title, &item.Done)
return err
}
SQL 注入防护
始终使用参数化查询:
// SAFE - values are parameterized
var u User
err := db.QueryRow(ctx, `
SELECT id, email, name FROM users WHERE email = $1
`, email).Scan(&u.ID, &u.Email, &u.Name)
// WRONG - SQL injection risk
query := fmt.Sprintf("SELECT * FROM users WHERE email = '%s'", email)
错误处理
import (
"errors"
"encore.dev/storage/sqldb"
"encore.dev/beta/errs"
)
func getUser(ctx context.Context, id string) (*User, error) {
var u User
err := db.QueryRow(ctx, `
SELECT id, email, name FROM users WHERE id = $1
`, id).Scan(&u.ID, &u.Email, &u.Name)
if errors.Is(err, sqldb.ErrNoRows) {
return nil, &errs.Error{
Code: errs.NotFound,
Message: "user not found",
}
}
if err != nil {
return nil, err
}
return &u, nil
}
指南
- 始终使用参数化查询(
$1、$2等) - 使用
Scan读取查询结果——列按位置映射 - 预期返回单行时,检查是否出现
sqldb.ErrNoRows - 迁移会在启动时自动应用
- 数据库名称应使用小写且具有描述性
- 每个服务通常都有自己的数据库
- 对必须保持原子性的操作使用事务
评论
支持 Markdown