hotime/db/HoTimeDB_使用说明.md

890 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# HoTimeDB ORM 使用说明书
## 概述
HoTimeDB是一个基于Golang实现的轻量级ORM框架参考PHP Medoo设计提供简洁的数据库操作接口。支持MySQL、SQLite等数据库并集成了缓存、事务、链式查询等功能。
## 目录
- [快速开始](#快速开始)
- [数据库配置](#数据库配置)
- [基本操作](#基本操作)
- [查询(Select)](#查询select)
- [获取单条记录(Get)](#获取单条记录get)
- [插入(Insert)](#插入insert)
- [更新(Update)](#更新update)
- [删除(Delete)](#删除delete)
- [链式查询构建器](#链式查询构建器)
- [条件查询语法](#条件查询语法)
- [JOIN操作](#join操作)
- [分页查询](#分页查询)
- [聚合函数](#聚合函数)
- [事务处理](#事务处理)
- [缓存机制](#缓存机制)
- [高级特性](#高级特性)
## 快速开始
### 初始化数据库连接
```go
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)
```
## 数据库配置
### 基本配置
```go
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开发模式
}
```
### 设置表前缀
```go
db.Prefix = "app_"
```
### 设置运行模式
```go
db.Mode = 2 // 开发模式会输出SQL日志
```
## 基本操作
### 查询(Select)
#### 基本查询
```go
// 查询所有字段
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中写多个字段条件**
```go
// 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)
```go
// 获取单个用户
user := db.Get("user", "*", common.Map{
"id": 1,
})
// 获取指定字段
user := db.Get("user", "id,name,email", common.Map{
"status": 1,
})
```
### 插入(Insert)
```go
// 基本插入
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)
```go
// 基本更新
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)
```go
// 根据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提供了链式查询构建器让查询更加直观
```go
// 基本链式查询
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()
```
### 链式条件组合
```go
// 复杂条件组合
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
### 基本比较
```go
// 等于
"id": 1
// 不等于
"id[!]": 1
// 大于
"age[>]": 18
// 大于等于
"age[>=]": 18
// 小于
"age[<]": 60
// 小于等于
"age[<=]": 60
```
### 模糊查询
```go
// LIKE %keyword%
"name[~]": "张"
// LIKE keyword% (右边任意)
"name[~!]": "张"
// LIKE %keyword (左边任意)
"name[!~]": "san"
// 手动LIKE需要手动添加%
"name[~~]": "%张%"
```
### 区间查询
```go
// BETWEEN
"age[<>]": []int{18, 60}
// NOT BETWEEN
"age[><]": []int{18, 25}
```
### IN查询
```go
// IN
"id": []int{1, 2, 3, 4, 5}
// NOT IN
"id[!]": []int{1, 2, 3}
```
### NULL查询
```go
// IS NULL
"deleted_at": nil
// IS NOT NULL
"deleted_at[!]": nil
```
### 直接SQL
```go
// 直接插入SQL表达式注意防注入
"created_time[#]": "> DATE_SUB(NOW(), INTERVAL 1 DAY)"
// 字段直接赋值(不使用参数化查询)
"update_time[#]": "NOW()"
// 直接SQL片段
"[##]": "user.status = 1 AND user.level > 0"
```
## JOIN操作
### 链式JOIN
```go
// 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语法
```go
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
## 分页查询
### 基本分页
```go
// 设置分页页码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()
```
### 分页信息获取
```go
// 获取总数
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)
```
## 聚合函数
### 计数
```go
// 总数统计
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,
},
)
```
### 求和
```go
// 基本求和
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",
},
)
```
## 事务处理
```go
// 事务操作
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集成了缓存功能可以自动缓存查询结果
### 缓存配置
```go
import "code.hoteas.com/golang/hotime/cache"
// 设置缓存
db.HoTimeCache = &cache.HoTimeCache{
// 缓存配置
}
```
### 缓存行为
- 查询操作会自动检查缓存
- 增删改操作会自动清除相关缓存
- 缓存键格式:`表名:查询MD5`
- `cached`表不会被缓存
### 缓存清理
```go
// 手动清除表缓存
db.HoTimeCache.Db("user*", nil) // 清除user表所有缓存
```
## 高级特性
### 调试模式
```go
// 设置调试模式
db.Mode = 2
// 查看最后执行的SQL
fmt.Println("最后的SQL:", db.LastQuery)
fmt.Println("参数:", db.LastData)
fmt.Println("错误:", db.LastErr.GetError())
```
### 主从分离
```go
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执行
```go
// 执行查询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)
}
```
### 数据类型处理
```go
// 时间戳插入
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`方法会自动处理不同数据类型:
```go
// 自动转换[]uint8为字符串
// 保持其他类型的原始值
// 处理NULL值
```
## 错误处理
```go
// 检查错误
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查询以提高性能
```go
// 这个查询会被自动优化单个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)
})
```
### 批量操作
```go
// 使用事务进行批量插入
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的差异
1. **类型系统**: Golang的强类型要求使用`common.Map``common.Slice`
2. **语法差异**: 某些条件语法可能略有不同
3. **错误处理**: 使用Golang的错误处理模式
4. **并发安全**: 需要注意并发使用时的安全性
5. **缓存集成**: 内置了缓存功能
6. **链式调用**: 提供了更丰富的链式API
## 完整示例
```go
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语言特性进行了适配和优化。