Files
hotime/docs/HoTimeDB_使用说明.md
T
hoteas 6b689f3a1b chore(logging): 更新日志重定向与捕获功能
- 在 .gitignore 中添加调试日志文件的忽略规则,避免不必要的调试信息被提交
- 修改 application.go 中的 stdout 重定向逻辑,使用 log.CaptureStream 以支持更灵活的日志捕获
- 更新 README 文档,增加对日志重定向功能的说明
2026-07-13 07:45:51 +08:00

27 KiB
Raw Blame History

HoTimeDB ORM 使用说明书

完整教程见本文;方法签名与条件运算符速查见 HoTimeDB_API参考.md

概述

HoTimeDB是一个基于Golang实现的轻量级ORM框架,参考PHP Medoo设计,提供简洁的数据库操作接口。支持MySQL、SQLite、PostgreSQL、达梦(DM8)等数据库,并集成了缓存、事务、链式查询等功能。

目录

快速开始

初始化数据库连接

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
database := &db.HoTimeDB{
    Type: "mysql", // 可选:mysql, sqlite3, postgres, dm
}
database.SetConnect(createConnection)

数据库配置

基本配置

type HoTimeDB struct {
    *sql.DB
    ContextBase
    DBName string
    *cache.HoTimeCache
    Log         *logrus.Logger
    Type        string    // 数据库类型:mysql, sqlite3, postgres, dm
    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开发模式
    Dialect     Dialect // 数据库方言适配器
}

设置表前缀

database.Prefix = "app_"

设置运行模式

database.Mode = 2 // 开发模式,会输出SQL日志

基本操作

查询(Select)

基本查询

// 查询所有字段
users := database.Select("user")

// 查询指定字段
users := database.Select("user", "id,name,email")

// 查询指定字段(数组形式)
users := database.Select("user", []string{"id", "name", "email"})

// 单条件查询
users := database.Select("user", "*", common.Map{
    "status": 1,
})

// 多条件查询(自动用 AND 连接)
users := database.Select("user", "*", common.Map{
    "status": 1,
    "age[>]": 18,
})
// 生成: WHERE `status`=? AND `age`>?

复杂条件查询

// 简化语法:多条件自动用 AND 连接
users := database.Select("user", "*", common.Map{
    "status": 1,
    "age[>]": 18,
    "name[~]": "张",
})
// 生成: WHERE `status`=? AND `age`>? AND `name` LIKE ?

// 显式 AND 条件(与上面等效)
users := database.Select("user", "*", common.Map{
    "AND": common.Map{
        "status": 1,
        "age[>]": 18,
        "name[~]": "张",
    },
})

// OR 条件
users := database.Select("user", "*", common.Map{
    "OR": common.Map{
        "status": 1,
        "type": 2,
    },
})

// 混合条件(嵌套 AND/OR
users := database.Select("user", "*", common.Map{
    "AND": common.Map{
        "status": 1,
        "OR": common.Map{
            "age[<]": 30,
            "level[>]": 5,
        },
    },
})

// 带 ORDER BY、LIMIT 等特殊条件(关键字支持大小写)
users := database.Select("user", "*", common.Map{
    "status": 1,
    "age[>]": 18,
    "ORDER": "id DESC",  // 或 "order": "id DESC"
    "LIMIT": 10,         // 或 "limit": 10
})

// 带多个特殊条件
users := database.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
})

// 使用 HAVING 过滤分组结果
users := database.Select("user", "dept_id, COUNT(*) as cnt", common.Map{
    "GROUP": "dept_id",
    "HAVING": common.Map{
        "cnt[>]": 5,
    },
})

// 使用独立 OFFSET
users := database.Select("user", "*", common.Map{
    "status": 1,
    "LIMIT": 10,
    "OFFSET": 20,
})

获取单条记录(Get)

// 获取单个用户
user := database.Get("user", "*", common.Map{
    "id": 1,
})

// 获取指定字段
user := database.Get("user", "id,name,email", common.Map{
    "status": 1,
})

插入(Insert)

// 基本插入
id := database.Insert("user", common.Map{
    "name":         "张三",
    "email":        "zhangsan@example.com",
    "age":          25,
    "status":       1,
    "created_time": Time2Str(time.Now()), // 推荐:使用服务器时间,避免数据库时区问题
})

// 返回插入的ID
fmt.Println("插入的用户ID:", id)

时间字段最佳实践:建议使用 Time2Str(time.Now()) 传入服务器时间,而非 "[#]": "NOW()" 让数据库执行 NOW()。 原因:NOW() 使用数据库服务器时间,当应用服务器与数据库服务器时区不同时会产生偏差;而 Time2Str(time.Now()) 始终使用应用服务器时间,行为一致,且跨数据库(MySQL / 达梦 / PostgreSQL)完全兼容。

import (
    "time"
    . "code.hoteas.com/golang/hotime/common" // Time2Str 在 common 包中
)

批量插入(Inserts)

// 批量插入多条记录(使用 []Map 格式,更直观)
affected := database.Inserts("user", []common.Map{
    {"name": "张三", "email": "zhang@example.com", "age": 25},
    {"name": "李四", "email": "li@example.com", "age": 30},
    {"name": "王五", "email": "wang@example.com", "age": 28},
})
// 生成: INSERT INTO `user` (`age`, `email`, `name`) VALUES (?, ?, ?), (?, ?, ?), (?, ?, ?)

fmt.Printf("批量插入 %d 条记录\n", affected)

// 推荐:使用服务器时间(Time2Str)而非数据库函数 NOW()
now := Time2Str(time.Now())
affected := database.Inserts("log", []common.Map{
    {"user_id": 1, "action": "login",  "created_time": now},
    {"user_id": 2, "action": "logout", "created_time": now},
})

更新(Update)

// 基本更新
affected := database.Update("user", common.Map{
    "name":        "李四",
    "email":       "lisi@example.com",
    "modify_time": Time2Str(time.Now()), // 推荐:服务器时间
}, common.Map{
    "id": 1,
})

// 条件更新(多条件自动 AND 连接)
affected := database.Update("user", common.Map{
    "status": 0,
}, common.Map{
    "age[<]": 18,
    "status": 1,
})

fmt.Println("更新的记录数:", affected)

Upsert操作

Upsert(插入或更新):如果记录存在则更新,不存在则插入。

// Upsert 操作(使用 Slice 格式)
affected := database.Upsert("user", 
    common.Map{
        "id": 1,
        "name": "张三",
        "email": "zhang@example.com",
        "login_count": 1,
    },
    common.Slice{"id"},                           // 唯一键(用于冲突检测)
    common.Slice{"name", "email", "login_count"}, // 冲突时更新的字段
)

// MySQL 生成:
// INSERT INTO user (id,name,email,login_count) VALUES (?,?,?,?)
// ON DUPLICATE KEY UPDATE name=VALUES(name), email=VALUES(email), login_count=VALUES(login_count)

// PostgreSQL 生成:
// INSERT INTO "user" (id,name,email,login_count) VALUES ($1,$2,$3,$4)
// ON CONFLICT (id) DO UPDATE SET name=EXCLUDED.name, email=EXCLUDED.email, login_count=EXCLUDED.login_count

// 达梦 DM8 生成:
// MERGE INTO "user" USING (SELECT ? AS "id", ? AS "name", ...) src
// ON ("user"."id" = src."id")
// WHEN MATCHED THEN UPDATE SET "name"=src."name", "email"=src."email", ...
// WHEN NOT MATCHED THEN INSERT ("id","name","email","login_count") VALUES (src."id", ...)

// 使用 [#] 标记直接 SQL 更新
affected := database.Upsert("user",
    common.Map{
        "id": 1,
        "name": "张三",
        "login_count[#]": "login_count + 1", // 直接 SQL 表达式
    },
    common.Slice{"id"},
    common.Slice{"name", "login_count"},
)

// 也支持可变参数形式
affected := database.Upsert("user",
    common.Map{"id": 1, "name": "张三"},
    common.Slice{"id"},
    "name", "email", // 可变参数
)

删除(Delete)

// 根据ID删除
affected := database.Delete("user", common.Map{
    "id": 1,
})

// 条件删除(多条件自动 AND 连接)
affected := database.Delete("user", common.Map{
    "status": 0,
    "created_time[<]": "2023-01-01",
})

fmt.Println("删除的记录数:", affected)

链式查询构建器

HoTimeDB提供了链式查询构建器,让查询更加直观:

// 基本链式查询
users := database.Table("user").
    Where("status", 1).
    And("age[>]", 18).
    Order("created_time DESC").
    Limit(10, 20). // offset, limit
    Select()

// 链式获取单条记录
user := database.Table("user").
    Where("id", 1).
    Get()

// 链式更新
affected := database.Table("user").
    Where("id", 1).
    Update(common.Map{
        "name":        "新名称",
        "modify_time": Time2Str(time.Now()),
    })

// 链式删除
affected := database.Table("user").
    Where("status", 0).
    Delete()

// 链式统计
count := database.Table("user").
    Where("status", 1).
    Count()

链式条件组合

// 复杂条件组合
users := database.Table("user").
    Where("status", 1).
    And("age[>=]", 18).
    Or(common.Map{
        "level[>]": 5,
        "vip": 1,
    }).
    Order("created_time DESC", "id ASC").
    Group("department").
    Having(common.Map{"COUNT(*).[>]": 5}). // 新增 HAVING 支持
    Limit(0, 20).
    Offset(10). // 新增独立 OFFSET 支持
    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片段(通常用于复杂条件,时间推荐用 Go 计算)
"[##]": "user.status = 1 AND user.level > 0"

// 时间范围条件:推荐在 Go 侧计算,避免跨库 NOW() 差异
"created_time[>]": Time2Str(time.Now().AddDate(0, 0, -1)) // 一天前

// 如必须用数据库函数(不跨库时可用,但不推荐)
"update_time[#]": "NOW()"

JOIN操作

链式JOIN

// LEFT JOIN
users := database.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 := database.Table("user").
    RightJoin("order", "user.id = order.user_id").
    Select()

// INNER JOIN
users := database.Table("user").
    InnerJoin("profile", "user.id = profile.user_id").
    Select()

// FULL JOIN
users := database.Table("user").
    FullJoin("profile", "user.id = profile.user_id").
    Select()

传统JOIN语法

users := database.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 := database.Page(3, 20).PageSelect("user", "*", common.Map{
    "status": 1,
})

// 链式分页
users := database.Table("user").
    Where("status", 1).
    Page(2, 15). // 第2页,每页15条
    Select()

分页信息获取

// 获取总数
total := database.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 := database.Count("user")

// 条件统计
activeUsers := database.Count("user", common.Map{
    "status": 1,
})

// JOIN统计
count := database.Count("user", 
    common.Slice{
        common.Map{"[>]profile": "user.id = profile.user_id"},
    },
    common.Map{
        "user.status": 1,
        "profile.verified": 1,
    },
)

求和

// 基本求和
totalAmount := database.Sum("order", "amount")

// 条件求和
paidAmount := database.Sum("order", "amount", common.Map{
    "status": "paid",
    "created_time[>]": "2023-01-01",
})

平均值

// 基本平均值
avgAge := database.Avg("user", "age")

// 条件平均值
avgAge := database.Avg("user", "age", common.Map{
    "status": 1,
})

最大值/最小值

// 最大值
maxAge := database.Max("user", "age")

// 最小值
minAge := database.Min("user", "age")

// 条件最大值
maxBalance := database.Max("user", "balance", common.Map{
    "status": 1,
})

事务处理

// 事务操作
success := database.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": Time2Str(time.Now()),
    })
    
    if orderId == 0 {
        return false // 回滚
    }
    
    return true // 提交
})

if success {
    fmt.Println("事务执行成功")
} else {
    fmt.Println("事务回滚")
    fmt.Println("错误:", database.LastErr.GetError())
}

缓存机制

HoTimeDB集成了缓存功能,可以自动缓存查询结果:

缓存配置

import "code.hoteas.com/golang/hotime/cache"

// 设置缓存
database.HoTimeCache = &cache.HoTimeCache{
    // 缓存配置
}

缓存行为

  • 查询操作会自动检查缓存
  • 增删改操作会自动清除相关缓存
  • 缓存键格式:表名:查询MD5
  • cached表不会被缓存

缓存清理

// 手动清除表缓存
database.HoTimeCache.Db("user*", nil) // 清除user表所有缓存

PostgreSQL支持

HoTimeDB 支持 PostgreSQL 数据库,自动处理语法差异。

PostgreSQL 配置

import (
    "code.hoteas.com/golang/hotime/db"
    _ "github.com/lib/pq" // PostgreSQL 驱动
)

database := &db.HoTimeDB{
    Type: "postgres", // 设置数据库类型为 postgres
    Prefix: "app_",
}

database.SetConnect(func(err ...*common.Error) (master, slave *sql.DB) {
    dsn := "host=localhost port=5432 user=postgres password=secret dbname=mydb sslmode=disable"
    master, _ = sql.Open("postgres", dsn)
    return master, master
})

主要差异

特性 MySQL PostgreSQL
标识符引号 `name` "name"
占位符 ? $1, $2, $3...
Upsert ON DUPLICATE KEY UPDATE ON CONFLICT DO UPDATE

所有这些差异由框架自动处理,无需手动调整代码。

达梦数据库支持

HoTimeDB 原生支持达梦数据库 DM8,驱动已内置于 vendor/gitee.com/chunanyong/dm,无需额外安装。

达梦配置

config.json 中配置:

{
    "db": {
        "dm": {
            "host": "127.0.0.1",
            "port": "5236",
            "user": "SYSDBA",
            "password": "your_password",
            "name": "TEST",
            "prefix": ""
        }
    }
}

代码配置方式:

import (
    "code.hoteas.com/golang/hotime/db"
    _ "gitee.com/chunanyong/dm" // 达梦驱动(已在 vendor 中)
)

database := &db.HoTimeDB{
    Type:   "dm",    // 或 "dameng"
    Prefix: "",      // 表前缀(可选)
}

database.SetConnect(func(err ...*common.Error) (master, slave *sql.DB) {
    // name 字段即为 schema(默认搜索路径)
    dsn := "dm://SYSDBA:your_password@127.0.0.1:5236?schema=TEST"
    master, _ = sql.Open("dm", dsn)
    return master, master
})

schema 说明?schema=TEST 将当前会话的默认搜索 schema 设置为 TEST。 所有不带 schema 前缀的表名均在该 schema 下查找和创建。 建议为应用创建独立的专用 schema,避免直接使用 SYSDBA 默认 schema。

与 MySQL/PostgreSQL 的差异对比

特性 MySQL PostgreSQL 达梦 DM8
标识符引号 `name` "name" "name"
占位符 ? $1, $2... ?
Upsert ON DUPLICATE KEY UPDATE ON CONFLICT DO UPDATE MERGE INTO ... USING
自增列 AUTO_INCREMENT SERIAL IDENTITY(1,1)
文本类型 TEXT TEXT TEXT / CLOB
时间赋值(推荐) Time2Str(time.Now()) Time2Str(time.Now()) Time2Str(time.Now())
时间函数(不推荐) NOW() NOW() NOW()
分页语法 LIMIT m, n LIMIT n OFFSET m LIMIT m, n 或 LIMIT n OFFSET m

所有这些差异由框架自动处理,业务代码无需修改。

Upsert 在达梦中的行为

达梦不支持 MySQL 的 ON DUPLICATE KEY UPDATE 和 PostgreSQL 的 ON CONFLICT,框架会自动生成 MERGE INTO 语句:

// 调用方式与其他数据库完全一致
affected := database.Upsert("admin",
    common.Map{
        "name":     "张三",
        "phone":    "13800000001",
        "password": "abc123",
        "role_id":  1,
    },
    common.Slice{"phone"},                    // 唯一键(冲突检测)
    common.Slice{"name", "password", "role_id"}, // 冲突时更新的字段
)

// 达梦自动生成:
// MERGE INTO "admin" USING (SELECT ? AS "name", ? AS "phone", ...) src
// ON ("admin"."phone" = src."phone")
// WHEN MATCHED THEN UPDATE SET "name" = src."name", ...
// WHEN NOT MATCHED THEN INSERT (...) VALUES (src....)

达梦注意事项

1. 标识符大小写敏感

达梦对双引号包裹的标识符大小写敏感。框架统一使用小写创建和访问表/列,确保一致性:

-- ✅ 正确:框架自动生成,小写标识符
SELECT * FROM "admin" WHERE "id" = ?

-- ❌ 错误:大小写不一致会导致 "无效的表或视图名" 错误
SELECT * FROM "ADMIN" WHERE "ID" = ?

使用 DM 管理工具手动创建表时,建议使用双引号包裹小写名称,与框架保持一致。

2. 保留字冲突

达梦有大量保留字(如 adminuserorderkeyvalue 等),直接使用这些名称作为表名或列名会报语法错误。
框架已自动为所有标识符加双引号,一般情况下无需手动处理。

如果在原生 SQLQuery/Exec)中使用保留字,需要手动加引号:

// 原生 SQL 中需要手动加双引号
results := database.Query(`SELECT "id", "name" FROM "admin" WHERE "state" = ?`, 1)

3. schema 与 USER_TABLES 的关系

连接达梦时使用 ?schema=TESTDM 的 USER_TABLES 视图只显示当前登录用户(如 SYSDBA)所属 schema 的表,而非当前会话 schema 的表。

框架内部已改用 COUNT(*) 方式直接探查表可访问性,避免 schema 错配问题。

如果在自定义代码中检测表是否存在,不要用 USER_TABLES,改用:

// ✅ 推荐:直接探查表可访问性
res := database.Query(`SELECT COUNT(*) as cnt FROM "your_table"`)
exists := len(res) > 0

// ❌ 不推荐:USER_TABLES 可能显示当前会话 schema 之外的表
res := database.Query(`SELECT TABLE_NAME FROM USER_TABLES WHERE TABLE_NAME='your_table'`)

4. DDL 建表建议

手动建表时,列定义与 MySQL 的区别:

-- 达梦建表示例
CREATE TABLE "admin" (
    "id"          INT IDENTITY(1,1) PRIMARY KEY,  -- 自增(MySQL: AUTO_INCREMENT
    "name"        VARCHAR(100),
    "phone"       VARCHAR(20),
    "content"     CLOB,                            -- 长文本(MySQL: TEXT/LONGTEXT
    "create_time" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    "modify_time" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    CONSTRAINT "uk_admin_phone" UNIQUE ("phone")
);

-- 索引
CREATE INDEX "idx_admin_state" ON "admin" ("state");

5. 日期时间函数差异

部分日期函数在达梦中语法不同,使用 [##] 直接SQL时需注意:

功能 推荐(Go侧) MySQL [##]/原生SQL 达梦 DM8 [##]/原生SQL
当前时间赋值 Time2Str(time.Now()) NOW() NOW()
一天前(时间范围查询) Time2Str(time.Now().AddDate(0,0,-1)) DATE_SUB(NOW(), INTERVAL 1 DAY) DATEADD(DAY, -1, NOW())
时间戳转字符串 Time2Str(time.Unix(ts, 0)) FROM_UNIXTIME(ts) 不支持,需 Go 侧处理

强烈推荐:时间字段赋值和时间范围计算都在 Go 侧完成(使用 Time2Strtime.Now().Add() 等),传入参数化的字符串值。这样完全规避数据库时区设置差异,也无需关心各数据库函数的语法不同。

6. 使用 IN 查询时的注意事项

IN 查询在达梦中与 MySQL 行为一致,框架已统一处理:

// 正常使用,框架自动展开
results := database.Select("article", "*", common.Map{
    "id": []int{1, 2, 3},
})
// 生成: WHERE "id" IN (?, ?, ?)

高级特性

调试模式

// 设置调试模式
database.Mode = 2

// 查看最后执行的SQL
fmt.Println("最后的SQL:", database.LastQuery)
fmt.Println("参数:", database.LastData)
fmt.Println("错误:", database.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
}

database.SetConnect(createConnection)
// 查询会自动使用从库,增删改使用主库

原生SQL执行

// 执行查询SQL
results := database.Query("SELECT * FROM user WHERE age > ? AND status = ?", 18, 1)

// 执行更新SQL(原生 SQL 中也建议使用服务器时间)
result, err := database.Exec("UPDATE user SET last_login = ? WHERE id = ?", Time2Str(time.Now()), 1)
if err.GetError() == nil {
    affected, _ := result.RowsAffected()
    fmt.Println("影响行数:", affected)
}

特殊语法详解

条件标记符说明

标记符 功能 示例 生成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表达式 "balance[#]": "balance + 1" balance = balance + 1
[##] SQL片段 "[##]": "a > b" a > b
[#!] 不等于直接SQL "status[#!]": "1" status != 1
[!#] 不等于直接SQL "status[!#]": "1" status != 1

特殊关键字(支持大小写)

关键字 功能 示例
ORDER / order 排序 "ORDER": "id DESC"
GROUP / group 分组 "GROUP": "dept_id"
LIMIT / limit 限制 "LIMIT": 10
OFFSET / offset 偏移 "OFFSET": 20
HAVING / having 分组过滤 "HAVING": Map{"cnt[>]": 5}
DISTINCT / distinct 去重 在 SELECT 中使用

常见问题

Q1: 多条件查询需要用 AND 包装吗?

A1: 不再需要!现在多条件会自动用 AND 连接。当然,使用 AND 包装仍然有效(向后兼容)。

Q2: 如何处理事务中的错误?

A2: 在 Action 函数中返回 false 即可触发回滚,所有操作都会被撤销。

Q3: 缓存何时会被清除?

A3: 执行 InsertUpdateDeleteUpsertInserts 操作时会自动清除对应表的缓存。

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

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

Q5: 主从分离如何工作?

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

Q6: 如何处理NULL值

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

Q7: PostgreSQL 和 MySQL 语法有区别吗?

A7: 框架会自动处理差异(占位符、引号等),代码无需修改。

Q8: 达梦数据库和 MySQL 的代码使用方式一样吗?

A8: 基本一样。切换数据库类型只需修改 Type 字段和连接字符串,业务查询代码完全不用改。唯一需要注意的是:

  • 原生 SQLQuery/Exec)中的标识符需要手动加双引号
  • MERGE INTO 等 DM 特有 DDL 需要按 DM 语法书写
  • 检测表是否存在时,不要用 USER_TABLES,改用直接 COUNT(*) 查询

Q9: 表名或列名用了达梦保留字怎么办?

A9: 框架所有自动生成的 SQL 都会对标识符加双引号,无需手动处理。但在原生 SQL 中使用保留字(如 adminuserorder)时,必须手动加双引号,否则会报语法错误。

Q10: 达梦数据库中 INSERT 后如何获取自增 ID?

A10: 框架的 Insert 方法已支持从达梦原生驱动获取 LastInsertId,与 MySQL 使用方式完全一致:

id := database.Insert("user", common.Map{"name": "张三"})
fmt.Println("新插入的ID:", id)

文档版本: 2.1
最后更新: 2026年3月

本文档基于HoTimeDB源码分析生成,如有疑问请参考源码实现。该ORM框架参考了PHP Medoo的设计理念,根据Golang语言特性进行了适配和优化,并新增了对达梦(DM8)国产数据库的完整支持。

更多参考: