Osm
The lightweight Go language ORM tool uses only the Go standard library to implement SQL templates, data introspection, and supports MySQL and PostgreSQL.
Install / Use
/learn @yinshuwei/OsmREADME
OSM - Object SQL Mapping
English | 简体中文
osm (Object SQL Mapping) 是用 Go 编写的轻量级 SQL 工具库,已在生产环境中广泛使用。
支持的数据库: MySQL、PostgreSQL、SQL Server、SQLite、Oracle、TiDB、CockroachDB、ClickHouse
✨ 核心理念
提供极简且优雅的 SQL 操作接口,让数据库操作更加简单直观:
// 链式调用风格
users, err := o.Select("SELECT * FROM users WHERE age > #{Age}", 18).Structs(&users)
// 传统风格
count, err := o.SelectStructs("SELECT * FROM users WHERE age > #{Age}", 18)(&users)
🚀 核心特性
零依赖
- 仅依赖 Go 标准库,无第三方依赖
- 轻量级设计,易于集成和维护
灵活的参数绑定
使用 #{ParamName} 语法进行参数绑定,支持多种参数类型:
- 顺序参数: 按参数顺序自动匹配
- Map 参数: 支持
map[string]interface{} - Struct 参数: 直接使用结构体作为参数
- IN 查询: 原生支持 SQL IN 语句
原生 SQL 占位符支持
除了 Named 参数绑定(#{ParamName})之外,osm 还支持原生 SQL 占位符,以获得更好的性能:
- MySQL 原生占位符: 使用
?占位符 - PostgreSQL 原生占位符: 使用
$1,$2等占位符 - 自动检测: 根据 SQL 内容自动判断使用哪种模式
- 高性能: 原生占位符比 Named 参数快 10-12 倍
MySQL 原生占位符示例:
// MySQL 原生占位符
var users []User
_, err := o.Select("SELECT * FROM users WHERE id = ? AND status = ?", 1, "active").Structs(&users)
// IN 查询使用原生占位符
var users []User
_, err := o.Select("SELECT * FROM users WHERE id IN (?,?,?)", 1, 2, 3).Structs(&users)
PostgreSQL 原生占位符示例:
// PostgreSQL 原生占位符
var users []User
_, err := o.Select("SELECT * FROM users WHERE id = $1 AND status = $2", 1, "active").Structs(&users)
// IN 查询使用原生占位符
var users []User
_, err := o.Select("SELECT * FROM users WHERE id IN ($1, $2, $3)", 1, 2, 3).Structs(&users)
检测机制:
- 如果 SQL 中包含
#{,则使用 Named 参数模式(向后兼容) - 否则使用 原生占位符模式(直接将参数传递给数据库驱动)
- 无需配置,完全自动
性能对比:
| 模式 | 性能 | 内存使用 | 分配次数 | |------|------------|--------------|--------| | 原生占位符 | ~100 ns/op | 160 B/op | 4 allocs/op | | Named 参数 | ~1100 ns/op | 2346 B/op | 33 allocs/op |
注意: 原生占位符绕过参数解析,直接将参数传递给数据库驱动,因此性能显著提升。在不需要 Named 参数灵活性的场景下,推荐使用原生占位符。
数据库占位符格式
不同的数据库使用不同的原生占位符格式,osm 会自动根据数据库类型生成正确的占位符:
| 数据库 | 占位符格式 | 示例 | 驱动名称 |
|--------|-----------|------|---------|
| MySQL | ? | SELECT * FROM users WHERE id = ? | mysql |
| SQLite | ? | SELECT * FROM users WHERE id = ? | sqlite3 |
| PostgreSQL | $1, $2 | SELECT * FROM users WHERE id = $1 | postgres |
| SQL Server | $1, $2 | SELECT * FROM users WHERE id = $1 | mssql |
| Oracle | :1, :2 | SELECT * FROM users WHERE id = :1 | oracle, godror |
| TiDB | ? | SELECT * FROM users WHERE id = ? | mysql (兼容) |
| CockroachDB | $1, $2 | SELECT * FROM users WHERE id = $1 | postgres (兼容) |
| ClickHouse | $1, $2 | SELECT * FROM users WHERE id = $1 | clickhouse |
使用 Named 参数时无需关心占位符格式:
当使用 #{ParamName} 语法时,osm 会自动根据数据库类型转换为正确的占位符格式,你无需关心底层数据库的差异。
// 这段代码在所有数据库上都能正常工作
var users []User
_, err := o.Select("SELECT * FROM users WHERE id = #{Id}", 1).Structs(&users)
// osm 会自动转换为各数据库的占位符格式:
// MySQL/SQLite/TiDB: SELECT * FROM users WHERE id = ?
// PostgreSQL/CockroachDB/ClickHouse/MSSQL: SELECT * FROM users WHERE id = $1
// Oracle: SELECT * FROM users WHERE id = :1
丰富的结果处理
支持多种数据接收方式,满足不同场景需求:
| 方法类型 | 说明 | 使用场景 |
|---------|------|---------|
| Value / Values | 单行/多行多列值 | 查询多列不同类型的值 |
| Struct / Structs | 单行/多行结构体 | 对象映射 |
| String / Strings | 单个/多个字符串 | 简单字段查询 |
| Int / Ints | 单个/多个整数 | 统计查询 |
| Float64 / Float64s | 单个/多个浮点数 | 数值计算 |
| Bool / Bools | 单个/多个布尔值 | 状态标识 |
| Kvs | 键值对映射 | 双列数据 → Map |
| ColumnsAndData | 列名 + 数据行 | 数据交换/导出 |
智能的 Struct 映射
- 优先读取
db标签 - 智能的字段名转换(支持常见缩写词,如 ID、URL、HTTP 等)
- 支持嵌套结构体
- 支持指针类型(可表示 NULL)
- 查看完整的字段映射规则
SQL 占位符替换
支持在 SQL 语句中使用占位符,在运行时自动替换为配置的值。这对于以下场景特别有用:
- 表前缀替换: 在表名前添加统一前缀
- 数据库 Schema 切换: 根据环境动态切换数据库 schema
- 环境标识: 在 SQL 中插入环境相关的标识
配置示例:
o, err := osm.New("mysql", "root:123456@/test?charset=utf8", osm.Options{
SQLReplacements: map[string]string{
"[TablePrefix]": "data_", // 表前缀
"[Schema]": "prod", // 数据库schema
"[Env]": "prod", // 环境标识
},
})
// SQL 中的占位符会被自动替换
// SELECT * FROM [TablePrefix]users
// 实际执行: SELECT * FROM data_users
使用示例:
// 单表查询
o.Select("SELECT * FROM [TablePrefix]users WHERE id = #{Id}", 1)
// 多表 JOIN
o.Select("SELECT * FROM [Schema].[TablePrefix]users u JOIN [TablePrefix]orders o ON u.id = o.user_id")
// 环境相关的条件
o.Select("SELECT * FROM [TablePrefix]config WHERE env = '[Env]'")
特性:
- ✅ 性能高效:仅增加约 174ns 的开销
- ✅ 零配置零开销:未配置时完全不影响性能
- ✅ 支持多占位符:可同时替换多个不同的占位符
- ✅ 支持重复占位符:同一个 SQL 中可以多次使用同一个占位符
- ✅ 执行前替换:替换发生在参数解析之前,不影响
#{...}参数绑定
📦 安装
go get github.com/yinshuwei/osm/v2
go.mod:
require (
github.com/yinshuwei/osm/v2 v2.0.8
)
📖 API 文档
完整文档请访问: https://pkg.go.dev/github.com/yinshuwei/osm/v2
🔗 链式调用 API
osm 支持优雅的链式调用,通过 Select() 方法返回 SelectResult 对象,可灵活选择结果处理方式。
快速开始
// 查询结构体列表
var users []User
_, err := o.Select("SELECT * FROM users WHERE id > #{Id}", 1).Structs(&users)
// 查询单个值
count, err := o.Select("SELECT COUNT(*) FROM users").Int()
// 查询字符串
email, err := o.Select("SELECT email FROM users WHERE id = #{Id}", 1).String()
// 查询多列不同类型的值
var id int64
var username string
_, err := o.Select("SELECT id, username FROM users WHERE id = #{Id}", 1).Value(&id, &username)
完整方法列表
1. Struct 和 Structs - 结构体查询
Struct - 查询单行数据并存入struct
var user User
_, err := o.Select(`SELECT * FROM users WHERE id = #{Id}`, 1).Struct(&user)
Structs - 查询多行数据并存入struct切片
var users []User
_, err := o.Select(`SELECT * FROM users`).Structs(&users)
2. Value 和 Values - 多列值查询
Value - 查询单行多列的值
var id int64
var email string
_, err := o.Select(`SELECT id, email FROM users WHERE id = #{Id}`, 1).Value(&id, &email)
Values - 查询多行多列的值
var ids []int64
var emails []string
_, err := o.Select(`SELECT id, email FROM users`).Values(&ids, &emails)
3. Kvs - 键值对查询
查询多行两列数据并存入map,第一列作为key,第二列作为value
var idEmailMap = map[int64]string{}
_, err := o.Select(`SELECT id, email FROM users`).Kvs(&idEmailMap)
4. ColumnsAndData - 列名和数据查询
查询多行数据,返回列名和数据(常用于数据交换)
columns, datas, err := o.Select(`SELECT id, email FROM users`).ColumnsAndData()
// columns 为 []string
// datas 为 [][]string
5. String 和 Strings - 字符串查询
String - 查询单个字符串值
email, err := o.Select(`SELECT email FROM users WHERE id = #{Id}`, 1).String()
Strings - 查询多个字符串值
emails, err := o.Select(`SELECT email FROM users`).Strings()
6. Int 和 Ints - 整数查询
Int - 查询单个int值
count, err := o.Select(`SELECT COUNT(*) FROM users`).Int()
Ints - 查询多个int值
ages, err := o.Select(`SELECT age FROM users`).Ints()
7. Int32 和 Int32s - 32位整数查询
Int32 - 查询单个int32值
count, err := o.Select(`SELECT count FROM table WHERE id = #{Id}`, 1).Int32()
Int32s - 查询多个int32值
counts, err := o.Select(`SELECT count FROM table`).Int32s()
8. Int64 和 Int64s - 64位整数查询
Int64 - 查询单个int64值
id, err := o.Select(`SELECT id FROM users WHERE email = #{Email}`, "test@example.com").Int64()
Int64s - 查询多个int64值
ids, err := o.Select(`SELECT id FROM users`).Int64s()
9. Uint 和 Uints - 无符号整数查询
Uint - 查询单个uint值
count, err := o.Select(`SELECT COUNT(*) FROM users`).Uint()
Uints - 查询多个uint值
counts, err := o.Select(`SELECT count FROM table`).Uints()
10. Uint64 和 Uint64s - 64位无符号整数查询
Uint64 - 查询单个uint64值
id, err := o.Select(`SELECT id FROM users WHERE email = #{Email}`, "test@example.com").Uint64()
Uint64s - 查询多个uint64值
ids, err := o.Select(`SELECT id FROM users`).Uint64s()
11. Float32 和 Float32s - 32位浮点数查询
Float32 - 查询单个float32值
price, err := o.Select(`SELECT price FROM products WHERE id = #{Id}`, 1).Float32()
Float32s - 查询多个float32值
prices, err := o.Select(`SELECT price FROM products`).Float32s()
12. Float64 和 Float64s - 64位浮点数查询
Float64 - 查询单个float64值
avg, err := o.Select(`SELECT AVG(score) FROM users`).Float64()
Float64s - 查询多个float64值
scores, err := o.Select(`SELECT score FROM users`).Float64s()
13. Bool 和 Bools - 布尔值查询
Bool - 查询单个布尔值
isActive, err := o.Select(`SELECT is_active FROM users WHERE id = #{Id}`, 1).Bool()
Bools - 查询多个布尔值
statuses, err := o.Select(`SELECT is_active FROM users`).Bools()
📊 方法分类总结
| 数据类型 | 单值方法 | 多值方法 | 典型用途 |
|---------|---------|---------|---------|
| 通用多列 | Value() | Values() | 查询多列不同类型的值 |
| 字符串 | String() | Strings() | 名称、邮箱等文本字段 |
| 整数 | Int() | Ints() | 计数、年龄等整数 |
| 32位整数 | Int32() | Int32s() | 小范围整数 |
| 64位整数 | Int64() | Int64s() | ID、大整数 |
| 无符号整数 | Uint() | Uints() | 正整数 |
| 64位无符号 | Uint64() | Uint64s() | 大范围正整数 |
| 32位浮点 | Float32() | Float32s() | 价格、比率等小精度 |
| 64位浮点 | Float64() | Float64s() | 科学计算、高精度数值 |
| 布尔值 | Bool() | Bools() | 状态标识、开关 |
| 结构体 | Struct() | Structs() | 完整对象映射 |
| 键值对 | - | Kvs() | 双列数据 → Map |
| 通用数据 | - | ColumnsAndData() | 数据导出、交换 |
⚠️ 重要说明
- 多列查询:
Value()和Values()方法支持查询多列不同类型的值,适用于查询不同数据类型的多个字段 - 零值处理: 单值方法在无结果时返回类型零值(
0,"",false) - 空切片: 多值方法在无结果时返回空切片
[] - 数据交换:
ColumnsAndData()返回的数据全部为字符串类型,适合跨语言数据交换 - 键值对:
Kvs()要求查询结果必须是两列(第一列为key,第二列为value)
🔄 事务支持
osm 提供了完整的事务支持,包括传统方式和闭包方式两种使用模式。
传统方式
传统方式需要手动开启事务、提交或回滚:
// 开启事务
tx, err := o.Begin()
if err != nil {
return err
}
// 执行插入操作
user := User{
EmailStruct: EmailStruct{Email: "test@foxmail.com"},
Nickname: "haha",
CreateTime: time.Now(),
}
insertID, count, err := tx.Insert("INSERT INTO user (email,nickname,create_time) VALUES (#{Email},#{Nickname},#{CreateTime});", user)
if err != nil {
tx.Rollback() // 发生错误时回滚
return err
}
// 执行更新操作
count, err = tx.Update("UPDATE user SET nickname=#{Nickname} WHERE id=#{ID}", "hello", insertID)
if err != nil {
tx.Rollback() // 发生错误时回滚
return err
}
//
