Spring Boot使用MongoDB Repository进行update

265 阅读2分钟

写公司的项目的时候遇到了一个问题,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中的值,如果传入就进行更新。