19 KiB
19 KiB
HoTimeDB ORM 使用说明书
概述
HoTimeDB是一个基于Golang实现的轻量级ORM框架,参考PHP Medoo设计,提供简洁的数据库操作接口。支持MySQL、SQLite等数据库,并集成了缓存、事务、链式查询等功能。
目录
快速开始
初始化数据库连接
import (
"code.hoteas.com/golang/hotime/db"
"code.hoteas.com/golang/hotime/common"
"database/sql"
_ "github.com/go-sql-driver/mysql"
)
// 创建连接函数
func createConnection() (master, slave *sql.DB) {
master, _ = sql.Open("mysql", "user:password@tcp(localhost:3306)/database")
// slave是可选的,用于读写分离
slave = master // 或者连接到从数据库
return
}
// 初始化HoTimeDB
db := &db.HoTimeDB{}
db.SetConnect(createConnection)
数据库配置
基本配置
type HoTimeDB struct {
*sql.DB
ContextBase
DBName string
*cache.HoTimeCache
Log *logrus.Logger
Type string // 数据库类型
Prefix string // 表前缀
LastQuery string // 最后执行的SQL
LastData []interface{} // 最后的参数
ConnectFunc func(err ...*Error) (*sql.DB, *sql.DB)
LastErr *Error
limit Slice
*sql.Tx // 事务对象
SlaveDB *sql.DB // 从数据库
Mode int // 0生产模式,1测试模式,2开发模式
}
设置表前缀
db.Prefix = "app_"
设置运行模式
db.Mode = 2 // 开发模式,会输出SQL日志
基本操作
查询(Select)
基本查询
// 查询所有字段
users := db.Select("user")
// 查询指定字段
users := db.Select("user", "id,name,email")
// 查询指定字段(数组形式)
users := db.Select("user", []string{"id", "name", "email"})
// 单条件查询
users := db.Select("user", "*", common.Map{
"status": 1,
})
// 多条件查询(必须使用AND包装)
users = db.Select("user", "*", common.Map{
"AND": common.Map{
"status": 1,
"age[>]": 18,
},
})
复杂条件查询
重要说明:多个条件必须使用AND或OR包装,不能直接在根Map中写多个字段条件
// AND条件(多个条件必须用AND包装)
users := db.Select("user", "*", common.Map{
"AND": common.Map{
"status": 1,
"age[>]": 18,
"name[~]": "张",
},
})
// OR条件
users := db.Select("user", "*", common.Map{
"OR": common.Map{
"status": 1,
"type": 2,
},
})
// 混合条件(嵌套AND/OR)
users := db.Select("user", "*", common.Map{
"AND": common.Map{
"status": 1,
"OR": common.Map{
"age[<]": 30,
"level[>]": 5,
},
},
})
// 带ORDER BY、LIMIT等特殊条件
users := db.Select("user", "*", common.Map{
"AND": common.Map{
"status": 1,
"age[>]": 18,
},
"ORDER": "id DESC",
"LIMIT": 10,
})
// 带多个特殊条件
users := db.Select("user", "*", common.Map{
"OR": common.Map{
"level": "vip",
"balance[>]": 1000,
},
"ORDER": []string{"created_time DESC", "id ASC"},
"GROUP": "department",
"LIMIT": []int{0, 20}, // offset 0, limit 20
})
获取单条记录(Get)
// 获取单个用户
user := db.Get("user", "*", common.Map{
"id": 1,
})
// 获取指定字段
user := db.Get("user", "id,name,email", common.Map{
"status": 1,
})
插入(Insert)
// 基本插入
id := db.Insert("user", common.Map{
"name": "张三",
"email": "zhangsan@example.com",
"age": 25,
"status": 1,
"created_time[#]": "NOW()", // [#]表示直接插入SQL函数
})
// 返回插入的ID
fmt.Println("插入的用户ID:", id)
更新(Update)
// 基本更新
affected := db.Update("user", common.Map{
"name": "李四",
"email": "lisi@example.com",
"updated_time[#]": "NOW()",
}, common.Map{
"id": 1,
})
// 条件更新
affected := db.Update("user", common.Map{
"status": 0,
}, common.Map{
"age[<]": 18,
"status": 1,
})
fmt.Println("更新的记录数:", affected)
删除(Delete)
// 根据ID删除
affected := db.Delete("user", common.Map{
"id": 1,
})
// 条件删除
affected := db.Delete("user", common.Map{
"status": 0,
"created_time[<]": "2023-01-01",
})
fmt.Println("删除的记录数:", affected)
链式查询构建器
HoTimeDB提供了链式查询构建器,让查询更加直观:
// 基本链式查询
users := db.Table("user").
Where("status", 1).
And("age[>]", 18).
Order("created_time DESC").
Limit(10, 20). // offset, limit
Select()
// 链式获取单条记录
user := db.Table("user").
Where("id", 1).
Get()
// 链式更新
affected := db.Table("user").
Where("id", 1).
Update(common.Map{
"name": "新名称",
"updated_time[#]": "NOW()",
})
// 链式删除
affected := db.Table("user").
Where("status", 0).
Delete()
// 链式统计
count := db.Table("user").
Where("status", 1).
Count()
链式条件组合
// 复杂条件组合
users := db.Table("user").
Where("status", 1).
And("age[>=]", 18).
Or(common.Map{
"level[>]": 5,
"vip": 1,
}).
Order("created_time DESC", "id ASC").
Group("department").
Limit(0, 20).
Select("id,name,email,age")
条件查询语法
HoTimeDB支持丰富的条件查询语法,类似于Medoo:
基本比较
// 等于
"id": 1
// 不等于
"id[!]": 1
// 大于
"age[>]": 18
// 大于等于
"age[>=]": 18
// 小于
"age[<]": 60
// 小于等于
"age[<=]": 60
模糊查询
// LIKE %keyword%
"name[~]": "张"
// LIKE keyword% (右边任意)
"name[~!]": "张"
// LIKE %keyword (左边任意)
"name[!~]": "san"
// 手动LIKE(需要手动添加%)
"name[~~]": "%张%"
区间查询
// BETWEEN
"age[<>]": []int{18, 60}
// NOT BETWEEN
"age[><]": []int{18, 25}
IN查询
// IN
"id": []int{1, 2, 3, 4, 5}
// NOT IN
"id[!]": []int{1, 2, 3}
NULL查询
// IS NULL
"deleted_at": nil
// IS NOT NULL
"deleted_at[!]": nil
直接SQL
// 直接插入SQL表达式(注意防注入)
"created_time[#]": "> DATE_SUB(NOW(), INTERVAL 1 DAY)"
// 字段直接赋值(不使用参数化查询)
"update_time[#]": "NOW()"
// 直接SQL片段
"[##]": "user.status = 1 AND user.level > 0"
JOIN操作
链式JOIN
// LEFT JOIN
users := db.Table("user").
LeftJoin("profile", "user.id = profile.user_id").
LeftJoin("department", "user.dept_id = department.id").
Where("user.status", 1).
Select("user.*, profile.avatar, department.name AS dept_name")
// RIGHT JOIN
users := db.Table("user").
RightJoin("order", "user.id = order.user_id").
Select()
// INNER JOIN
users := db.Table("user").
InnerJoin("profile", "user.id = profile.user_id").
Select()
// FULL JOIN
users := db.Table("user").
FullJoin("profile", "user.id = profile.user_id").
Select()
传统JOIN语法
users := db.Select("user",
common.Slice{
common.Map{"[>]profile": "user.id = profile.user_id"},
common.Map{"[>]department": "user.dept_id = department.id"},
},
"user.*, profile.avatar, department.name AS dept_name",
common.Map{
"user.status": 1,
},
)
JOIN类型说明
[>]
: LEFT JOIN[<]
: RIGHT JOIN[><]
: INNER JOIN[<>]
: FULL JOIN
分页查询
基本分页
// 设置分页:页码3,每页20条
users := db.Page(3, 20).PageSelect("user", "*", common.Map{
"status": 1,
})
// 链式分页
users := db.Table("user").
Where("status", 1).
Page(2, 15). // 第2页,每页15条
Select()
分页信息获取
// 获取总数
total := db.Count("user", common.Map{
"status": 1,
})
// 计算分页信息
page := 2
pageSize := 20
offset := (page - 1) * pageSize
totalPages := (total + pageSize - 1) / pageSize
fmt.Printf("总记录数: %d, 总页数: %d, 当前页: %d\n", total, totalPages, page)
聚合函数
计数
// 总数统计
total := db.Count("user")
// 条件统计
activeUsers := db.Count("user", common.Map{
"status": 1,
})
// JOIN统计
count := db.Count("user",
common.Slice{
common.Map{"[>]profile": "user.id = profile.user_id"},
},
common.Map{
"user.status": 1,
"profile.verified": 1,
},
)
求和
// 基本求和
totalAmount := db.Sum("order", "amount")
// 条件求和
paidAmount := db.Sum("order", "amount", common.Map{
"status": "paid",
"created_time[>]": "2023-01-01",
})
// JOIN求和
sum := db.Sum("order", "amount",
common.Slice{
common.Map{"[>]user": "order.user_id = user.id"},
},
common.Map{
"user.level": "vip",
"order.status": "paid",
},
)
事务处理
// 事务操作
success := db.Action(func(tx db.HoTimeDB) bool {
// 在事务中执行多个操作
// 扣减用户余额
affected1 := tx.Update("user", common.Map{
"balance[#]": "balance - 100",
}, common.Map{
"id": 1,
})
if affected1 == 0 {
return false // 回滚
}
// 创建订单
orderId := tx.Insert("order", common.Map{
"user_id": 1,
"amount": 100,
"status": "paid",
"created_time[#]": "NOW()",
})
if orderId == 0 {
return false // 回滚
}
// 添加订单详情
detailId := tx.Insert("order_detail", common.Map{
"order_id": orderId,
"product_id": 1001,
"quantity": 1,
"price": 100,
})
if detailId == 0 {
return false // 回滚
}
return true // 提交
})
if success {
fmt.Println("事务执行成功")
} else {
fmt.Println("事务回滚")
fmt.Println("错误:", db.LastErr.GetError())
}
缓存机制
HoTimeDB集成了缓存功能,可以自动缓存查询结果:
缓存配置
import "code.hoteas.com/golang/hotime/cache"
// 设置缓存
db.HoTimeCache = &cache.HoTimeCache{
// 缓存配置
}
缓存行为
- 查询操作会自动检查缓存
- 增删改操作会自动清除相关缓存
- 缓存键格式:
表名:查询MD5
cached
表不会被缓存
缓存清理
// 手动清除表缓存
db.HoTimeCache.Db("user*", nil) // 清除user表所有缓存
高级特性
调试模式
// 设置调试模式
db.Mode = 2
// 查看最后执行的SQL
fmt.Println("最后的SQL:", db.LastQuery)
fmt.Println("参数:", db.LastData)
fmt.Println("错误:", db.LastErr.GetError())
主从分离
func createConnection() (master, slave *sql.DB) {
// 主库连接
master, _ = sql.Open("mysql", "user:password@tcp(master:3306)/database")
// 从库连接
slave, _ = sql.Open("mysql", "user:password@tcp(slave:3306)/database")
return master, slave
}
db.SetConnect(createConnection)
// 查询会自动使用从库,增删改使用主库
原生SQL执行
// 执行查询SQL
results := db.Query("SELECT * FROM user WHERE age > ? AND status = ?", 18, 1)
// 执行更新SQL
result, err := db.Exec("UPDATE user SET last_login = NOW() WHERE id = ?", 1)
if err.GetError() == nil {
affected, _ := result.RowsAffected()
fmt.Println("影响行数:", affected)
}
数据类型处理
// 时间戳插入
db.Insert("log", common.Map{
"user_id": 1,
"action": "login",
"created_time[#]": "UNIX_TIMESTAMP()",
})
// JSON数据
db.Insert("user", common.Map{
"name": "张三",
"preferences": `{"theme": "dark", "language": "zh-CN"}`,
})
数据行处理
HoTimeDB内部的Row
方法会自动处理不同数据类型:
// 自动转换[]uint8为字符串
// 保持其他类型的原始值
// 处理NULL值
错误处理
// 检查错误
users := db.Select("user", "*", common.Map{"status": 1})
if db.LastErr.GetError() != nil {
fmt.Println("查询错误:", db.LastErr.GetError())
return
}
// 插入时检查错误
id := db.Insert("user", common.Map{
"name": "test",
"email": "test@example.com",
})
if id == 0 && db.LastErr.GetError() != nil {
fmt.Println("插入失败:", db.LastErr.GetError())
}
性能优化
IN查询优化
HoTimeDB会自动优化IN查询,将连续的数字转换为BETWEEN查询以提高性能:
// 这个查询会被自动优化(单个IN条件)
users := db.Select("user", "*", common.Map{
"id": []int{1, 2, 3, 4, 5, 10, 11, 12},
// 自动转为 (id BETWEEN 1 AND 5) OR (id BETWEEN 10 AND 12)
})
批量操作
// 使用事务进行批量插入
success := db.Action(func(tx db.HoTimeDB) bool {
for i := 0; i < 1000; i++ {
id := tx.Insert("user", common.Map{
"name": fmt.Sprintf("User%d", i),
"email": fmt.Sprintf("user%d@example.com", i),
"created_time[#]": "NOW()",
})
if id == 0 {
return false
}
}
return true
})
特殊语法详解
条件标记符说明
标记符 | 功能 | 示例 | 生成SQL |
---|---|---|---|
[>] |
大于 | "age[>]": 18 |
age > 18 |
[<] |
小于 | "age[<]": 60 |
age < 60 |
[>=] |
大于等于 | "age[>=]": 18 |
age >= 18 |
[<=] |
小于等于 | "age[<=]": 60 |
age <= 60 |
[!] |
不等于/NOT IN | "id[!]": 1 |
id != 1 |
[~] |
LIKE模糊查询 | "name[~]": "张" |
name LIKE '%张%' |
[!~] |
左模糊 | "name[!~]": "张" |
name LIKE '%张' |
[~!] |
右模糊 | "name[~!]": "张" |
name LIKE '张%' |
[~~] |
手动LIKE | "name[~~]": "%张%" |
name LIKE '%张%' |
[<>] |
BETWEEN | "age[<>]": [18,60] |
age BETWEEN 18 AND 60 |
[><] |
NOT BETWEEN | "age[><]": [18,25] |
age NOT BETWEEN 18 AND 25 |
[#] |
直接SQL | "time[#]": "NOW()" |
time = NOW() |
[##] |
SQL片段 | "[##]": "a > b" |
a > b |
[#!] |
不等于直接SQL | "status[#!]": "1" |
status != 1 |
[!#] |
不等于直接SQL | "status[!#]": "1" |
status != 1 |
与PHP Medoo的差异
- 类型系统: Golang的强类型要求使用
common.Map
和common.Slice
- 语法差异: 某些条件语法可能略有不同
- 错误处理: 使用Golang的错误处理模式
- 并发安全: 需要注意并发使用时的安全性
- 缓存集成: 内置了缓存功能
- 链式调用: 提供了更丰富的链式API
完整示例
package main
import (
"fmt"
"database/sql"
"code.hoteas.com/golang/hotime/db"
"code.hoteas.com/golang/hotime/common"
_ "github.com/go-sql-driver/mysql"
)
func main() {
// 初始化数据库
database := &db.HoTimeDB{
Prefix: "app_",
Mode: 2, // 开发模式
}
database.SetConnect(func(err ...*common.Error) (master, slave *sql.DB) {
master, _ = sql.Open("mysql", "root:password@tcp(localhost:3306)/testdb")
return master, master
})
// 查询用户列表
users := database.Table("user").
Where("status", 1).
And("age[>=]", 18).
Order("created_time DESC").
Limit(0, 10).
Select("id,name,email,age")
for _, user := range users {
fmt.Printf("用户: %s, 邮箱: %s, 年龄: %v\n",
user.GetString("name"),
user.GetString("email"),
user.Get("age"))
}
// 创建新用户
userId := database.Insert("user", common.Map{
"name": "新用户",
"email": "new@example.com",
"age": 25,
"status": 1,
"created_time[#]": "NOW()",
})
fmt.Printf("创建用户ID: %d\n", userId)
// 更新用户
affected := database.Table("user").
Where("id", userId).
Update(common.Map{
"last_login[#]": "NOW()",
"login_count[#]": "login_count + 1",
})
fmt.Printf("更新记录数: %d\n", affected)
// 复杂查询示例
orders := database.Table("order").
LeftJoin("user", "order.user_id = user.id").
LeftJoin("product", "order.product_id = product.id").
Where("order.status", "paid").
And("order.created_time[>]", "2023-01-01").
And(common.Map{
"OR": common.Map{
"user.level": "vip",
"order.amount[>]": 1000,
},
}).
Order("order.created_time DESC").
Group("user.id").
Select("user.name, user.email, SUM(order.amount) as total_amount, COUNT(*) as order_count")
// 事务示例
success := database.Action(func(tx db.HoTimeDB) bool {
// 在事务中执行操作
orderId := tx.Insert("order", common.Map{
"user_id": userId,
"total": 100.50,
"status": "pending",
"created_time[#]": "NOW()",
})
if orderId == 0 {
return false
}
detailId := tx.Insert("order_detail", common.Map{
"order_id": orderId,
"product_id": 1,
"quantity": 2,
"price": 50.25,
})
return detailId > 0
})
if success {
fmt.Println("订单创建成功")
} else {
fmt.Println("订单创建失败:", database.LastErr.GetError())
}
// 统计示例
totalUsers := database.Count("user", common.Map{"status": 1})
totalAmount := database.Sum("order", "amount", common.Map{"status": "paid"})
fmt.Printf("活跃用户数: %d, 总交易额: %.2f\n", totalUsers, totalAmount)
}
常见问题
Q1: 如何处理事务中的错误?
A1: 在Action
函数中返回false
即可触发回滚,所有操作都会被撤销。
Q2: 缓存何时会被清除?
A2: 执行Insert
、Update
、Delete
操作时会自动清除对应表的缓存。
Q3: 如何执行复杂的原生SQL?
A3: 使用Query
方法执行查询,使用Exec
方法执行更新操作。
Q4: 主从分离如何工作?
A4: 查询操作自动使用从库(如果配置了),增删改操作使用主库。
Q5: 如何处理NULL值?
A5: 使用nil
作为值,查询时使用"field": nil
表示IS NULL
。
文档版本: 1.0
最后更新: 2024年
本文档基于HoTimeDB源码分析生成,如有疑问请参考源码实现。该ORM框架参考了PHP Medoo的设计理念,但根据Golang语言特性进行了适配和优化。