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 <[email protected]>
 * @param (?1:)
 * @return (?1:)
 */
func NewDB() *DB {
	o := orm.NewOrm()
	return &DB{
		ormer: o,
		qs:    nil, // 不再使用空表名
	}
}

/**
 * Table 设置表名
 * @Author Xven <[email protected]>
 * @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 <[email protected]>
 * @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 <[email protected]>
 * @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 <[email protected]>
 * @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 <[email protected]>
 * @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 <[email protected]>
 * @param (?1:)
 * @return (?1:)
 * 示例:Fields("name", "email")
 */
func (d *DB) Fields(fields ...string) *DB {
	d.fields = fields
	return d
}

/**
 * Order 排序
 * @Author Xven <[email protected]>
 * @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 <[email protected]>
 * @param (?1:)
 * @return (?1:)
 * 示例:Limit(10)
 */
func (d *DB) Limit(limit int) *DB {
	d.qs = d.qs.Limit(limit)
	return d
}

/**
 * Page 分页
 * @Author Xven <[email protected]>
 * @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 <[email protected]>
 * @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 <[email protected]>
 * @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 <[email protected]>
 * @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 <[email protected]>
 * @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 <[email protected]>
 * @param (?1:)
 * @return (?1:)
 * 示例:db.Table("users").Where("id", 1).Delete()
 */
func (d *DB) Delete() (int64, error) {
	return d.qs.Delete()
}

/**
 * BatchDelete 根据主键批量删除
 * @Author Xven <[email protected]>
 * @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 <[email protected]>
 * @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 <[email protected]>
 * @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 <[email protected]>
 * @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 <[email protected]>
 * @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 <[email protected]>
 * @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 <[email protected]>
 * @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 <[email protected]>
 * @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 <[email protected]>
 * @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()
相关推荐
yuanlaile2 小时前
Gin介绍及Gin环境搭建
golang·gin·gin环境搭建
大数据魔法师2 小时前
基于SpringBoot和Vue的SQL TO API平台的设计与实现
vue.js·spring boot·sql
techdashen2 小时前
性能比拼: Go(Gin) vs Python(Flask)
python·golang·gin
techdashen3 小时前
性能比拼: Go标准库 vs Python FastAPI
python·golang·fastapi
alenliu06213 小时前
Go 语言规范学习(7)
golang
神经毒素3 小时前
WEB安全--SQL注入--无列名注入
sql·安全·web安全
Ai 编码助手4 小时前
Golang并发编程:Data Race检测与解决方案
开发语言·后端·golang
宦如云4 小时前
Assembly语言的嵌入式调试
开发语言·后端·golang
二狗哈7 小时前
go游戏后端开发20:房间消息推送处理
开发语言·游戏·golang
豆浆whisky7 小时前
Go语言内存管理揭秘:三级分配器架构与性能优化|Go语言进阶(2)
性能优化·架构·golang