https://github.com/yinshuwei/osm
The lightweight Go language ORM tool uses only the Go standard library to implement SQL templates, data introspection, and supports MySQL and PostgreSQL.
https://github.com/yinshuwei/osm
database db go orm sql-mapping sqlmap
Last synced: 3 months ago
JSON representation
The lightweight Go language ORM tool uses only the Go standard library to implement SQL templates, data introspection, and supports MySQL and PostgreSQL.
- Host: GitHub
- URL: https://github.com/yinshuwei/osm
- Owner: yinshuwei
- License: mit
- Created: 2014-05-22T04:01:25.000Z (about 12 years ago)
- Default Branch: main
- Last Pushed: 2026-02-26T08:00:17.000Z (3 months ago)
- Last Synced: 2026-02-26T13:09:42.898Z (3 months ago)
- Topics: database, db, go, orm, sql-mapping, sqlmap
- Language: Go
- Homepage:
- Size: 219 KB
- Stars: 52
- Watchers: 6
- Forks: 10
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# OSM - Object SQL Mapping
[English](./README_EN.md) | 简体中文
osm (Object SQL Mapping) 是用 Go 编写的轻量级 SQL 工具库,已在生产环境中广泛使用。
**支持的数据库:** MySQL、PostgreSQL、SQL Server、SQLite、Oracle、TiDB、CockroachDB、ClickHouse
## ✨ 核心理念
提供极简且优雅的 SQL 操作接口,让数据库操作更加简单直观:
```go
// 链式调用风格
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 原生占位符示例:**
```go
// 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 原生占位符示例:**
```go
// 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 会自动根据数据库类型转换为正确的占位符格式,你无需关心底层数据库的差异。
```go
// 这段代码在所有数据库上都能正常工作
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)
- [查看完整的字段映射规则](#field_column_mapping)
### SQL 占位符替换
支持在 SQL 语句中使用占位符,在运行时自动替换为配置的值。这对于以下场景特别有用:
- **表前缀替换**: 在表名前添加统一前缀
- **数据库 Schema 切换**: 根据环境动态切换数据库 schema
- **环境标识**: 在 SQL 中插入环境相关的标识
**配置示例:**
```go
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
```
**使用示例:**
```go
// 单表查询
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 中可以多次使用同一个占位符
- ✅ 执行前替换:替换发生在参数解析之前,不影响 `#{...}` 参数绑定
## 📦 安装
```bash
go get github.com/yinshuwei/osm/v2
```
**go.mod:**
```go
require (
github.com/yinshuwei/osm/v2 v2.0.8
)
```
## 📖 API 文档
完整文档请访问: https://pkg.go.dev/github.com/yinshuwei/osm/v2
## 🔗 链式调用 API
osm 支持优雅的链式调用,通过 `Select()` 方法返回 `SelectResult` 对象,可灵活选择结果处理方式。
### 快速开始
```go
// 查询结构体列表
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
```go
var user User
_, err := o.Select(`SELECT * FROM users WHERE id = #{Id}`, 1).Struct(&user)
```
**Structs** - 查询多行数据并存入struct切片
```go
var users []User
_, err := o.Select(`SELECT * FROM users`).Structs(&users)
```
#### 2. Value 和 Values - 多列值查询
**Value** - 查询单行多列的值
```go
var id int64
var email string
_, err := o.Select(`SELECT id, email FROM users WHERE id = #{Id}`, 1).Value(&id, &email)
```
**Values** - 查询多行多列的值
```go
var ids []int64
var emails []string
_, err := o.Select(`SELECT id, email FROM users`).Values(&ids, &emails)
```
#### 3. Kvs - 键值对查询
查询多行两列数据并存入map,第一列作为key,第二列作为value
```go
var idEmailMap = map[int64]string{}
_, err := o.Select(`SELECT id, email FROM users`).Kvs(&idEmailMap)
```
#### 4. ColumnsAndData - 列名和数据查询
查询多行数据,返回列名和数据(常用于数据交换)
```go
columns, datas, err := o.Select(`SELECT id, email FROM users`).ColumnsAndData()
// columns 为 []string
// datas 为 [][]string
```
#### 5. String 和 Strings - 字符串查询
**String** - 查询单个字符串值
```go
email, err := o.Select(`SELECT email FROM users WHERE id = #{Id}`, 1).String()
```
**Strings** - 查询多个字符串值
```go
emails, err := o.Select(`SELECT email FROM users`).Strings()
```
#### 6. Int 和 Ints - 整数查询
**Int** - 查询单个int值
```go
count, err := o.Select(`SELECT COUNT(*) FROM users`).Int()
```
**Ints** - 查询多个int值
```go
ages, err := o.Select(`SELECT age FROM users`).Ints()
```
#### 7. Int32 和 Int32s - 32位整数查询
**Int32** - 查询单个int32值
```go
count, err := o.Select(`SELECT count FROM table WHERE id = #{Id}`, 1).Int32()
```
**Int32s** - 查询多个int32值
```go
counts, err := o.Select(`SELECT count FROM table`).Int32s()
```
#### 8. Int64 和 Int64s - 64位整数查询
**Int64** - 查询单个int64值
```go
id, err := o.Select(`SELECT id FROM users WHERE email = #{Email}`, "test@example.com").Int64()
```
**Int64s** - 查询多个int64值
```go
ids, err := o.Select(`SELECT id FROM users`).Int64s()
```
#### 9. Uint 和 Uints - 无符号整数查询
**Uint** - 查询单个uint值
```go
count, err := o.Select(`SELECT COUNT(*) FROM users`).Uint()
```
**Uints** - 查询多个uint值
```go
counts, err := o.Select(`SELECT count FROM table`).Uints()
```
#### 10. Uint64 和 Uint64s - 64位无符号整数查询
**Uint64** - 查询单个uint64值
```go
id, err := o.Select(`SELECT id FROM users WHERE email = #{Email}`, "test@example.com").Uint64()
```
**Uint64s** - 查询多个uint64值
```go
ids, err := o.Select(`SELECT id FROM users`).Uint64s()
```
#### 11. Float32 和 Float32s - 32位浮点数查询
**Float32** - 查询单个float32值
```go
price, err := o.Select(`SELECT price FROM products WHERE id = #{Id}`, 1).Float32()
```
**Float32s** - 查询多个float32值
```go
prices, err := o.Select(`SELECT price FROM products`).Float32s()
```
#### 12. Float64 和 Float64s - 64位浮点数查询
**Float64** - 查询单个float64值
```go
avg, err := o.Select(`SELECT AVG(score) FROM users`).Float64()
```
**Float64s** - 查询多个float64值
```go
scores, err := o.Select(`SELECT score FROM users`).Float64s()
```
#### 13. Bool 和 Bools - 布尔值查询
**Bool** - 查询单个布尔值
```go
isActive, err := o.Select(`SELECT is_active FROM users WHERE id = #{Id}`, 1).Bool()
```
**Bools** - 查询多个布尔值
```go
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 提供了完整的事务支持,包括传统方式和闭包方式两种使用模式。
### 传统方式
传统方式需要手动开启事务、提交或回滚:
```go
// 开启事务
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
}
// 提交事务
err = tx.Commit()
if err != nil {
return err
}
```
### 闭包方式(推荐)
闭包方式更加简洁,自动处理 commit 和 rollback:
```go
err := o.Transaction(func(tx *Tx) error {
// 执行插入操作
user := User{
EmailStruct: EmailStruct{Email: "test@foxmail.com"},
Nickname: "haha",
CreateTime: time.Now(),
}
insertID, _, err := tx.Insert("INSERT INTO user (email,nickname,create_time) VALUES (#{Email},#{Nickname},#{CreateTime});", user)
if err != nil {
return err // 返回错误会自动 rollback
}
// 执行更新操作
_, err = tx.Update("UPDATE user SET nickname=#{Nickname} WHERE id=#{ID}", "hello", insertID)
if err != nil {
return err // 返回错误会自动 rollback
}
// 查询操作
var result User
_, err = tx.Select("SELECT * FROM user WHERE id = #{ID}", insertID).Struct(&result)
if err != nil {
return err // 返回错误会自动 rollback
}
return nil // 返回 nil 会自动 commit
})
if err != nil {
logger.Error("transaction error", zap.Error(err))
}
```
**闭包方式的优势:**
- ✅ **自动提交**: 闭包函数返回 `nil` 时自动执行 `Commit()`
- ✅ **自动回滚**: 闭包函数返回 `error` 时自动执行 `Rollback()`
- ✅ **异常安全**: 即使闭包中发生 `panic`,也会先执行 `Rollback()` 再抛出 panic
- ✅ **简化代码**: 无需在每次操作后检查错误并手动回滚
- ✅ **保持一致性**: 闭包中直接使用 `*Tx` 对象,API 与传统方式完全一致
**事务中支持的操作:**
在事务中(无论是传统方式还是闭包方式),都可以使用所有数据库操作方法:
- **查询操作**: `Select()`, `SelectStruct()`, `SelectStructs()`, `SelectValue()`, `SelectValues()`, `SelectKVS()`, `SelectStrings()` 等
- **写操作**: `Insert()`, `Update()`, `UpdateMulti()`, `Delete()`
- **链式调用**: 所有 `Select()` 返回的 `SelectResult` 方法(`.Struct()`, `.Int()`, `.String()` 等)
## 💡 完整示例
### 数据库准备
```sql
CREATE DATABASE test;
USE test;
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`email` varchar(255) DEFAULT NULL,
`nickname` varchar(45) DEFAULT NULL,
`create_time` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户表';
```
### 示例代码
**基础示例 (osm_demo.go)**
```go
package main
import (
"encoding/json"
"fmt"
"time"
_ "github.com/go-sql-driver/mysql"
"github.com/yinshuwei/osm/v2"
"go.uber.org/zap"
)
// InfoLogger 适配zap logger
type InfoLogger struct {
zapLogger *zap.Logger
}
// WarnLoggor 适配zap logger
type WarnLoggor struct {
zapLogger *zap.Logger
}
// ErrorLogger 适配zap logger
type ErrorLogger struct {
zapLogger *zap.Logger
}
func loggerFields(data map[string]string) []zap.Field {
var fields []zap.Field
for key, val := range data {
fields = append(fields, zap.String(key, val))
}
return fields
}
func (l *ErrorLogger) Log(msg string, data map[string]string) {
if l == nil || l.zapLogger == nil {
return
}
l.zapLogger.Error(msg, loggerFields(data)...)
}
func (l *InfoLogger) Log(msg string, data map[string]string) {
if l == nil || l.zapLogger == nil {
return
}
l.zapLogger.Info(msg, loggerFields(data)...)
}
func (l *WarnLoggor) Log(msg string, data map[string]string) {
if l == nil || l.zapLogger == nil {
return
}
l.zapLogger.Warn(msg, loggerFields(data)...)
}
// User 用户Model
type User struct {
ID int64
Nickname string `db:"name"`
CreateTime time.Time
EmailStruct // 匿名属性
}
type EmailStruct struct {
Email string `db:"email"`
}
func main() {
logger, _ := zap.NewDevelopment()
o, err := osm.New("mysql", "root:123456@/test?charset=utf8", osm.Options{
MaxIdleConns: 0, // int
MaxOpenConns: 0, // int
ConnMaxLifetime: 0, // time.Duration
ConnMaxIdleTime: 0, // time.Duration
WarnLogger: &WarnLoggor{logger}, // Logger
ErrorLogger: &ErrorLogger{logger}, // Logger
InfoLogger: &InfoLogger{logger}, // Logger
ShowSQL: true, // bool
SlowLogDuration: 0, // time.Duration
SQLReplacements: map[string]string{ // SQL替换映射(可选)
"[TablePrefix]": "data_", // 表前缀
"[Schema]": "prod", // 数据库schema
},
})
if err != nil {
fmt.Println(err.Error())
}
// 插入数据
user := User{
EmailStruct: EmailStruct{
Email: "test@foxmail.com",
},
Nickname: "haha",
CreateTime: time.Now(),
}
id, count, err := o.Insert("INSERT INTO user (email,nickname,create_time) VALUES (#{Email},#{Nickname},#{CreateTime});", user)
if err != nil {
logger.Error("insert error", zap.Error(err))
}
logger.Info("test insert", zap.Int64("id", id), zap.Int64("count", count))
// 更新数据
user = User{
EmailStruct: EmailStruct{
Email: "test@foxmail.com",
},
Nickname: "hello",
}
count, err = o.Update("UPDATE user SET nickname=#{name} WHERE email=#{Email}", user)
if err != nil {
logger.Error("update error", zap.Error(err))
}
logger.Info("test update", zap.Int64("count", count))
// 查询数据
user = User{
EmailStruct: EmailStruct{
Email: "test@foxmail.com",
},
}
var results []User
count, err = o.SelectStructs("SELECT id,email,nickname,create_time FROM user WHERE email=#{Email} or email=#{email};", user)(&results)
if err != nil {
logger.Error("test select", zap.Error(err))
}
resultBytes, _ := json.Marshal(results)
logger.Info("test select", zap.Int64("count", count), zap.ByteString("result", resultBytes))
// 删除数据
count, err = o.Delete("DELETE FROM user WHERE email=#{Email}", user)
if err != nil {
logger.Error("test delete", zap.Error(err))
}
logger.Info("test delete", zap.Int64("count", count))
// 关闭连接
err = o.Close()
if err != nil {
logger.Error("close", zap.Error(err))
}
}
```
**运行结果:**
```log
2025-01-13T16:16:41.301+0800 INFO osmtt/main.go:47 main.go:95, readSQLParamsBySQL showSql {"dbParams": "[\"test@foxmail.com\",\"haha\",\"2025-01-13 16:16:41\"]", "sql": "INSERT INTO user (email,nickname,create_time) VALUES (#{Email},#{Nickname},#{CreateTime});", "params": "{\"ID\":0,\"Nickname\":\"haha\",\"CreateTime\":\"2025-01-13T16:16:41.301032455+08:00\",\"Email\":\"test@foxmail.com\"}", "dbSql": "INSERT INTO user (email,nickname,create_time) VALUES (?,?,?);"}
2025-01-13T16:16:41.305+0800 INFO osmtt/main.go:99 test insert {"id": 11, "count": 1}
2025-01-13T16:16:41.305+0800 INFO osmtt/main.go:47 main.go:108, readSQLParamsBySQL showSql {"dbParams": "[\"hello\",\"test@foxmail.com\"]", "sql": "UPDATE user SET nickname=#{name} WHERE email=#{Email}", "params": "{\"ID\":0,\"Nickname\":\"hello\",\"CreateTime\":\"0001-01-01T00:00:00Z\",\"Email\":\"test@foxmail.com\"}", "dbSql": "UPDATE user SET nickname=? WHERE email=?"}
2025-01-13T16:16:41.310+0800 INFO osmtt/main.go:112 test update {"count": 1}
2025-01-13T16:16:41.310+0800 INFO osmtt/main.go:47 main.go:121, readSQLParamsBySQL showSql {"params": "{\"ID\":0,\"Nickname\":\"\",\"CreateTime\":\"0001-01-01T00:00:00Z\",\"Email\":\"test@foxmail.com\"}", "dbSql": "SELECT id,email,nickname,create_time FROM user WHERE email=? or email=?;", "dbParams": "[\"test@foxmail.com\",\"test@foxmail.com\"]", "sql": "SELECT id,email,nickname,create_time FROM user WHERE email=#{Email} or email=#{email};"}
2025-01-13T16:16:41.310+0800 INFO osmtt/main.go:126 test select {"count": 1, "result": "[{\"ID\":11,\"Nickname\":\"hello\",\"CreateTime\":\"2025-01-13T16:16:41+08:00\",\"Email\":\"test@foxmail.com\"}]"}
2025-01-13T16:16:41.310+0800 INFO osmtt/main.go:47 main.go:129readSQLParamsBySQL showSql {"dbParams": "[\"test@foxmail.com\"]", "sql": "DELETE FROM user WHERE email=#{Email}", "params": "{\"ID\":0,\"Nickname\":\"\",\"CreateTime\":\"0001-01-01T00:00:00Z\",\"Email\":\"test@foxmail.com\"}", "dbSql": "DELETE FROM user WHERE email=?"}
2025-01-13T16:16:41.313+0800 INFO osmtt/main.go:133 test delete {"count": 1}
```
### 指针类型示例
**指针类型支持 NULL (osm_demo2.go)**
```go
package main
import (
"encoding/json"
"fmt"
"time"
_ "github.com/go-sql-driver/mysql"
"github.com/yinshuwei/osm/v2"
"go.uber.org/zap"
)
// InfoLogger 适配zap logger
type InfoLogger struct {
zapLogger *zap.Logger
}
// WarnLoggor 适配zap logger
type WarnLoggor struct {
zapLogger *zap.Logger
}
// ErrorLogger 适配zap logger
type ErrorLogger struct {
zapLogger *zap.Logger
}
func loggerFields(data map[string]string) []zap.Field {
var fields []zap.Field
for key, val := range data {
fields = append(fields, zap.String(key, val))
}
return fields
}
func (l *ErrorLogger) Log(msg string, data map[string]string) {
if l == nil || l.zapLogger == nil {
return
}
l.zapLogger.Error(msg, loggerFields(data)...)
}
func (l *InfoLogger) Log(msg string, data map[string]string) {
if l == nil || l.zapLogger == nil {
return
}
l.zapLogger.Info(msg, loggerFields(data)...)
}
func (l *WarnLoggor) Log(msg string, data map[string]string) {
if l == nil || l.zapLogger == nil {
return
}
l.zapLogger.Warn(msg, loggerFields(data)...)
}
// User 用户Model
type User struct {
ID *int64
Email *string
Nickname *string
CreateTime *time.Time
}
func stringPoint(t string) *string {
return &t
}
func timePoint(t time.Time) *time.Time {
return &t
}
func main() {
logger, _ := zap.NewDevelopment()
o, err := osm.New("mysql", "root:123456@/test?charset=utf8", osm.Options{
MaxIdleConns: 0, // int
MaxOpenConns: 0, // int
ConnMaxLifetime: 0, // time.Duration
ConnMaxIdleTime: 0, // time.Duration
WarnLogger: &WarnLoggor{logger}, // Logger
ErrorLogger: &ErrorLogger{logger}, // Logger
InfoLogger: &InfoLogger{logger}, // Logger
ShowSQL: true, // bool
SlowLogDuration: 0, // time.Duration
SQLReplacements: map[string]string{ // SQL替换映射(可选)
"[TablePrefix]": "data_",
"[Schema]": "prod",
},
})
if err != nil {
fmt.Println(err.Error())
}
// 插入数据(Nickname 为 nil,表示 NULL)
{
user := User{
Email: stringPoint("test@foxmail.com"),
Nickname: nil, // NULL 值
CreateTime: timePoint(time.Now()),
}
id, count, err := o.Insert("INSERT INTO user (email,nickname,create_time) VALUES (#{Email},#{Nickname},#{CreateTime});", user)
if err != nil {
logger.Error("insert error", zap.Error(err))
}
logger.Info("test insert", zap.Int64("id", id), zap.Int64("count", count))
}
// 查询数据
{
user := User{
Email: stringPoint("test@foxmail.com"),
}
var results []User
count, err := o.SelectStructs("SELECT id,email,nickname,create_time FROM user WHERE email=#{Email};", user)(&results)
if err != nil {
logger.Error("test select", zap.Error(err))
}
resultBytes, _ := json.Marshal(results)
logger.Info("test select", zap.Int64("count", count), zap.ByteString("result", resultBytes))
}
// 更新数据
{
user := User{
Email: stringPoint("test@foxmail.com"),
Nickname: stringPoint("hello"),
}
count, err := o.Update("UPDATE user SET nickname=#{Nickname} WHERE email=#{Email}", user)
if err != nil {
logger.Error("update error", zap.Error(err))
}
logger.Info("test update", zap.Int64("count", count))
}
// 再次查询验证
{
user := User{
Email: stringPoint("test@foxmail.com"),
}
var results []User
count, err := o.SelectStructs("SELECT id,email,nickname,create_time FROM user WHERE email=#{Email};", user)(&results)
if err != nil {
logger.Error("test select", zap.Error(err))
}
resultBytes, _ := json.Marshal(results)
logger.Info("test select", zap.Int64("count", count), zap.ByteString("result", resultBytes))
}
// 删除数据
{
user := User{
Email: stringPoint("test@foxmail.com"),
}
count, err := o.Delete("DELETE FROM user WHERE email=#{Email}", user)
if err != nil {
logger.Error("test delete", zap.Error(err))
}
logger.Info("test delete", zap.Int64("count", count))
}
// 关闭连接
{
err = o.Close()
if err != nil {
logger.Error("close", zap.Error(err))
}
}
}
```
**运行结果:**
```log
2022-02-21T11:42:44.591+0800 INFO v2@v2.0.2/sql.go:311 readSQLParamsBySQL showSql, sql: INSERT INTO user (email,nickname,create_time) VALUES (#{Email},#{Nickname},#{CreateTime});, params: {"ID":null,"Email":"test@foxmail.com","Nickname":null,"CreateTime":"2022-02-21T11:42:44.591619385+08:00"}, dbSql: INSERT INTO user (email,nickname,create_time) VALUES (?,?,?);, dbParams: ["test@foxmail.com",null,"2022-02-21T11:42:44.591619385+08:00"]
2022-02-21T11:42:44.596+0800 INFO osm_demo/main.go:48 test insert {"id": 10, "count": 1}
2022-02-21T11:42:44.596+0800 INFO v2@v2.0.2/sql.go:311 readSQLParamsBySQL showSql, sql: SELECT id,email,nickname,create_time FROM user WHERE email=#{Email};, params: {"ID":null,"Email":"test@foxmail.com","Nickname":null,"CreateTime":null}, dbSql: SELECT id,email,nickname,create_time FROM user WHERE email=?;, dbParams: ["test@foxmail.com"]
2022-02-21T11:42:44.597+0800 INFO osm_demo/main.go:61 test select {"count": 1, "result": "[{\"ID\":10,\"Email\":\"test@foxmail.com\",\"Nickname\":\"\",\"CreateTime\":\"2022-02-21T03:42:44+08:00\"}]"}
2022-02-21T11:42:44.597+0800 INFO v2@v2.0.2/sql.go:311 readSQLParamsBySQL showSql, sql: UPDATE user SET nickname=#{Nickname} WHERE email=#{Email}, params: {"ID":null,"Email":"test@foxmail.com","Nickname":"hello","CreateTime":null}, dbSql: UPDATE user SET nickname=? WHERE email=?, dbParams: ["hello","test@foxmail.com"]
2022-02-21T11:42:44.598+0800 INFO osm_demo/main.go:73 test update {"count": 1}
2022-02-21T11:42:44.598+0800 INFO v2@v2.0.2/sql.go:311 readSQLParamsBySQL showSql, sql: SELECT id,email,nickname,create_time FROM user WHERE email=#{Email};, params: {"ID":null,"Email":"test@foxmail.com","Nickname":null,"CreateTime":null}, dbSql: SELECT id,email,nickname,create_time FROM user WHERE email=?;, dbParams: ["test@foxmail.com"]
2022-02-21T11:42:44.599+0800 INFO osm_demo/main.go:86 test select {"count": 1, "result": "[{\"ID\":10,\"Email\":\"test@foxmail.com\",\"Nickname\":\"hello\",\"CreateTime\":\"2022-02-21T03:42:44+08:00\"}]"}
2022-02-21T11:42:44.600+0800 INFO v2@v2.0.2/sql.go:311 readSQLParamsBySQL showSql, sql: DELETE FROM user WHERE email=#{Email}, params: {"ID":null,"Email":"test@foxmail.com","Nickname":null,"CreateTime":null}, dbSql: DELETE FROM user WHERE email=?, dbParams: ["test@foxmail.com"]
2022-02-21T11:42:44.603+0800 INFO osm_demo/main.go:97 test delete {"count": 1}
```
### 自动转换规则
SQL 列名会自动转换为 Go 结构体字段名,转换过程如下:
1. **分隔**: 用 `_` 分隔列名
- 例: `user_email` → `user`, `email`
2. **首字母大写**: 每个部分转为首字母大写,其余小写
- 例: `user`, `email` → `User`, `Email`
3. **拼接**: 拼接所有部分
- 例: `User`, `Email` → `UserEmail`
**示例:**
```
user_name → UserName
create_time → CreateTime
user_id → UserId 或 UserID
```
### 常见缩写词支持
以下缩写词支持两种形式(大小写不敏感),可在结构体中任选一种:
**示例:** `user_id` 列可映射到 `UserId` 或 `UserID` 字段
> ⚠️ **注意**: 同一结构体中不能同时包含两种形式(如同时有 `UserId` 和 `UserID`),否则只有一个会被赋值。
**支持的缩写词列表:**
```
Acl 或 ACL
Api 或 API
Ascii 或 ASCII
Cpu 或 CPU
Css 或 CSS
Dns 或 DNS
Eof 或 EOF
Guid 或 GUID
Html 或 HTML
Http 或 HTTP
Https 或 HTTPS
Id 或 ID
Ip 或 IP
Json 或 JSON
Lhs 或 LHS
Qps 或 QPS
Ram 或 RAM
Rhs 或 RHS
Rpc 或 RPC
Sla 或 SLA
Smtp 或 SMTP
Sql 或 SQL
Ssh 或 SSH
Tcp 或 TCP
Tls 或 TLS
Ttl 或 TTL
Udp 或 UDP
Ui 或 UI
Uid 或 UID
Uuid 或 UUID
Uri 或 URI
Url 或 URL
Utf8 或 UTF8
Vm 或 VM
Xml 或 XML
Xmpp 或 XMPP
Xsrf 或 XSRF
Xss 或 XSS
```
### 使用 db 标签
可以使用 `db` 标签显式指定字段与列的映射关系,标签优先级最高:
```go
type User struct {
ID int64 `db:"user_id"` // 显式映射到 user_id 列
Name string `db:"user_name"` // 显式映射到 user_name 列
Email string // 自动映射到 email 列
IsActive bool `db:"is_active"` // 显式映射到 is_active 列
}
```
## 🤝 贡献
欢迎提交 Issue 和 Pull Request!
## 📄 许可证
本项目采用 MIT 许可证 - 详见 [LICENSE](LICENSE) 文件
---
**如果这个项目对你有帮助,请给个 ⭐️ Star 支持一下!**