问题

上一篇我们讲到,对于未知数据类型的解析,UnknownTypeHandler 把部分任务交给了 TypeHandlerRegistry ,甚至可能仅仅只丢了一个 javaType 过来就要求返回一个合适的解析器回去 。TypeHandlerRegistry 到底里面做了啥可以找到合适的解析器呢?

功能

从类的名字我们就知道这个类的功能大概就是个注册表,而且很可能是全局共用的,记录各种 javaType,jdbcType,TypeHandler 的映射关系。实际这个类的核心也就是维护了几个 map 。

核心字段

JDBC_TYPE_HANDLER_MAP

1
private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP = new EnumMap<JdbcType, TypeHandler<?>>(JdbcType.class);

这个很直观,就是注册 jdbcType 和解析器的对应关系。

TYPE_HANDLER_MAP

1
private final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP = new ConcurrentHashMap<Type, Map<JdbcType, TypeHandler<?>>>();

书上原文是:“记录了 java 类型向指定的 JdbcType 转换时,需要使用的 TypeHandler 对象。例如:Java 类型中的 String 转换成数据库的 char 、varchar 等多种类型,所以存在一对多关系”。

Type 是 java.lang.reflect 包下的接口,Class 类实现了此接口。所以此 map 的 key 值是 javaType ,比如 String.class 。

UNKNOWN_TYPE_HANDLER

就是上一篇文章学习过的 UnknownTypeHandler 的实例,主要用在 Object.class 和 JdbcType.OTHER 上。

ALL_TYPE_HANDLERS_MAP

1
private final Map<Class<?>, TypeHandler<?>> ALL_TYPE_HANDLERS_MAP = new HashMap<Class<?>, TypeHandler<?>>();

key 是解析器的 class ,value 是解析器自身。

记录了全部的解析器的类型及该类型相对应的 TypeHandler 对象。

NULL_TYPE_HANDLER_MAP

仅仅是一个空 TypeHandler 集合的标识。因为 TYPE_HANDLER_MAP 是ConcurrentHashMap, 不能塞 null 值,因此在需要的地方以此空标识作为 value 替代null塞入。

注册

在构造方法中,就调用了大量的 register(…) 的方法,注册了很多映射关系。

register 有很多重载方法,除了简单的向 JDBC_TYPE_HANDLER_MAP 注册之外,全都最终指向了下面的重载方法。

1
2
3
4
5
6
7
8
9
10
11
private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
if (javaType != null) {
Map<JdbcType, TypeHandler<?>> map = TYPE_HANDLER_MAP.get(javaType);
if (map == null) {
map = new HashMap<JdbcType, TypeHandler<?>>();
TYPE_HANDLER_MAP.put(javaType, map);
}
map.put(jdbcType, handler);
}
ALL_TYPE_HANDLERS_MAP.put(handler.getClass(), handler);
}

方法并不复杂,我们只需要理解的是这里为两个 map 中都塞入了值,一个是 key 为 javaType 的 map , 也就是 String 与 char/varchar 的一对多的关系。另一个则是记录全部 handler 的map。

这里有一点需要特别留意的就是,比如 String 类,除了映射 char/varchar 之外,还映射 null ,对应的解析器也是 StringTypeHandler 。

1
2
3
4
//这一行就是javaType为String.class jdbcType 为null 的解析器注册
register(String.class, new StringTypeHandler());
//这是普通的注册
register(String.class, JdbcType.CHAR, new StringTypeHandler());

查找 TypeHandler

终于到了查找 TypeHandler 的部分了,看了这么久,是不是差点晕得都忘了我们的这个注册表最核心的功能在这呢?

根据 jdbcType 和 typeHandler 的class 查找解析器的功能都很简单,就是上面的 JDBC_TYPE_HANDLER_MAP 和 ALL_TYPE_HANDLERS_MAP 中取值。

关键的在于根据 javaType 寻找解析器。而且我们的在上一篇也讲到了 PreparedStatement 在赋值的时候,我们没有提供 jdbcType ( null ), 仅仅只有 javaType 。

getTypeHandler 其实就是从 TYPE_HANDLER_MAP 取值.我们入参类型为 String 为例来看看其查找解析器的过程 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private <T> TypeHandler<T> getTypeHandler(Type type, JdbcType jdbcType) {
//首先根据 String.class 查找其一对多的解析器集合.
Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = getJdbcHandlerMap(type);
TypeHandler<?> handler = null;
if (jdbcHandlerMap != null) {
//然后根据其指定的 jdbcType 来找对应的解析器。而此处我们的 jdbcType 是null
//但是在注册的时候我们留意过,对于 null ,同样也进行了注册
//就相当于是给了个默认的解析器
handler = jdbcHandlerMap.get(jdbcType);
if (handler == null) {
handler = jdbcHandlerMap.get(null);
}
if (handler == null) {
handler = pickSoleHandler(jdbcHandlerMap);
}
}
return (TypeHandler<T>) handler;
}

总结

目前我们已经接触过了两种方式对明确指明的 javaType 的转换。主要可以分为两大类:

ObjectTypeHandler

这个主要依靠 JDBC 底层的方法来查找合适的 javaType ,大量的 switch-case 语句。主要用在将返回值的封装到 JSONObject 中。

1
2
3
4
5
6
7
8
9
10
switch (field.getSQLType()) {
case Types.TINYINT:
if (!field.isUnsigned()) {
return Integer.valueOf(getByte(columnIndex));
}
return Integer.valueOf(getInt(columnIndex));
case Types.SMALLINT:
return Integer.valueOf(getInt(columnIndex));
//...
}

TypeHandlerRegistry

注册表功能,将常规的数十种关系映射在初始化时就都注册好,也就是提前存入 map 中,需要的时候去 map 中取。

尤其是将 javaType 和 TypeHandler 关联起来,并对一些类型注册了默认的解析器,即 jdbcType 未指明时所要采用的解析器。

这个更多是用在 PreparedStatement 入参的赋值时。

评论和共享

问题

前面说到,我们可以用 JSONObject 替代习惯使用的 JavaBean ,而之所以能用 JSONObject 主要就是因为它实现了 Map<String,Object>

实际使用我们就会发现,MyBatis 使用JSONObject 封装返回结果的时候很“智能”,数据库里字段是 varchar 类型,JSONObject 中返回值就是 String 类型,数据库字段是 int/float 类型,JSONObject 中返回值就是对应的数值类型。甚至通过 debug 发现数据库中保存了datetime 类型的数据,JSONObject 中保存的是 java.sql.timestamp 类型,而timestamp 类型继承了常见的 java.util.Date

为什么 MyBatis 可以用得这么爽呢?我们实现可完全没声明需要此字段的 javaType 呢。而且用得爽了,类型转换会不会导致程序性能大打折扣呢?

今天我们就从深入源码,探究一番 MyBatis 到底是怎样做到对未明确声明的字段处理返回类型的。

TypeHandler

MyBatis 类型转换的核心就是这个接口,定义的方法可以看做就两种 setParameter 和 getResult ,很好理解,我们传参和接收 sql 结果时就调用这个。

1
2
3
4
5
6
7
8
9
10
11
public interface TypeHandler<T> {

void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

T getResult(ResultSet rs, String columnName) throws SQLException;

T getResult(ResultSet rs, int columnIndex) throws SQLException;

T getResult(CallableStatement cs, int columnIndex) throws SQLException;

}

抽象类 BaseTypeHandler 部分实现了 TypeHandler ,主要完成了对空值的处理。 非空值的处理全部交给了子类完成。

BaseTypeHandler 子类非常多,对应了数据库的各种数据类型,实现都很简单,比如SqlTimestampTypeHandler 处理 Timestamp 类型。

1
2
3
4
5
6
7
8
9
10
11
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Timestamp parameter, JdbcType jdbcType)
throws SQLException {
ps.setTimestamp(i, parameter);
}

@Override
public Timestamp getNullableResult(ResultSet rs, String columnName)
throws SQLException {
return rs.getTimestamp(columnName);
}

都是直接调用了 PreparedStatement 和 ResultSet 处理相应类型字段的方法。

很明显,一旦指明了我们需要 MyBatis 给我们返回的此字段类型,MyBatis 肯定就去找到对应的 TypeHandler 实现类去处理。而我们没有指定返回类型的是怎么处理的呢?或者说,对于 Object 类型是怎么处理的呢?

ObjectTypeHandler

我们先来看看这个类,看名字就会猜可能估计未知类型全靠它了吧。提前预告下,并不是哦,getNullableResult 还算经常使用,入参赋值就没见用了,毕竟入参的 JavaType 我们通过反射还是可以找到的。

1
2
3
4
5
6
7
8
9
10
11
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType)
throws SQLException {
ps.setObject(i, parameter);
}

@Override
public Object getNullableResult(ResultSet rs, String columnName)
throws SQLException {
return rs.getObject(columnName);
}

一看这实现,居然还是调用的 JDBC 底层的对应方法。事实上 com.mysql.jdbc 在处理Object 类型是也是通过大量的 if-else 或 switch-case 来找到本数据真正的类型的。入参绑定依据 parameterObj instanceof 各种类型,返回结果类型依据 field.getSQLType 的各种类型。

UnknownTypeHandler

事实上,MyBatis 在很多我们没有指明参数类型的情况下,都是使用 UnknownTypeHandler 来解决类型转换的。UnknownTypeHandler 中的核心resolveTypeHandler 方法,就是查找对应数据的类型解析器(TypeHandler) , 再用这个合适的 typeHandler 进行解析。

resolveTypeHandler 方法的重载有3种,主要的两种就是一种处理入参的,一种处理返回结果的。

入参类型解析

1
2
3
4
5
6
7
8
9
10
11
12
13
private TypeHandler<? extends Object> resolveTypeHandler(Object parameter, JdbcType jdbcType) {
TypeHandler<? extends Object> handler;
if (parameter == null) {
handler = OBJECT_TYPE_HANDLER;
} else {
handler = typeHandlerRegistry.getTypeHandler(parameter.getClass(), jdbcType);
// check if handler is null (issue #270)
if (handler == null || handler instanceof UnknownTypeHandler) {
handler = OBJECT_TYPE_HANDLER;
}
}
return handler;
}

在入参为 null 或者实在找不到解析器的情况下,就会返回我们上面讲的 ObjectTypeHandler 。

而这里面关键的方法就是

1
typeHandlerRegistry.getTypeHandler(parameter.getClass(), jdbcType);

typeHandlerRegistry 可以理解为全局共用的各种类型与解析器关系的注册表,后面的文章还会继续深入讲解,我们首先注意这个方法的入参,第一个参数拿到了参数的 class , 第二个参数拿到了 jdbcType 。这不就相当于javaType 和 jdbcType 都有了吗?那即使还没研究 typeHandlerRegistry 到底干了啥,但是条件给的这么充分了,注册表的任务也太轻松了吧!

等等,我们的 DAO 层给入参是 JSONObject 类型,里面 username 字段是 String 类型,money 字段是 float 类型,这些都能通过 getClass() 获取到确实没毛病。但是 jdbcType 是哪来的呢?我们现在可是在处理 PreparedStatement 呢!

通过 debug 我们发现,这里的 jdbcType 我们没有指明的情况下,确实都是 null 。说明 typeHandlerRegistry 里仅仅是通过 javaType 来寻找解析器的。所以 typeHandlerRegistry 还是有很多门道等着我们去探索哦。

返回结果类型解析

对 ResultSet 的解析有两种方式,首先查看此结果字段–比如nickname–在 field 中的序号,如果没序号,则直接返回 ObjectTypeHandler 。如果有序号,则进入下面的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private TypeHandler<?> resolveTypeHandler(ResultSetMetaData rsmd, Integer columnIndex) throws SQLException {
TypeHandler<?> handler = null;
//通过下面两个方法获取到jdbcType和javaType
JdbcType jdbcType = safeGetJdbcTypeForColumn(rsmd, columnIndex);
Class<?> javaType = safeGetClassForColumn(rsmd, columnIndex);
//后面的任务就还是交给了typeHandlerRegistry
if (javaType != null && jdbcType != null) {
handler = typeHandlerRegistry.getTypeHandler(javaType, jdbcType);
} else if (javaType != null) {
handler = typeHandlerRegistry.getTypeHandler(javaType);
} else if (jdbcType != null) {
handler = typeHandlerRegistry.getTypeHandler(jdbcType);
}
return handler;
}

其中的关键方法还是从 rsmd 中获取 jdbcType 和 javaType ,然后再通过 typeHandlerRegistry 去查找对应的 handler 。

debug 发现几乎每次从 rsmd 中获取 jdbcType 和 javaType 都获取到了,看来玄机都在safeGetJdbcTypeForColumnsafeGetClassForColumn 中了。

两个方法的关键代码分别如下

1
2
3
return JdbcType.forCode(rsmd.getColumnType(columnIndex));

return Resources.classForName(rsmd.getColumnClassName(columnIndex));

JdbcType

com.mysql.jdbc.ResultSetMetaData 实现了 java.sql.ResultSetMetaData 接口,此处我们调用了其中的

1
2
3
public int getColumnType(int column) throws SQLException {
return getField(column).getSQLType();
}

序号的作用就体现出来了,根据序号找到此 field ,再找其 SQLType ,根据 SQLType 去JdbcType 类 (enum类型) 内查找对应的 jdbcType。

JdbcType 类内维护了一个 map 类型静态变量 codeLookup ,类加载时为 codeLookup 添加了39个元素,key 值其实就是 SQLType , int 类型,value 就是本 jdbcType 。

因此根据 SQLType 在此处就直接能毫不费力地找出对应的 jdbcType。

1
2
3
4
5
static {
for (JdbcType type : JdbcType.values()) {
codeLookup.put(type.TYPE_CODE, type);
}
}

javaType

同样需要先找到 SQLType ,以及field 内的另外几个属性值,例如 isUnsigned 等一起进入 getClassNameForJavaType 方法找到对应类名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static String getClassNameForJavaType(int javaType, boolean isUnsigned, int mysqlTypeIfKnown, boolean isBinaryOrBlob, boolean isOpaqueBinary,
boolean treatYearAsDate) {
switch (javaType) {
case Types.BIT:
case Types.BOOLEAN:
return "java.lang.Boolean";
case Types.TINYINT:
if (isUnsigned) {
return "java.lang.Integer";
}
return "java.lang.Integer";
//......大量 case
default:
return "java.lang.Object";

而这里的大量的 swtich-case 终于算是解除了我们一部分的疑惑了!底层终归还是通过 swtich-case 这种最原始的操作来把 jdbcType 映射到 java 类里去的!

奇怪的现象

上面说到,如果没序号,则直接返回 ObjectTypeHandler。

序号是getColumnType(int column) 用于找到到对应的 field 的关键属性。

那么为什么有可能会没有序号呢?

通过 debug 我们发现,明明 fields 内有 8 个元素,每个字段的原始名和别名都清清楚楚,到 columnIndexLookup 里居然只剩下 6 个? 很显然,问题出在了

1
String name = rsmd.getColumnName(i);

中间有几次取出了重复的 name 。为什么会有重复的 name 呢,我们进入getColumnName 一探究竟。

1
2
3
4
5
6
7
8
9
10
11
12
13
public String getColumnName(int column) throws SQLException {
if (this.useOldAliasBehavior) {
//如果设置了使用别名的属性,就获取此field的别名。
return getField(column).getName();
}
//获取此field的name属性,而不是别名。
String name = getField(column).getNameNoAliases();
if (name != null && name.length() == 0) {
//如果连原始字段名都找不到,就还是获取别名
return getField(column).getName();
}
return name;
}

实际debug发现,我们每次都是通过 getNameNoAliases 找到 name 的。再底层的代码就不需要贴了,看到这里我们就明白了。这里的 name 其实是每个字段的数据库内的字段名,而不是我们定义的别名,所以才会出现重复的情况,比如 user 表有 id 字段,address 表同样会有 id 字段。

总结

今天我们分析完了类型转换器,发现对于未知的数据类型,有一部分是通过ObjectTypeHandler 解析,其底层的用了com.mysql.jdbc.ResultSetImpl.getObject 内的依据 Field.SQLType 的 swtich-case 。

另一部分则是通过UnknownTypeHandler 去查找合适的解析器来解析。

关于查找解析器的步骤,我们将进入下一层级TypeHandlerRegistry 来继续学习。

评论和共享

问题

上一文介绍了 JSONObject 接受 MyBatis 的结果集的简单用法,但是在处理一对多的情况时,单纯的JSONObject就不好使了。

比如要查询一个角色下的多个用户,resultMap如下定义

1
2
3
4
5
6
7
8
<resultMap id="roleMap" type="com.alibaba.fastjson.JSONObject">
<id column="roleId" property="roleId"/>
<result column="roleName" property="roleName"/>
<collection property="users" ofType="com.alibaba.fastjson.JSONObject">
<id column="userId" property="userId"/>
<result column="nickname" property="nickname"/>
</collection>
</resultMap>

期望查出来的users 属性对应着一个数组,

然而实际查出来只是一个对象,只有一条数据。

解决方案

只需要建一个实体类继承 JSONObject ,里面有你要的集合类型的成员变量,就足够了。

比如我建的 One2Many 类:

1
2
3
public class One2Many extends JSONObject {
private List<JSONObject> users;
}

然后xml改为

1
2
3
4
5
6
7
8
<resultMap id="roleMap" type="com.heeexy.example.util.model.One2Many">
<id column="roleId" property="roleId"/>
<result column="roleName" property="roleName"/>
<collection property="users" ofType="com.alibaba.fastjson.JSONObject">
<id column="userId" property="userId"/>
<result column="nickname" property="nickname"/>
</collection>
</resultMap>

是不是非常简单?

更棒的是,这个 One2Many 类是可以复用的,里面再添加其它的成员变量就 OK 了。而且 Dao 层不需要改动,外面正常的还是用 JSONObject 就可以了。

原理

MyBatis 在处理嵌套结果的时候,会判断这个属性的类型,如果是集合,就会初始化一个集合来接收这个属性,否则就只是一个普通的 Object 了。

什么,不满意这个答案?那就拿出源码来吧!

首先我们直接看到最底层判断这个属性是不是集合的这段源码:

DefaultResultSetHandler.instantiateCollectionPropertyIfAppropriate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
private Object instantiateCollectionPropertyIfAppropriate(ResultMapping resultMapping, MetaObject metaObject) {
final String propertyName = resultMapping.getProperty();
//先去拿在metaObject已经存好的这个属性值,我们这里以users属性为例
Object propertyValue = metaObject.getValue(propertyName);
if (propertyValue == null) {
//在这里拿你的users的java类型,由于我们使用的是JSONObject,这里的类型都会返回Object,如果是One2Many,这里就会拿到List类型
Class<?> type = resultMapping.getJavaType();
if (type == null) {
type = metaObject.getSetterType(propertyName);
}
try {
//判断属性类型是不是集合,如果是结合才会初始化一个集合的值返回到下一步,否则都会返回null
if (objectFactory.isCollection(type)) {
propertyValue = objectFactory.create(type);
metaObject.setValue(propertyName, propertyValue);
return propertyValue;
}
} catch (Exception e) {
throw new ExecutorException("Error instantiating collection property for result '" + resultMapping.getProperty() + "'. Cause: " + e, e);
}
} else if (objectFactory.isCollection(propertyValue.getClass())) {
//propertyValue不为空的情况,即我们的JSONObject里已经塞入了users属性,即使是这样,MyBatis还是要求你塞入的users属性必须是集合,才返回到下一步,否则还是会返回null到下一步。
return propertyValue;
}
return null;
}

从上面这段代码我们就知道 MyBatis 确实有做这个判断,你定义的 users 属性到底是不是集合类型,

  1. 如果是并且没有初始化好的话,就帮你初始化一个集合到下一步,
  2. 如果已经初始化好了(通常这时候就是已经塞入了几个 user 对象了),就直接返回这个 users 的值到下一步
  3. 如果不是集合类型,就返回 null 到下一步。

那么下一步到底是干啥呢?正常情况下应该就是继续往 users 集合里添加元素吧。

DefaultResultSetHandler.linkObjects

1
2
3
4
5
6
7
8
9
10
11
private void linkObjects(MetaObject metaObject, ResultMapping resultMapping, Object rowValue) {
final Object collectionProperty = instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject);
if (collectionProperty != null) {
//如果上一步返回来的不是null,那就向这个集合里添加元素
final MetaObject targetMetaObject = configuration.newMetaObject(collectionProperty);
targetMetaObject.add(rowValue);
} else {
//上一步返回了null,那就直接把这个属性赋上本值,就是这种情况导致了上面的第二张图片的情况,我们的users变成了一个对象,而不是想要的数组。
metaObject.setValue(resultMapping.getProperty(), rowValue);
}
}

评论和共享

问题背景

项目后端与前端全部使用 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 项目

评论和共享

  • 第 1 页 共 1 页
作者的图片

heeexy

世上是不是就没有你不认识的字了?


JAVA


南京