hotime/db/HoTimeDB_使用说明.md

19 KiB
Raw Blame History

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的差异

  1. 类型系统: Golang的强类型要求使用common.Mapcommon.Slice
  2. 语法差异: 某些条件语法可能略有不同
  3. 错误处理: 使用Golang的错误处理模式
  4. 并发安全: 需要注意并发使用时的安全性
  5. 缓存集成: 内置了缓存功能
  6. 链式调用: 提供了更丰富的链式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: 执行InsertUpdateDelete操作时会自动清除对应表的缓存。

Q3: 如何执行复杂的原生SQL

A3: 使用Query方法执行查询,使用Exec方法执行更新操作。

Q4: 主从分离如何工作?

A4: 查询操作自动使用从库(如果配置了),增删改操作使用主库。

Q5: 如何处理NULL值

A5: 使用nil作为值,查询时使用"field": nil表示IS NULL


文档版本: 1.0
最后更新: 2024年

本文档基于HoTimeDB源码分析生成如有疑问请参考源码实现。该ORM框架参考了PHP Medoo的设计理念但根据Golang语言特性进行了适配和优化。