MyBatis 中使用 JSONObject 替代 JavaBean

# 问题背景

项目后端与前端全部使用 JSON 进行数据交互,比如查询用户列表,通常后端从数据库查出的数据放入实体类再转为 JSON 返回给前端,但是前端的小伙伴表示明明只需要 nickname,avatar,userId 三个字段,为什么传过来的数据多出 phone,address ,profile 之类十几个字段,里面都 null,0 等值?

其实是因为 User 实体类定义了太多字段,转为 JSON 的时候都被包括进去了。稍微多几个还能忍,但通常 User/Order 这些实体类字段特别多,对调试开发实在非常不友好。

因此本次任务就是:确保每个接口返回字段都与接口文档上一模一样,去除冗余字段。

解决过程就省略了,直接抛出我最后的解决方案吧:就是 MyBatis 的返回值几乎完全抛弃实体类,全部使用com.alibaba.fastjson.JSONObject

# 使用 JSONObject

先最简化直观的看一下 JSONObject 的使用吧。

Controller:

1
2
3
4
@GetMapping("/list")
public JSONObject listUser(@RequestBody JSONObject requestJson){
    return userService.listUser(requestJson);
}

Service:

1
JSONObject listUser(JSONObject jsonObject);

ServiceImpl:

1
2
3
4
5
6
7
8
9
@Override
public JSONObject listUser(JSONObject jsonObject) {
  	//fillPageParam是自定义的封装分页参数
    CommonUtil.fillPageParam(jsonObject);
    int count = userDao.countUser(jsonObject);
    List<JSONObject> list = userDao.listUser(jsonObject);
    //自定义successPage封装分页结果
    return CommonUtil.successPage(jsonObject, list, count);
}

Dao:

1
2
3
4
public interface UserDao {
    int countUser(JSONObject jsonObject);
    List<JSONObject> listUser(JSONObject jsonObject);
}

UserMapper.xml , 这里的 resultType 直接就是 JSONObject ,如果是一对多的情况,就要多加一步,请移步看这篇文章

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<select id="countUser" resultType="Integer">
    SELECT count(0)
    FROM sys_user u
  	WHERE age=#{age}
</select>
<select id="users" resultType="com.alibaba.fastjson.JSONObject">
     SELECT
   		id  		userId,
   		nickname     	nickname,
   		avatar       	avatar
     FROM sys_user
  	 WHERE age=#{age}
     LIMIT #{offSet}, #{pageRow}
</select>

# 为什么可以使用JSONObject

因为 JSONObject 实现了 Map<String,Object> , MyBatis 那边完全是把它当成 Map<String,Object> 处理的,相信不少人都直接用过 map 来接收MyBatis 返回结果。

fastjson 是这样的,Gson 并不是,所以 Gson 的 JsonObject 是不可以的。

有兴趣了解更深入的原理的话,请参考我此系列的其它文章。

# 优劣对比

# 便捷性

JSONObject 便捷到可以说是无脑,接收参数、 sql 传参、封装 sql 结果、返回到前端,全程使用。

JavaBean 则接口层接收参数转为实体类,返回给前端再转为 json,新增一个业务通常还需要多建一个实体类。

# 工具方法拓展

JSONObject 可以方便地封装出通用的工具方法,比如封装分页信息,比如封装处理结果的成功和失败信息,特别是校验参数字段非空,不同的接口通常需要校验的字段不同,如下面这个简单的方法就可以校验各字段是否都非空。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
public static void hasAllRequired(final JSONObject jsonObject, String requiredColumns) {
    if (!StringTools.isNullOrEmpty(requiredColumns)) {
        String[] columns = requiredColumns.split(",");
        String missCol = "";
        for (String column : columns) {
            Object val = jsonObject.get(column.trim());
            if (StringTools.isNullOrEmpty(val)) {
                missCol += column + "  ";
            }
        }
        if (!StringTools.isNullOrEmpty(missCol)) {
            jsonObject.clear();
            jsonObject.put("returnCode", ErrorEnum.E_90003.getErrorCode());
            jsonObject.put("returnMsg", "缺少必填参数:" + missCol.trim());
            throw new CommonJsonException(jsonObject);
        }
    }
} 

JavaBean 在处理分页上通常要引各自写的 PageBean 工具类 , 使用过程通常不会比上面ServiceImpl里的分页简单。

JavaBean 的参数校验都不用说,更头痛更复杂了,不知道有没有人有用起来很爽的方法。

# 返回字段

JSONObject 想返回几个字段就几个字段,而 JavaBean 如果只想返回某几个字段的话,就需要单独建个实体类。

字段有改动的话,JSONObject 灵活性就更强了,直接在 mapper.xml 里改下就完事。

# 业务层

不可否认的是,JSONObject 在业务层进行处理时,失去了 JavaBean 的编译提示,也失去了 IDE 的快捷补全,更有可能出现取错值的情况。

比如 jsonObject.getString(“pasword”) ,输入错了单词都可能没注意,最终取出来 null 。

# 可读性

通常大家可能认为 JavaBean 有更好的可读性,我觉得只要变量名取得好,JSONObject 也是没有什么问题的。毕竟类名是固定死的,变量名和参数名才能更好地反映此处业务的处理。

尤其是如今前后端分离的项目,前后端开发人员一般对照着文档确认字段含义。

# 总结

JSONObject 开发非常方便,具有更强的灵活性,适用于中小型项目,简单业务的开发,也适用于文档至上的前后端分离开发与微服务项目。

更具体的用法,可以参考 Github 项目