这都得益于OQM(Object Query-Language Mapping)技术的加持,我们开发了一个Go版本的实现,GoooQo 。
OQM介绍
OQM技术与传统的ORM(Object Relational Mapping)技术的最大区别是,OQM技术提出直接基于对象构造各种增删查改语句。
其中,最核心的功能就是通过查询对象构建查询子句,这也是OQM名字中Q的由来。
OQM技术中另一个重要的发现就是查询对象的字段名称和查询子句的查询条件可以相互转换。
例如SQL中的查询条件age > ?
,我们只需要将>
改写为字符串别名Gt
(Greater-Than的缩写),再将Gt作为后缀附在列名age后得到ageGt
,即可用作字段的名称。
然后我们使用OQM技术对这样的对象进行解析,在遇到名称为ageGt
的字段被赋值时,只需要逆向进行上述过程就能得到对应的查询条件。
这样,我们只需要创建一个实体对象和一个查询对象,通过实体对象来确定表名和列名,通过在查询对象中定义字段和赋值来控制查询子句的构造。再将各种样板代码进行封装后,为单张表构建增删查改接口的代码就只剩下两行了。
Demo介绍
其中,test.db 是一个 sqlite 数据库,包含一个有4行数据的表t_user
。
id | username | password | mobile | nickname | memo | valid | |
---|---|---|---|---|---|---|---|
1 | f0rb | 123456 | f0rb@163.com | 18888888881 | test1 | 1 | |
2 | user2 | 123456 | test2@qq.com | 18888888882 | test2 | 0 | |
3 | user3 | 123456 | test3@qq.com | 18888888883 | test3 | memo | 1 |
4 | user4 | 123456 | test4@qq.com | 18888888884 | test4 | 1 |
接着我们在 user.go 中为user表定义两个结构体 UserEntity
和 UserQuery
。
UserEntity
就是一个传统的实体对象,字段对应上述表中的列名。
UserQuery
中的字段按照列名加后缀的格式定义,以便生成对应的查询语句(字段后缀请参考文末的后缀映射表):
type UserQuery struct {
goooqo.PageQuery
IdGt *int // id > ?
IdIn *[]int // id IN (?,?,?)
EmailContain *string // email LIKE "%value%"
MemoNull *bool // memo is [NOT] NULL
}
此外,嵌入的goooqo.PageQuery
中定义了PageNumber
、PageSize
、Sort
3个字段,用于构造分页子句和排序子句。
接下来,在main方法里建立好数据库连接后,我们即可使用下面两行代码来为user表构建一个增删查改接口:
userDataAccess := rdb.NewTxDataAccess[UserEntity](tm)
goooqo.BuildRestService[UserEntity, UserQuery]("/user/", userDataAccess)
其中,第一行代码为user表创建一个数据库访问模块,第二行代码使用创建的数据库访问模块为user表创建一个web访问模块。
这个web访问模块负责处理 http://localhost:9090/user/
上的访问请求。
Demo运行
把demo仓库的代码克隆到本地,运行demo.go文件里的main方法启动程序,然后访问以下地址即可查看请求结果:
- http://localhost:9090/user/
- http://localhost:9090/user/3
- http://localhost:9090/user/?pageNumber=2&pageSize=2
- http://localhost:9090/user/?sort=id,desc
- http://localhost:9090/user/?sort=memo,desc%3Bemail,desc
- http://localhost:9090/user/?idIn=1,3
- http://localhost:9090/user/?emailContain=qq
- http://localhost:9090/user/?emailContain=qq&IdGt=2
- http://localhost:9090/user/?emailContain=qq&memoNull=true
- http://localhost:9090/user/?emailContain=qq&memoNull=false
对于查询参数?emailContain=qq&memoNull=false
,将会生成SQL语句SELECT * FROM t_user WHERE email LIKE '%qq' AND memo IS NOT NULL
,对应的返回结果为:
{
"data": {
"list": [
{
"id": 3,
"username": "user3",
"email": "test3@qq.com",
"mobile": "18888888883",
"nickname": "test3",
"memo": "memo",
"valid": true
}
],
"total": 1
},
"success": true
}
我们还可以使用user.http文件里提供的HTTP请求来测试 POST/PUT/PATCH/DELETE
等接口.
例如:
### 添加新的用户
POST http://localhost:9090/user/
Content-Type: application/json
[{
"username": "Ada Wong",
"email": "AdaW@gmail.com",
"mobile": "01066666",
"nickname": "ada",
"memo": "An agent.",
"valid": true
}, {
"username": "Leon Kennedy",
"email": "LeonKennedy@gmail.com",
"mobile": "01077777",
"nickname": "leon",
"memo": "The hero.",
"valid": true
}]
### 为id为3的用户更新指定字段
PATCH http://localhost:9090/user/3
Content-Type: application/json
{
"username": "Leon Kennedy",
"email": "LeonKennedy@gmail.com"
}
### 删除memo字段为NULL的用户
DELETE http://localhost:9090/user/?memoNull=false
后记
GoooQo是OQM技术在继Java版框架DoytoQuery之后的Go语言版本,现在还只是一个得到初步验证的MVP,后续还需多轮迭代才能实现OQM技术提出的所有特性。请大家保持关注,多多支持!
附录:后缀映射表
当前版本支持的后缀以及对应的查询条件可以参考这张后缀映射表:
后缀名称 | 字段名称 | 字段赋值 | SQL查询条件 | MongoDB查询条件 |
---|---|---|---|---|
- | id | 5 | id = 5 | {"id":5} |
Eq | idEq | 5 | id = 5 | {"idEq":5} |
Ne | idNe | 5 | id != 5 | {"idNe":{"$ne":5}} |
Gt | idGt | 5 | id > 5 | {"idGt":{"$gt":5}} |
Ge | idGe | 5 | id >= 5 | {"idGe":{"$gte":5}} |
Lt | idLt | 5 | id < 5 | {"idLt":{"$lt":5}} |
Le | idLe | 5 | id <= 5 | {"idLe":{"$lte":5}} |
In | idIn | [1,2,3] | id IN (1,2,3) | {"id":{"$in":[1, 2, 3]}} |
NotIn | idNotIn | [1,2,3] | id NOT IN (1,2,3) | {"id":{"$nin":[1, 2, 3]}} |
Null | memoNull | false | memo IS NOT NULL | {"memo":{"$not":{"$type", 10}}} |
Null | memoNull | true | memo IS NULL | {"memo":{"$type", 10}} |
NotLike | nameNotLike | "arg" | name NOT LIKE '%arg%' | {"name":{"$not":{"$regex":"arg"}}} |
Like | nameLike | "arg" | name LIKE '%arg%' | {"name":{"$regex":"arg"}} |
NotStart | nameNotStart | "arg" | name NOT LIKE 'arg%' | {"name":{"$not":{"$regex":"^arg"}}} |
Start | nameStart | "arg" | name LIKE 'arg%' | {"name":{"$regex":"^arg"}} |
NotEnd | nameNotEnd | "arg" | name NOT LIKE '%arg' | {"name":{"$not":{"$regex":"arg$"}}} |
End | nameEnd | "arg" | name LIKE '%arg' | {"name":{"$regex":"arg$"}} |
NotContain | nameNotContain | "arg" | name NOT LIKE '%arg%’ | {"name":{"$not":{"$regex":"arg"}}} |
Contain | nameContain | "arg" | name LIKE '%arg%’ | {"name":{"$regex":"arg"}} |
Rx | nameRx | "arg\d" | name REGEXP 'arg\d’ | {"name":{"$regex":"arg\d"}} |
© 2024 Yuan Zhen. All rights reserved.