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,
}
},
)
驱动支持
- mysql : https://github.com/go-sql-driver/mysql
- sqlite3 : https://github.com/mattn/go-sqlite3
- postgres : https://github.com/lib/pq
- oracle : https://github.com/mattn/go-oci8
- mssql : https://github.com/denisenkom/go-mssqldb
目前实现了以上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
- 参数
table 参数, 可以是字符串, 也可以是 User 结构体
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 sub query
// 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 sub builder
// 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 方法
-
Table
-
Select
-
SelectRaw
-
AddSelect
-
Join
-
GroupBy
-
Having
-
HavingRaw
-
OrHaving
-
OrHavingRaw
-
OrderBy
-
Limit
-
Offset
-
Where
-
WhereRaw
-
OrWhere
-
OrWhereRaw
-
WhereBetween
-
OrWhereBetween
-
WhereNotBetween
-
OrWhereNotBetween
-
WhereIn
-
OrWhereIn
-
WhereNotIn
-
OrWhereNotIn
-
WhereNull
-
OrWhereNull
-
WhereNotNull
-
OrWhereNotNull
-
WhereLike
-
OrWhereLike
-
WhereNotLike
-
OrWhereNotLike
-
WhereExists
-
WhereNotExists
-
WhereNot
-
Get
-
First
-
Find
-
Insert
-
Update
-
Delete
-
Max
-
Min
-
Sum
-
Avg
-
Count
-
InsertGetId
-
Upsert
-
InsertOrIgnore
-
Exists
-
DoesntExist
-
Pluck
-
List
-
Value
-
Paginate
-
Increment
-
Decrement
-
IncrementEach
-
DecrementEach
-
Truncate
-
Union
-
UnionAll
-
SharedLock
-
LockForUpdate
额外增加的 api
-
Replace
-
Page
-
LastSql
-
WhereBuilder
-
OrWhereBuilder
-
WhereSub
-
OrWhereSub
-
WhereNested
-
OrWhereNested
-
To
-
Bind
-
ListTo
-
PluckTo
-
ValueTo
-
SumTo
-
MaxTo
-
UnionTo
-
UnionAllTo