ORM规范API通用格式及禁止联表查询方案实现ORM
什么是ORM?
大部分程序员对ORM的理解就是不用写SQL,通过对象的方式来增删改查数据,这种对ORM的理解可以说是偏差很大,如果通读百度百科对于ORM的解释,没有任何描述是关于写SQL的,我们先看一下百度百科的解释:
对象关系映射(Object Relational Mapping),是一种程序技术,用于实现面向对象编程语言里不同类型系统的数据之间的转换。
着重看标红部分,比如说PHP写API接口,Android调用此接口,这就是不同类型系统的数据之间的转换。
如何写出符合ORM的接口?
实例演示:
文章表:articles字段:id user_id title content created_at updated_at
评论表:comments
字段:id user_id article_id content created_at updated_at
用户表:users
字段:id name avatar created_at updated_at
文章列表接口 /articles:
需求:文章id 文章标题 用户名 用户头像
常用联表实现:
SELECT a.id, a.title, u.name, u.avatar FROM articles AS a, users AS u WHERE a.user_id=u.id
返回结果:
[
{
"id": 1,
"title": "文章标题1",
"name": "用户1",
"avatar": "头像地址1"
},
{
"id": 2,
"title": "文章标题2",
"name": "用户2",
"avatar": "头像地址2"
},
]
这样写会有什么问题???
我们先看Android如何解析这个文章列表接口:
先建个bean,跟接口字段一一对应,如下
public class ArticleBean {public int id;
public String title;
public String name;
public String avatar;
}
如果Android端要做缓存,在sqlite里建了一张articles表,表里字段id、title、name、avatar;
现在问题来了,本来name、avatar是服务器users表中的字段,到了Android端变成了articles中的字段,这样就不符合ORM规范。
文章列表接口符合ORM到底要长什么样?先看正确格式:
[
{
"id": 1,
"user_id" => 1,
"title": "文章标题1",
"user": {
"id" => 1,
"name": "用户1","avatar": "头像地址1"
}
},
{
"id": 2,
"user_id" => 2,
"title": "文章标题2",
"user": {
"id" => 2,
"name": "用户2",
"avatar": "头像地址2"
}
}
]
再看看这样的格式Android要如何解析?
建一个ArticleBean和一个UserBean,如下
public class ArticleBean {public int id;
public int user_id;
public UserBean user; // 注意这里
}
public class UserBean {
public int id;
public String name;
public String avatar;
}
这个时候Android如果要做缓存,会建articles和users表,这样就保证了客户端跟服务器数据表是一一对应,这样才符合ORM规范。
练习一下:最新文章评论列表接口怎么写?
[{
"id": 1,
"artile_id": 1,
"user_id": 1,
"content": "评论内容1",
"artile": {
"id": 1,
"title": "文章标题1"
},
"user": {
"id": 1,
"name": "用户1",
"avatar": "头像地址1"
}
}
]
符合ORM规范的API接口的优势:
1、移动端model与服务器model一一对应,不同业务就是不同model的组合。
2、移动端使用第三方json框架非常容易解析,不用写一行代码,比如 Android:Gson Objective-C:MJExtension。
3、数据表增加属性服务器端和移动端都很简单,比如增加一个用户等级字段level,如果按以前的方式客户端要在所有bean里找到需要用到level的bean,再一一添加,现在只需要在userBean添加level属性就OK了。
PHP如何写出如何符合ORM的API?
文章列表接口:
$articles = 'SELECT id, user_id, title FROM `articles`';
// 把文章表中的user_id字段归入一个数组中
$userIdArr = [];forearh($articles as $article) {
$userIdArr[] = $article['user_id'];
}
// 通过userId数组查询出所需要的user
$users = 'SELECT id, name, avatar FROM `users` WHERE id IN ' . '(' . implode(',', $userIdArr) . ')';
// 用用户id做key,创建出一个新的用户数组
$usersNew = [];forearh($users as $user) {
$usersNew[$user['id']] = $user;
}
// 循环文章数组,通过用户id获取到对应的用户
forearh($articles as $article) {$articles['user'] => $usersNew[$article['user_id']];
}
大家看起来感觉肯定很费劲,本来联表查询两三行的代码变成了十几行,这还只是两张表,如果是最新文章评论接口,涉及到三张表更麻烦,如果以这种方式要求程序员写出符合ORM规范的接口肯定有抵触情绪;
如果让大家开心快乐又省心又省力的写出符合ORM规范的接口呢?
后文会有解决方案,先不急,我们先看看一种不常见的优化MySQL的方案:禁止联表方案。
MySQL优化方案:禁止联表
高性能MySQL_第3版 6.3.3章节:分解关联查询
补充:
优化慢查询:针对单表SQL的优化肯定比多表联合SQL优化要简单。
如何方便的把禁止联表方案和ORM结合一起?
Eloquent ORM的使用:
class User extends Model { }
public function user() {
return $this->belongsTo('App\User');
}
}
public function user() {
return $this->belongsTo('App\User');
}
public function article() {
return $this->belongsTo('App\Article');
}
}
文章列表:
$articles = Article::with('user')->get(); // 一行代码代替了上面的十几行代码
最新文章评论列表:
Eloquent ORM跟禁止联表有毛关系?
我们把查询文章列表的SQL打印出来:
DB::enableQueryLog();
$articles = Article::with('user')->get();
print_r(DB::getQueryLog());
Array(
[0] => Array (
[query] => select * from `articles`
[bindings] => Array ( )
[time] => 0
)
[1] => Array (
[query] => select * from `users` where `users`.`id` in (?, ?, ?, ?)
[bindings] => Array (
[0] => 1
[1] => 2
[2] => 3
[3] => 4
)
[time] => 1
)
)
这里可以看到,Eloquent ORM跟我们刚才十几行代码查询的方式一样,也是先取出文章表中的用户id,然后通过用户id查询到所需要的用户,再合到文章数组中。
DB::enableQueryLog();
$comments = Comment::with('user')->with('article')->get();
print_r(DB::getQueryLog());
Array(
[0] => Array (
[query] => select * from `comments`
[bindings] => Array ( )
[time] => 0
)
[1] => Array (
[query] => select * from `users` where `users`.`id` in (?)
[bindings] => Array (
[0] => 1
)
[time] => 0
)
[2] => Array (
[query] => select * from `articles` where `articles`.`id` in (?)
[bindings] => Array (
[0] => 1
)
[time] => 0
)
)
很方便是吧,当时看到Eloquent ORM是这种分表查询的方式,很激动,刚好跟禁止联表查询方案思路很符合,推广起来大家也愿意接受。
总结:
1、符合ORM规范的API可以大大提高客户端的对接效率;
2、使用Eloquent ORM,可以节省程序员20-30%的开发时间;
3、Eloquent ORM非常好用,个人觉得也是Laravel在PHP框架中排行第一的很重要的一个原因;
4、Laravel开发后台 + Lumen开发接口,完美搭配。
曾经跟一个携程的哥们和一个腾讯的哥们聊这个方案,他们认为这个方案适合服务器跟客户端打交道,如果只是服务器之间的API调用,或者微服务之间的API调用,这方案还适合吗,比如PHP对接文章列表接口,对接项目还需要建ArticleModel、UserModel、CommentModel,需要这么麻烦吗?其实如果使用过Apache Thrift(远程服务调用框架 )或者Go语言中使用很广泛的Protobuf数据交换格式,这两种RPC框架都会自动生成各个语言的扩展包,扩展包包含类似的Model集合,然后由对接项目引入这个扩展包进行对接,所以ORM在这些RPC框架里都是广泛存在的,只不过PHP语言里应用的很少。
ORM不仅仅可以作用于服务器与移动端的交互,也可以作用于前后端分离、RPC调用,前后端分离跟上述的方式差不多,我们再看看ORM在RPC中的使用。