写公司的项目的时候遇到了一个问题,Spring Boot定义的Entity是有默认值的,使用MongoDB Repository进行更新的时候,如果使用postman没有给定其中的field,原来field的值会被默认值所覆盖掉。刚开始写的解决方案很蠢。。就是一个一个field的去更新,这种代码没有复用性,很难维护,需要找到一个替代方案。
Google一了一大顿,尝试使用反射的方式来解决。。感觉变得更麻烦了,不知道还没有更好的方法。
public JsonResult<Ticket> updateTicket(@PathVariable("id") String id, @RequestBody Ticket ticket) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Ticket foundTicket = ticketRepository.findById(id).orElse(null);
Field[] ticketFields = ticket.getClass().getDeclaredFields();
// update every field in ticket
if (foundTicket != null) {
for (int i = 0; i < ticketFields.length; i++) {
String name = ticketFields[i].getName();
name = name.substring(0, 1).toUpperCase() + name.substring(1);
String type = ticketFields[i].getGenericType().toString();
if (type.equals("class java.lang.String")) {
Method ticketGetMethod = ticket.getClass().getMethod("get" + name);
Method foundTicketGetMethod = foundTicket.getClass().getMethod("get" + name);
Method foundTicketSetMethod = foundTicket.getClass().getMethod("set" + name, String.class);
String ticketValue = (String) ticketGetMethod.invoke(ticket);
String foundTicketValue = (String) foundTicketGetMethod.invoke(foundTicket);
if (ticketValue != null) {
foundTicketSetMethod.invoke(foundTicket, ticketValue);
} else if (foundTicketValue != null) {
foundTicketSetMethod.invoke(foundTicket, foundTicketValue);
} else {
foundTicketSetMethod.invoke(foundTicket, "");
}
}
if (type.equals("class com.iimswiss.servicedesk.enums.TicketStatus")) {
Method ticketGetMethod = ticket.getClass().getMethod("get" + name);
Method foundTicketGetMethod = foundTicket.getClass().getMethod("get" + name);
Method foundTicketSetMethod = foundTicket.getClass().getMethod("set" + name, TicketStatus.class);
TicketStatus ticketValue = (TicketStatus) ticketGetMethod.invoke(ticket);
TicketStatus foundTicketValue = (TicketStatus) foundTicketGetMethod.invoke(foundTicket);
if (ticketValue != null) {
foundTicketSetMethod.invoke(foundTicket, ticketValue);
} else if (foundTicketValue != null) {
foundTicketSetMethod.invoke(foundTicket, foundTicketValue);
} else {
foundTicketSetMethod.invoke(foundTicket, "");
}
}
if (type.equals("class com.iimswiss.servicedesk.enums.TicketPriority")) {
Method ticketGetMethod = ticket.getClass().getMethod("get" + name);
Method foundTicketGetMethod = foundTicket.getClass().getMethod("get" + name);
Method foundTicketSetMethod = foundTicket.getClass().getMethod("set" + name, TicketPriority.class);
TicketPriority ticketValue = (TicketPriority) ticketGetMethod.invoke(ticket);
TicketPriority foundTicketValue = (TicketPriority) foundTicketGetMethod.invoke(foundTicket);
if (ticketValue != null) {
foundTicketSetMethod.invoke(foundTicket, ticketValue);
} else if (foundTicketValue != null) {
foundTicketSetMethod.invoke(foundTicket, foundTicketValue);
} else {
foundTicketSetMethod.invoke(foundTicket, "");
}
}
if (type.equals("java.util.ArrayList<java.lang.String>") || type.equals("java.util.ArrayList<java.util.Map<java.lang.String, java.lang.String>>")) {
Method ticketGetMethod = ticket.getClass().getMethod("get" + name);
Method foundTicketGetMethod = foundTicket.getClass().getMethod("get" + name);
Method foundTicketSetMethod = foundTicket.getClass().getMethod("set" + name, ArrayList.class);
ArrayList ticketValue = (ArrayList) ticketGetMethod.invoke(ticket);
ArrayList foundTicketValue = (ArrayList) foundTicketGetMethod.invoke(foundTicket);
if (ticketValue != null) {
foundTicketSetMethod.invoke(foundTicket, ticketValue);
} else if (foundTicketValue != null) {
foundTicketSetMethod.invoke(foundTicket, foundTicketValue);
} else {
foundTicketSetMethod.invoke(foundTicket, "");
}
}
}
ticketRepository.save(foundTicket);
} else {
return new JsonResult<>(null, "1", "can not find ticket");
}
return new JsonResult<>(foundTicket, "update ticket successfully");
}
问了一个senior这个问题之后,他给我说了一个解决方案,茅塞顿开啊。 之前一直不懂DTO层是干吗用的,不明白已经有了model层为什么还需要DTO这层,终于遇到这个问题的时候明白了。。 简单来说,model层是直接来操作database的层,而DTO层是针对与前端页面的数据封装,也就是前端会直接拿DTO的传输回去的数据进行交互渲染,用这种方式的话,model层和view层就解耦了,我们可以不直接引用model层。
举个例子,当我们Ticket的Model层含有title,description和createDate,而我们的前端只需要前两个值,不需要createDate,我们就可以在DTO层中将createDate去掉,不包含这个信息,然后将DTO层返回给前端。
另一个好处就是本文的问题了,当我们直接引用Model层的时候,如果其中的一个field含有默认值,而前端没有给出这个field的定义的话,后端在更新的时候会自动将这个field使用默认值给覆盖掉。如果我们加入了DTO层,就可以使用DTO层来进行判断,如果前端没有传入这个field,就保持为database中的值,如果传入就进行更新。