目前项目中涉及到前端编辑后,向后台做请求更新、写入到数据库的场景。
前端是一个表格控件,可以在单元格中编辑各种类型的数据。
旧做法
- 前端JS构造或者提取JSON对象(从表格控件中根据id获取行数据);
- 对JSON对象进行装饰,剔除前端自身增加的一些字段,去除可能影响对象转换的一些空字段;
- ajax请求到后台Spring MVC框架;
- Spring MVC做对象转换,从JSON对象转换为对应的类对象;
- JPA执行对象保存,并返回保存结果;
- 前端执行刷新;
弊端
这样的处理有如下的问题:
- 前后端交互的信息量较大(本来仅修改了一个字段,但是更新的时候需要上传整个对象的所有信息);
- 后端对于更新信息不够明确(接收到的是整个的对象,后台搞不清楚前台究竟做了什么事情需要更新);
- 前端代码很啰嗦,需要做构造对象的事情,而且需要剔除掉那些可能在Spring MVC的转换中捣乱的字段(前台显示必要,增加了一些字段);
改造构思
基于前面的分析,考虑的做法就是:前端编辑了哪个属性数据,就向后台请求更新哪个属性数据;不做额外的事情。
这样,前端向后台发送的数据就是:id,field,value;也就是数据ID、编辑的属性、属性的新值。
存在的一个问题就是:前端编辑的数据包括字符串、整型、浮点数、布尔型等各种类型;而JS作为一个弱类型语言,不能强求去做数据类型解析的事情。
综上考虑:
后台利用Java反射机制来获取属性类型,然后获取相应的set方法进行更新存储。
实现
- Controller层:
增加相应的接口方法updateXXXInfo,接收前端请求,参数为id,field,value(三者都是String类型,id为uuid); - Service层:
根据id获取到对应的Java类对象(JPA的findOne方式);
做相关的有效性校验;
使用反射进行更新,代码如下;
public static boolean update(Class clazz, Object object, String field, String value) {
String formatFieldName = field.substring(0, 1).toUpperCase() + field.substring(1);
String getMethodName = "get" + formatFieldName;
String setMethodName = "set" + formatFieldName;
try {
Method getMethod = clazz.getMethod(getMethodName);
Class type = getMethod.getReturnType();
Method setMethod = clazz.getMethod(setMethodName, type);
String typeName = type.getSimpleName();
String trimedValue = value.trim();
if (typeName.equalsIgnoreCase("String")) {
setMethod.invoke(object, trimedValue);
} else if (typeName.equalsIgnoreCase("Boolean")) {
setMethod.invoke(object, Boolean.parseBoolean(trimedValue));
} else if (typeName.equalsIgnoreCase("Double")) {
setMethod.invoke(object, Double.parseDouble(trimedValue));
} else if ((typeName.equalsIgnoreCase("Integer")) || typeName.equalsIgnoreCase("int")) {
setMethod.invoke(object, Integer.parseInt(trimedValue));
} else if (typeName.equalsIgnoreCase("Byte")) {
setMethod.invoke(object, Byte.parseByte(trimedValue));
} else if (typeName.equalsIgnoreCase("Short")) {
setMethod.invoke(object, Short.parseShort(trimedValue));
} else if (typeName.equalsIgnoreCase("Long")) {
setMethod.invoke(object, Long.parseLong(trimedValue));
} else if (typeName.equalsIgnoreCase("Float")) {
setMethod.invoke(object, Float.parseFloat(trimedValue));
} else if (typeName.equalsIgnoreCase("Date")) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
setMethod.invoke(object, sdf.parse(trimedValue));
}
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
遗留的一点问题
上面的反射方法中,属性的类型信息是通过get方法的returnType来获取的(这样做的原因在于,如果直接获取Field,那么存在父类中的Field获取不到的问题)。
而get方法的获取,需要注意boolean类型属性的处理(IDE默认生成的get方法是以is做前缀,而不是get做前缀的)。
合理的处理方式是在反射方法中catch相关异常进行处理,因为项目中boolean类型目前就一两个字段,简单处理为get前缀了。