Home

Awesome

GoRose ORM V3

PHP Laravel ORM 的 go 实现, 与 laravel 官方文档保持一致 https://laravel.com/docs/10.x/queries .
分为 go 风格 (struct 结构绑定用法) 和 php 风格 (map 结构用法).
php 风格用法, 完全可以使用 laravel query builder 的文档做参考, 尽量做到 1:1 还原.

GoRose V3 架构图

安装

目前还处于beta阶段, 请谨慎使用. 由于没有打 tag, 只能使用 go mod 的方式引入

# go.mod
require github.com/gohouse/gorose/v3 master

概览

go风格用法

package main

import (
    gorose "github.com/gohouse/gorose/v3"
    // 引入mysql驱动
    _ "github.com/go-sql-driver/mysql"
)

type User struct {
	Id    int64  `db:"id,pk"`   // 这里的 pk 是指主键
	Name  string `db:"name"`
	Email string `db:"email"`
    
    // 定义表名字,等同于 func (User) TableName() string {return "users"}, 二选一即可
	// TableName string `db:"users" json:"-"` 
}
func (User) TableName() string {
    return "users"
}

var rose = gorose.Open("mysql", "root:123456@tcp(localhost:3306)/test?charset=utf8mb4&parseTime=true")

func db() *gorose.Database {
	return rose.NewDatabase()
}
func main() {
    // select id,name,email from users limit 1;
    var user User
    db().To(&user)

    // insert into users (name,email) values ("test","test@test.com");
    var user = User{Name: "test", Email: "test@test.com"}}
    db().Insert(&user)

    // update users set name="test2" where id=1;
    var user = User{Id: 1, Name: "test2"}
    db().Update(&user)

    // delete from users where id=1;
    var user = User{Id: 1}
    db().Delete(&user) 
}

php风格用法 和 go风格用法查询如下sql对比:

select id,name,email from users where id=1 and name="test"
// php 风格写法
user,err := db().Table("users").
    Select("id","name","email").
    Where("id", "=", 1).Where("name", "test").
    First()
// go 风格写法
var user = User{Id: 1, Name: "test"}
err := db().To(&user)

上边的两种用法结果相同,由此可以看出,除了对 表 模型的绑定区别, 其他方法通用

配置

单数据库连接, 可以直接同官方接口一样用法

var rose = gorose.Open("mysql", "root:123456@tcp(localhost:3306)/test?charset=utf8mb4&parseTime=true")

也可以用

var conf1 = gorose.Config{
    Driver:          "mysql",
    DSN:             "root:123456@tcp(localhost:3306)/test?charset=utf8mb4&parseTime=true",
    Prefix:          "tb_",
    Weight:          0,
    MaxIdleConns:    0,
    MaxOpenConns:    0,
    ConnMaxLifetime: 0,
    ConnMaxIdleTime: 0,
}
var rose = gorose.Open(conf)

或者使用读写分离集群,事务内,自动强制从写库读数据

var rose = gorose.Open(
    gorose.ConfigCluster{
        WriteConf: []gorose.Config{
            conf1,
            conf2,
        },
        ReadConf: []gorose.Config{
            conf3,
            conf4,
        }
    },
)

驱动支持

目前实现了以上5中数据库的支持, 通用数据库,只需要添加更多的 gorose/driver/dialect.IDialect 接口即可
非通用数据库,只要实现了 gorose/driver.IDriver 接口,理论上可以支持任意数据库,包括 redis,mongo 等都可以通过这个接口来实现
所有的 orm 链式操作都在 gorose/builder.Context 中, 直接可以拿到最原始的数据

事务

// 全自动事务, 有错误会自动回滚, 无错误会自动提交
// Transaction 方法可以接收多个操作, 同用一个 tx, 方便在不同方法内处理同一个事务
db().Transaction(func(tx gorose.TxHandler) error {
    tx().Insert(&user)
    tx().Update(&user)
    tx().To(&user)
})

// 手动事务
tx = db().Begin()
tx().Insert(&user)
tx().Update(&user)
tx().To(&user)
tx().Rollback()
tx().Commit()

// 全自动嵌套事务
db().Transaction(func(tx gorose.TxHandler) error {
    tx().Insert(&user)
    ...
    // 自动子事务
    tx().Transaction(func(tx2 gorose.TxHandler) error {
        tx2().Update(&user)
        ...
    }
}

// 手动嵌套事务
var tx = db().Begin()
// 自动子事务
tx().Begin() // 自动 savepoint 子事务
...
tx().Rollback()   // 自动回滚到上一个 savepoint
...
// 手动子事务
tx().SavePoint("savepoint1")    // 手动 savepoint 到 savepoint1(自定义名字)
...
tx().RollbackTo("savepoint1") // 手动回滚到自定义的 savepoint
tx().Commit()

update

在插入和更新数据时,如果使用 struct 模型作为数据对象的时候, 默认忽略类型零值,如果想强制写入,则可以从第二个参数开始传入需要强制写入的字段即可,如:

var user = User{Id: 1, Name: "test"}
// 这里不会对 sex 做任何操作, 
//update user set name="test" where id=1
db().Update(&user)
// 这里会强制将sex更改为0
//update user set name="test", sex=0 where id=1
db().Update(&user, "sex")
// 等同于
db().Table(&user).Where("id", 1).Update(map[string]any{"name": "test", "sex": 0}))

如果没有where条件,则会自动添加tag中指定了pk的字段作为条件,如: db:"id,pk", 因为指定了 pk,如果 id 的值不为0值, 则 id 会作为主键条件更新

insert

参考 update

delete

var user = User{Id: 1}
db().Delete(&user)
// 等同于
db().Table(&user).Where("id", 1).Delete()
// 要加上字段0值条件,只需要传入第二个字段,如:
// delete from users where id=1 and sex=0 and name=""
db().Delete(&user, "sex", "name")

table

db().Table(User{})
db().Table("users")
db().Table(User{}, "u")
db().Table("users", "u")
sub := db().Table("users").Select("id", "name")
db().Table(sub).Where("id", ">", 1).Get()

join

type UserInfo struct {
    UserId      int64   `db:"user_id"`
    TableName   string  `db:"user_info"`
}
db().Table("users").Join(UserInfo{}, "user.id", "=", "user_info.user_id").Get()
// select * from users a inner join user_info b on a.id=b.user_id
db().Table("users", "u").Join(gorose.As(UserInfo{}, "b"), "u.id", "=", "b.user_id").Get()
// 等同于
db().Table(User{}, "u").Join(gorose.As("user_info", "b"), "u.id", "=", "b.user_id").Get()

gorose.As(UserInfo{}, "b") 中, user_info 取别名 b

db().Table("users").Join(UserInfo{}, func(wh builder.IJoinOn) {
    wh.On("a.id", "b.user_id").OrOn("a.sex", "b.sex")
}).Get()
// 等同于
db().Table("users").JoinOn(UserInfo{}, func(wh builder.IJoinOn) {
    wh.On("a.id", "b.user_id").OrOn("a.sex", "b.sex")
}).Get()

Join的第二个参数为builder.IJoinOn时,等同于JoinOn用法(第二个参数有强类型提醒,方便ide快捷提示)

where sub

// where id in (select user_id from user_info)
sub := db().Table("user_info").Select("user_id")
db().Table(User{}).Where("id", "in", sub).Get()
// where id in (select user_id from user_info)
db().Table(User{}).WhereSub("id", "in", func(tx *builder.Context) {
    tx.Table("user_info").Select("user_id")
}).Get()
// where id in (select user_id from user_info)
sub := db().Table("user_info").Select("user_id")
db().Table(User{}).WhereBuilder("id", "in", sub).Get()

以上3种用法等同

where nested

// where id>1 and (sex=1 or sex=2)
db().Table(User{}).Where("id",">", 1).Where(func(wh builder.IWhere) {
    wh.Where("sex", 1).OrWhere("sex", 2)
})

这里的 Where 等同于 WhereNested

// where id>1 and (sex=1 or sex=2)
db().Table(User{}).Where("id",">", 1).WhereNested(func(wh builder.IWhere) {
    wh.Where("sex", 1).OrWhere("sex", 2)
})

以上两种用法等同

子查询

Table,Where,Join内都可以用子查询

sub := db().Table("user").Where().OrWhere()
sub2 := db().Table("address").Select("user_id").Where().OrWhere()
sub3 := db().Table("user_info").Select("user_id").Where().OrWhere()

用在 Table,Where,Join 内

db().
    Table(sub, "a").
    Join(gorose.As(sub2, "b"), "a.id", "=", "b.user_id")).
    WhereIn("a.id", sub3).
    Get()

用在 Union,UnionAll 内

db().
    Table("users").
    Union(sub).
    Get()
var sub2222 = db().Table("user").Where().OrWhere()
var to []User
db().
    Table("users").
    UnionAll(sub, sub2222).
    To(&to)

Pluck

返回两列数据到一个map中,第一列为value,第二列为key

// select id,name from users
db().Table("users").Pluck("name", "id")
// 返回 map[<id>]<name>, 实际得到 map[int64]string{1: "张三", 2: "李四"}

List

返回一列数据到一个数组中

// select id,name from users
db().Table("users").List("id")
// 返回 []<id>, 实际得到 []int64{1,2,3}

To 查询结果绑定到对象

使用结构体字段作为 select 字段
使用结构体字段值作为 where 条件
查询结果绑定到结构体,支持一条或多条

// 查询一条数据
var user User
db().To(&user)

// 查询条件,一条数据
// select id,name,email from users where id=1
var user = User{Id: 1}
db().To(&user)

// 查询多条数据
var users []User
db().To(&users)

// 查询条件,多条数据
var users []User
db().Where("id", ">", 1).To(&users)

Bind 查询结果绑定到对象

仅仅用作查询结果的绑定
结构体字段,不作为查询字段和条件
常用作join或者手动指定字段查询绑定

type Result struct {
    Id    int64  `db:"id"`
    Aname string `db:"aname"`
    Bname string `db:"bname"`
}
var res Result
// select a.id, a.name aname, b.name bname from a inner join b on a.id=b.aid where a.id>1
db().Table("a").Join("b", "a.id","b.aid").Select("a.id", "a.name aname","b.name bname").Where("a.id", ">", 1).Bind(&res)

查询字段的显示名字一定要跟 结构体的字段 tag(db) 名字相同, 否则不会被赋值
字段数量可以不一样

ListTo,PluckTo,ValueTo

var list []int
db().Table("users").ListTo("age", &list)
var pluck map[int64]string
db().Table("users").PluckTo("name","id", &pluck)
var value int
db().Table("users").ValueTo("age", &value)

SumTo,MaxTo,MinTo

var sum int
db().Table("users").SumTo("age", &sum)
var max int
db().Table("users").MaxTo("age", &max)
var min int
db().Table("users").MinTo("age", &min)

日志

默认采用 官方库的 slog debug level, 如果不想显示sql日志, 只需要设置slog的level到debug以上即可, 如: Info, Warn, Error

数据库字段为null的处理

type User struct {
    Id  int64  `db:"id,pk"`
    Sex *int8  `db:"sex"` // 使用指针可以绑定 null 值
    Age sql.NullInt64 `db:"age"` // 使用sql.NullInt64 可以绑定 null 值
}

指针赋值

var sex = int8(1)
var user = User{Id: 1, Sex: &sex}
// 或者,快捷用法
var user = User{Id: 1, Sex: gorose.Ptr(int8(1))}

sql.Null* 赋值

var age = sql.NullInt64{Int64: 18, Valid: true}

sql.Null* 使用

if age.Valid {
    fmt.Println(age.Int64)
}

由此可见,null处理起来还是有点麻烦,所以,建议在设计表的时候,不要允许null即可,给定默认值,而大部分默认值刚好可以与go的类型零值对应

已经支持的 laravel query builder 方法

额外增加的 api