Golang Beego SQL链式查询(包含Join关联)

用官方提供的ORM用的不习惯,就花时间搞了个链式的操作,主要是方便PHP转GO的小白子,可

以灵活运用,我也搞了Redis的快捷函数操作,和sql表名数据同步

conf/app.conf 配置信息

Go 复制代码
# MySQL服务器地址
db_host = 127.0.0.1
# MySQL端口
db_port = 3306
# MySQL用户名
db_user = root
# MySQL密码
db_password = root
# MySQL数据库表名
db_name = golang

services/database.go

直接在main.go 初始化数据库,services.InitDatabase()

Go 复制代码
/*
+--------------------------------------------------------------------------------
| If this code works, it was written by Xven. If not, I don't know who wrote it.
+--------------------------------------------------------------------------------
| Statement: An Ordinary Person
+--------------------------------------------------------------------------------
| Author: Xven <QQ:270988107>
+--------------------------------------------------------------------------------
| Copyright (c) 2025 Xven All rights reserved.
+--------------------------------------------------------------------------------
*/
package services

import (
	"context"
	"fmt"
	"time"

	"github.com/astaxie/beego/orm"
	"github.com/beego/beego/v2/core/config"
	_ "github.com/go-sql-driver/mysql"
)

// DB 是全局数据库连接实例
var DB orm.Ormer // 修改为使用beego的orm

func InitDatabase() error {
	// 读取配置
	host, _ := config.String("db_host")
	port, _ := config.String("db_port")
	user, _ := config.String("db_user")
	password, _ := config.String("db_password")
	dbName, _ := config.String("db_name")

	// 注册MySQL驱动
	orm.RegisterDriver("mysql", orm.DRMySQL)

	// 构建DSN
	dsn := user + ":" + password + "@tcp(" + host + ":" + port + ")/" + dbName + "?charset=utf8&parseTime=true"
	// 注册数据库时设置合理的连接池参数
	maxIdle := 20
	maxOpen := 80
	// 注册默认数据库
	if err := orm.RegisterDataBase("default", "mysql", dsn, maxIdle, maxOpen); err != nil {
		if err.Error() == "DataBase alias name `default` already registered, cannot reuse" {
			// 如果已经注册,返回一个自定义错误
			return fmt.Errorf("数据库已注册: %w", err)
		}
		return fmt.Errorf("MySQL连接失败: %w", err)
	}

	// 获取数据库连接
	db, err := orm.GetDB("default")
	if err != nil {
		return fmt.Errorf("获取数据库连接失败: %w", err)
	}

	// 修改连接生命周期为7小时50分钟(比MySQL的8小时短)
	db.SetConnMaxLifetime(28200 * time.Second) // 28200s = 7h50m

	// 空闲连接超时设为30分钟(小于服务端可能的interactive_timeout)
	db.SetConnMaxIdleTime(30 * time.Minute) // 1800s = 30m

	return nil
}

builder/db.go

Go 复制代码
/*
+--------------------------------------------------------------------------------
| If this code works, it was written by Xven. If not, I don't know who wrote it.
+--------------------------------------------------------------------------------
| Statement: An Ordinary Person
+--------------------------------------------------------------------------------
| Author: Xven <QQ:270988107>
+--------------------------------------------------------------------------------
| Copyright (c) 2025 Xven All rights reserved.
+--------------------------------------------------------------------------------
*/
package builder

import (
	"fmt"
	"strings"

	"github.com/astaxie/beego/orm"
)

// JoinType 定义连接类型常量
const (
	JoinInner = "INNER"
	JoinLeft  = "LEFT"
	JoinRight = "RIGHT"
)

// JoinInfo 存储JOIN信息
type JoinInfo struct {
	Type      string // 连接类型:INNER/LEFT/RIGHT
	Table     string // 连接表名
	Condition string // 连接条件
}

type DB struct {
	ormer           orm.Ormer
	tableName       string
	qs              orm.QuerySeter
	fields          []string      // SELECT字段
	joins           []JoinInfo    // JOIN信息
	whereConditions []string      // WHERE条件语句
	whereParams     []interface{} // WHERE参数
	orderBy         string        // 排序语句
	limit           int           // 限制条数
	offset          int           // 偏移量
	rawMode         bool          // 标记是否使用原生SQL模式
}

/**
 * 初始化DB实例
 * @Author Xven <270988107@qq.com>
 * @param (?1:)
 * @return (?1:)
 */
func NewDB() *DB {
	o := orm.NewOrm()
	return &DB{
		ormer: o,
		qs:    nil, // 不再使用空表名
	}
}

/**
 * Table 设置表名
 * @Author Xven <270988107@qq.com>
 * @param (?1:)
 * @return (?1:)
 * 示例:db.Table("users")
 */
func (d *DB) Table(tableName string) *DB {
	d.tableName = tableName
	d.qs = d.ormer.QueryTable(tableName)
	return d
}

/**
 * Join 添加关联查询
 * @Author Xven <270988107@qq.com>
 * @param joinType 连接类型(INNER/LEFT/RIGHT)
 * @param table 连接表名(可包含别名)
 * @param condition 连接条件(包含占位符时需自行处理参数)
 * @return *DB 返回链式调用对象
 *
 * 示例1:内连接基础用法
 * db.Table("users").Join(JoinInner, "profiles", "profiles.user_id = users.id")
 *
 * 示例2:带别名连接
 * db.Table("u").Join(JoinLeft, "profiles p", "p.user_id = u.id")
 *
 * 示例3:多条件连接
 * db.Table("orders o").
 *   Join(JoinInner, "customers c", "c.id = o.customer_id AND c.status = ?", "active")
 */
func (d *DB) Join(joinType, table, condition string, params ...interface{}) *DB {
	// 启用原生SQL模式
	d.rawMode = true

	// 处理带参数的连接条件(需要手动处理参数)
	if len(params) > 0 {
		// 将占位符替换为beego的?格式
		condition = strings.Replace(condition, "?", "___", -1) // 临时替换
		condition = strings.Replace(condition, "___", "?", -1)

		// 添加参数到whereParams(注意顺序)
		d.whereParams = append(d.whereParams, params...)
	}

	d.joins = append(d.joins, JoinInfo{
		Type:      joinType,
		Table:     table,
		Condition: condition,
	})
	return d
}

/**
 * Where 添加条件,支持多种格式
 * @Author Xven <270988107@qq.com>
 * @param (?1:)
 * @return (?1:)
 * 示例:
 * Where("id", 1)
 * Where("age", ">", 18)
 * Where(map[string]interface{}{"name": "张三", "age": 18})
 * 当存在JOIN时自动切换到原生SQL模式
 */
func (d *DB) Where(args ...interface{}) *DB {
	// 如果已经存在JOIN或者手动启用了原生模式
	if d.rawMode {
		switch v := args[0].(type) {
		case string:
			// 字符串格式处理
			if len(args) == 2 {
				d.whereConditions = append(d.whereConditions, fmt.Sprintf("%s = ?", v))
				d.whereParams = append(d.whereParams, args[1])
			} else if len(args) == 3 {
				operator := args[1].(string)
				d.whereConditions = append(d.whereConditions, fmt.Sprintf("%s %s ?", v, operator))
				d.whereParams = append(d.whereParams, args[2])
			}
		case map[string]interface{}:
			// Map格式处理
			for field, value := range v {
				d.whereConditions = append(d.whereConditions, fmt.Sprintf("%s = ?", field))
				d.whereParams = append(d.whereParams, value)
			}
		}
	} else {
		// 原始QuerySeter处理逻辑
		switch v := args[0].(type) {
		case string:
			if len(args) == 2 {
				d.qs = d.qs.Filter(v, args[1])
			} else if len(args) == 3 {
				operator := args[1].(string)
				d.qs = d.qs.Filter(v+"__"+convertOperator(operator), args[2])
			}
		case map[string]interface{}:
			for field, value := range v {
				d.qs = d.qs.Filter(field, value)
			}
		}
	}
	return d
}

/**
 * WhereIn IN条件
 * @Author Xven <270988107@qq.com>
 * @param (?1:)
 * @return (?1:)
 * 示例:WhereIn("id", []int{1,2,3})
 */
func (d *DB) WhereIn(field string, values interface{}) *DB {
	d.qs = d.qs.Filter(field+"__in", values)
	return d
}

/**
 * WhereLike 模糊查询
 * @Author Xven <270988107@qq.com>
 * @param (?1:)
 * @return (?1:)
 * 示例:
 * WhereLike("name", "张")
 * WhereLike("*", "张")(需先调用Fields指定字段)
 */
func (d *DB) WhereLike(field string, value string) *DB {
	if field == "*" {
		cond := orm.NewCondition()
		for _, f := range d.fields {
			cond = cond.Or(f+"__icontains", value)
		}
		d.qs = d.qs.SetCond(cond)
	} else {
		d.qs = d.qs.Filter(field+"__icontains", value)
	}
	return d
}

/**
 * Fields 设置字段(用于全字段模糊查询)
 * @Author Xven <270988107@qq.com>
 * @param (?1:)
 * @return (?1:)
 * 示例:Fields("name", "email")
 */
func (d *DB) Fields(fields ...string) *DB {
	d.fields = fields
	return d
}

/**
 * Order 排序
 * @Author Xven <270988107@qq.com>
 * @param (?1:)
 * @return (?1:)
 * 示例:Order("id desc")
 */
func (d *DB) Order(order string) *DB {
	d.qs = d.qs.OrderBy(order)
	return d
}

/**
 * Limit 限制条数
 * @Author Xven <270988107@qq.com>
 * @param (?1:)
 * @return (?1:)
 * 示例:Limit(10)
 */
func (d *DB) Limit(limit int) *DB {
	d.qs = d.qs.Limit(limit)
	return d
}

/**
 * Page 分页
 * @Author Xven <270988107@qq.com>
 * @param (?1:)
 * @return (?1:)
 * 示例:db.Table("users").Page(2, 10)(第二页,每页10条)
 * 示例:db.Table("users").Page(2, 10).Select(&users)(第二页,每页10条)
 */
func (d *DB) Page(page, pageSize int) *DB {
	return d.Limit(pageSize).Offset((page - 1) * pageSize)
}

/**
 * Offset 设置偏移量
 * @Author Xven <270988107@qq.com>
 * @param (?1:)
 * @return (?1:)
 * 示例:db.Table("users").Offset(10)(偏移10条)
 * 示例:db.Table("users").Offset(10).Select(&users)(偏移10条)
 */
func (d *DB) Offset(offset int) *DB {
	d.qs = d.qs.Offset(offset)
	return d
}

/**
 * Insert 插入单条,返回自增主键
 * @Author Xven <270988107@qq.com>
 * @param (?1:)
 * @return (?1:)
 * 示例:user := User{Name: "张三"}; id, err := db.Table("users").Insert(&user)
 */
func (d *DB) Insert(data interface{}) (int64, error) {
	return d.ormer.Insert(data)
}

/**
 * BatchInsert 批量插入
 * @Author Xven <270988107@qq.com>
 * @param (?1:)
 * @return (?1:)
 * 示例:users := []User{{Name: "李四"}, {Name: "王五"}}; rows, err := db.Table("users").BatchInsert(users, 100)
 */
func (d *DB) BatchInsert(data interface{}, batchSize int) (int64, error) {
	return d.ormer.InsertMulti(batchSize, data)
}

/**
 * Update 更新数据
 * @Author Xven <270988107@qq.com>
 * @param (?1:)
 * @return (?1:)
 * 示例:db.Table("users").Where("id", 1).Update(map[string]interface{}{"name": "张三"})
 */
func (d *DB) Update(data map[string]interface{}) (int64, error) {
	return d.qs.Update(data)
}

/**
 * Delete 删除数据
 * @Author Xven <270988107@qq.com>
 * @param (?1:)
 * @return (?1:)
 * 示例:db.Table("users").Where("id", 1).Delete()
 */
func (d *DB) Delete() (int64, error) {
	return d.qs.Delete()
}

/**
 * BatchDelete 根据主键批量删除
 * @Author Xven <270988107@qq.com>
 * @param (?1:)
 * @return (?1:)
 * 示例:db.Table("users").BatchDelete([]int{1,2,3})
 */
func (d *DB) BatchDelete(ids interface{}) (int64, error) {
	return d.qs.Filter("id__in", ids).Delete()
}

/**
 * Select 增强查询方法(自动处理JOIN查询)
 * @Author Xven <270988107@qq.com>
 * @param (?1:)
 * @return (?1:)
 * 示例:var users []User; db.Table("users").Where("age", ">", 18).Select(&users)
 */
func (d *DB) Select(list interface{}) (int64, error) {
	if !d.rawMode {
		return d.qs.All(list)
	}

	// 构建SELECT语句
	fields := "*"
	if len(d.fields) > 0 {
		fields = strings.Join(d.fields, ", ")
	}
	sql := fmt.Sprintf("SELECT %s FROM %s", fields, d.tableName)

	// 添加JOIN语句
	for _, join := range d.joins {
		sql += fmt.Sprintf(" %s JOIN %s ON %s", join.Type, join.Table, join.Condition)
	}

	// 处理WHERE条件
	if len(d.whereConditions) > 0 {
		sql += " WHERE " + strings.Join(d.whereConditions, " AND ")
	}

	// 处理ORDER BY
	if d.orderBy != "" {
		sql += " ORDER BY " + d.orderBy
	}

	// 处理LIMIT和OFFSET(关键修改点)
	if d.limit > 0 {
		sql += fmt.Sprintf(" LIMIT %d", d.limit)
	}
	if d.offset > 0 {
		sql += fmt.Sprintf(" OFFSET %d", d.offset)
	}

	// 执行查询(移除了SetMaxRows)
	rawSeter := d.ormer.Raw(sql, d.whereParams...)
	return rawSeter.QueryRows(list)
}

/**
 * Find 查询单条
 * @Author Xven <270988107@qq.com>
 * @param (?1:)
 * @return (?1:)
 * 示例:var user User; db.Table("users").Where("id", 1).Find(&user)
 */
func (d *DB) Find(obj interface{}) error {
	if !d.rawMode {
		return d.qs.One(obj)
	}

	// 构建基础查询
	fields := "*"
	if len(d.fields) > 0 {
		fields = strings.Join(d.fields, ", ")
	}
	sql := fmt.Sprintf("SELECT %s FROM %s", fields, d.tableName)

	// 添加JOIN
	for _, join := range d.joins {
		sql += fmt.Sprintf(" %s JOIN %s ON %s", join.Type, join.Table, join.Condition)
	}

	// 处理WHERE
	if len(d.whereConditions) > 0 {
		sql += " WHERE " + strings.Join(d.whereConditions, " AND ")
	}

	// 添加LIMIT 1
	sql += " LIMIT 1"

	return d.ormer.Raw(sql, d.whereParams...).QueryRow(obj)
}

/**
 * Count 统计数量
 * @Author Xven <270988107@qq.com>
 * @param (?1:)
 * @return (?1:)
 * 示例:total, err := db.Table("users").Where("age", ">", 18).Count()
 */
func (d *DB) Count() (int64, error) {
	if !d.rawMode {
		return d.qs.Count()
	}

	sql := fmt.Sprintf("SELECT COUNT(*) FROM %s", d.tableName)

	// 添加JOIN
	for _, join := range d.joins {
		sql += fmt.Sprintf(" %s JOIN %s ON %s", join.Type, join.Table, join.Condition)
	}

	// 处理WHERE
	if len(d.whereConditions) > 0 {
		sql += " WHERE " + strings.Join(d.whereConditions, " AND ")
	}

	var count int64
	err := d.ormer.Raw(sql, d.whereParams...).QueryRow(&count)
	return count, err
}

/**
 * Cursor 游标查询
 * @Author Xven <270988107@qq.com>
 * @param {int} limit 每页限制条数
 * @param {int} offset 偏移量
 * @return {int64, error}
 * 示例:var results []User; db.Table("users").Cursor(10, 0).Select(&results)
 */
func (d *DB) Cursor(limit int, offset int) *DB {
	d.qs = d.qs.Limit(limit).Offset(offset)
	return d
}

/**
 * Paginate 分页查询
 * @Author Xven <270988107@qq.com>
 * @param {int} page 当前页码
 * @param {int} pageSize 每页限制条数
 * @return {int64, error}
 * 示例:var results []User; total, err := db.Table("users").Paginate(1, 10).Select(&results)
 */
func (d *DB) Paginate(page int, pageSize int) *DB {
	d.qs = d.qs.Limit(pageSize).Offset((page - 1) * pageSize)
	return d
}

/**
 * Max 查询指定字段的最大值
 * @Author Xven <270988107@qq.com>
 * @param {string} field 指定的字段名
 * @return {float64, error} 返回最大值和可能出现的错误
 * 示例:maxValue, err := db.Table("xven_menus").Where("parent_id", parentID).Max("id")
 */
func (d *DB) Max(field string) (float64, error) {
	var result float64
	sql := "SELECT MAX(" + field + ") FROM " + d.tableName
	// 假设当前的查询条件需要应用到 SQL 中,这里简单拼接
	if d.qs != nil {
		// 这里需要更复杂的逻辑来处理查询条件,示例只是简单示意
		// 实际应用中需要处理更多的查询条件类型和格式
		sql += " WHERE ..."
	}
	// 修复:d.ormer.Raw(sql).QueryRow 只返回一个 error,去掉 num 变量
	err := d.ormer.Raw(sql).QueryRow(&result)
	if err != nil {
		return 0, err
	}
	return result, nil
}

/**
 * Exists 检查查询结果是否存在
 * @Author Xven <270988107@qq.com>
 * @param 无
 * @return {bool, error} 返回是否存在和可能出现的错误
 * 示例:exists, err := db.Table("xven_menus").Where("parent_id", parentID).Exists()
 */
func (d *DB) Exists() (bool, error) {
	count, err := d.qs.Count()
	if err != nil {
		return false, err
	}
	return count > 0, nil
}

/**
 * 转换操作符到orm支持的格式
 * @Author Xven <270988107@qq.com>
 * @param (?1:)
 * @return (?1:)
 * 示例:convertOperator(">")
 */
func convertOperator(op string) string {
	switch op {
	case ">":
		return "gt"
	case "<":
		return "lt"
	case ">=":
		return "gte"
	case "<=":
		return "lte"
	case "!=":
		return "ne"
	case "in":
		return "in"
	case "like":
		return "icontains"
	default:
		return ""
	}
}

使用示例

Go 复制代码
// 初始化
db := NewDB()

// 简单条件查询
db.Table("users").Where("id", 1)

// 比较运算
db.Table("users").Where("age", ">", 18)

// 多条件查询
db.Table("users").Where(map[string]interface{}{"name": "张三", "age": 18})

// IN查询
db.Table("users").WhereIn("id", []int{1,2,3})

// 单字段模糊查询
db.Table("users").WhereLike("name", "张")

// 全字段模糊查询
db.Table("users").Fields("name", "email").WhereLike("*", "张")

// 排序
db.Table("users").Order("id desc")

// 分页
var users []User
db.Table("users").Page(2, 10).Select(&users)

// 删除
db.Table("users").WhereIn("id", []int{1,2,3}).Delete()

// 插入并返回ID
user := User{Name: "张三"}
id, _ := db.Table("users").Insert(&user)

// 批量插入
users := []User{{Name: "李四"}, {Name: "王五"}}
db.Table("users").BatchInsert(users, 100)

// 游标查询
var results []User
db.Table("users").Cursor(10, 0).Select(&results)

// 分页查询
var results []User
total, _ := db.Table("users").Paginate(1, 10).Select(&results)

// 统计数量
total, _ := db.Table("users").Count()


// 批量删除
db.Table("users").BatchDelete([]int{1,2,3})

// 更新
db.Table("users").Where("id", 1).Update(map[string]interface{}{"name": "张三"})

// 查询单条
var user User
db.Table("users").Where("id", 1).Find(&user)

// 查询多条
var users []User
db.Table("users").Where("age", ">", 18).Select(&users)

// 基础INNER JOIN
var orders []Order
db.Table("orders").
	Join(JoinInner, "customers", "customers.id = orders.customer_id").
	Where("customers.status = ?", "active").
	Select(&orders)

// 多表JOIN带别名
var results []struct {
	UserName  string
	OrderID   int
	Product   string
}
db.Table("users u").
	Join(JoinLeft, "orders o", "o.user_id = u.id").
	Join(JoinInner, "products p", "p.order_id = o.id").
	Fields("u.name as user_name", "o.id as order_id", "p.product_name").
	Where("u.created_at > ?", "2023-01-01").
	Order("u.name ASC").
	Limit(10).
	Select(&results)

// 带参数的复杂JOIN条件
var logs []LoginLog
db.Table("users u").
	Join(JoinLeft, "login_logs l", "l.user_id = u.id AND l.login_time > ?", "2023-06-01").
	Where("u.status = ?", 1).
	Select(&logs)

// 统计关联数据量
total, _ := db.Table("departments d").
	Join(JoinLeft, "employees e", "e.department_id = d.id").
	Where("d.company_id = ?", 123).
	Count()
相关推荐
Nyarlathotep01131 小时前
事务隔离级别
sql·mysql
Nyarlathotep01134 小时前
SQL的事务控制
sql·mysql
NineData21 小时前
NineData智能数据管理平台新功能发布|2026年1-2月
数据库·sql·数据分析
阿里云大数据AI技术2 天前
用 SQL 调大模型?Hologres + 百炼,让数据开发直接“对话”AI
sql·llm
花酒锄作田7 天前
Gin 框架中的规范响应格式设计与实现
golang·gin
tryCbest7 天前
数据库SQL学习
数据库·sql
cowboy2587 天前
mysql5.7及以下版本查询所有后代值(包括本身)
数据库·sql
努力的lpp7 天前
SQL 报错注入
数据库·sql·web安全·网络安全·sql注入
麦聪聊数据7 天前
统一 Web SQL 平台如何收编企业内部的“野生数据看板”?
数据库·sql·低代码·微服务·架构
山峰哥7 天前
吃透 SQL 优化:告别慢查询,解锁数据库高性能
服务器·数据库·sql·oracle·性能优化·编辑器