Estamos tratando de implementar un especial de actualización parcial de la función en la Primavera de 3.2. Estamos utilizando la Primavera para el backend y tiene un simple Javascript frontend. No he sido capaz de encontrar una sencilla solución a nuestras necesidades, que es La función update() debería tomar en cualquier número de campo:los valores y actualizar el modelo de persistencia en consecuencia.

Tenemos en la edición en línea para todos nuestros campos, de modo que cuando el usuario edita un campo y confirma, una identificación y la modificación de campo se pasan al controlador como json. El controlador debe ser capaz de tomar cualquier número de campos desde el cliente (de 1 a n) y la actualización sólo aquellos campos.

por ejemplo, cuando un usuario con id==1 edita su nombre de visualización, los datos publicados en el servidor se parece a esto:

{"id":"1", "displayName":"jim"}

Actualmente, tenemos una solución incompleta en el UserController como se indica a continuación:

@RequestMapping(value = "/{id}", method = RequestMethod.POST )
public @ResponseBody ResponseEntity<User> update(@RequestBody User updateUser) {
    dbUser = userRepository.findOne(updateUser.getId());
    customObjectMerger(updateUser, dbUser);
    userRepository.saveAndFlush(updateUuser);
    ...
}

El código funciona, pero tiene algunos problemas: La @RequestBody crea un nuevo updateUser, rellena el id y la displayName. CustomObjectMerger combina este updateUser con la correspondiente dbUser de la base de datos, la actualización de los únicos campos que se incluyen en updateUser.

El problema es que la Primavera rellena algunos campos en updateUser con los valores predeterminados y otros generado automáticamente los valores de campo, que, tras la fusión, sobrescribe los datos válidos que tenemos en dbUser. Declarar explícitamente que debe ignorar estos campos no es una opción, como queremos que nuestros update a ser capaz de establecer estos campos así.

Estoy buscando alguna manera de tener la Primavera de combinar automáticamente SÓLO la información explícita, enviados a la update() función en la dbUser (sin restablecer predeterminado/auto valores de campo). ¿Hay alguna manera sencilla de hacerlo?

Actualización: ya he considerado la opción siguiente que hace casi lo que estoy pidiendo, pero no del todo. El problema es que se necesita la actualización de datos en @RequestParam y (AFAIK) no hacer cadenas JSON:

//load the existing user into the model for injecting into the update function
@ModelAttribute("user")
public User addUser(@RequestParam(required=false) Long id){
    if (id != null) return userRepository.findOne(id);
    return null;
}
....
//method declaration for using @MethodAttribute to pre-populate the template object
@RequestMapping(value = "/{id}", method = RequestMethod.POST )
public @ResponseBody ResponseEntity<User> update(@ModelAttribute("user") User updateUser){
....
}

He considerado la posibilidad de re-escribir mi customObjectMerger() a trabajar en forma más adecuada con JSON, contar y tener que tomar en consideración sólo los campos que vienen de HttpServletRequest. pero incluso el tener que usar un customObjectMerger() en primer lugar se siente hacky cuando la primavera se proporciona casi exactamente lo que estoy buscando, menos la falta de JSON funcionalidad. Si alguien sabe cómo conseguir la Primavera para ello, me gustaría ayudarme se lo agradeceria mucho!

  • ¿Alguna vez encontrar una mejor solución, en particular para la situación en la que hay objetos anidados? Tengo el mismo problema, y antes de leer este post, hizo algo similar a ti.. Si tienes tiempo por favor ver: stackoverflow.com/questions/16473727/…
InformationsquelleAutor SamEsla | 2013-02-27

8 Comentarios

  1. 23

    Sólo me he topado con este mismo problema. Mi solución actual se parece a esto. No he hecho muchas pruebas, pero tras la inspección inicial parece estar funcionando bastante bien.

    @Autowired ObjectMapper objectMapper;
    @Autowired UserRepository userRepository;
    
    @RequestMapping(value = "/{id}", method = RequestMethod.POST )
    public @ResponseBody ResponseEntity<User> update(@PathVariable Long id, HttpServletRequest request) throws IOException
    {
        User user = userRepository.findOne(id);
        User updatedUser = objectMapper.readerForUpdating(user).readValue(request.getReader());
        userRepository.saveAndFlush(updatedUser);
        return new ResponseEntity<>(updatedUser, HttpStatus.ACCEPTED);
    }

    La ObjectMapper es un bean de tipo org.codehaus.jackson.mapa.ObjectMapper.

    La esperanza de que esto ayude a alguien,

    Edición:

    Tienen problemas con los objetos secundarios. Si un niño objeto recibe una propiedad para actualizar parcialmente creará un nuevo objeto, la actualización de la propiedad, y la puso. Esto borra todas las otras propiedades de ese objeto. Voy a actualizar si encuentro una solución limpia.

    • Gracias Tyler, esto parece como una solución práctica. Todavía me pregunto si hay una fuente de función para permitir la continua/automática de la unión de JSON de enviar datos a un objeto persistente.
    • Gracias por la solución. Pensé que la única manera de resolver esta situación es la elaboración del manual. No he conocido jamás Jackson ObjectMapper antes de…
    • ¿Alguna vez encontrar una mejor solución, en particular para la situación en la que hay objetos anidados? Tengo el mismo problema, y antes de leer este post, hizo algo similar a la OP. Si tienes tiempo por favor ver: stackoverflow.com/questions/16473727/…
    • Echa un vistazo a este viejo error de Jackson: jira.codehaus.org/browse/JACKSON-679 Hay un comentario que muestra un código que puede hacer un profundo actualización. Por supuesto, la API ha cambiado desde 2011, pero hemos sido capaces de arreglarlo y hacerlo funcionar.
    • Gracias por señalarlo. De que el proyecto está hecho y se sacudió ahora. Pero los datos proporcionados, sin pruebas de que el fragmento de código, era exactamente lo que estaba buscando. Espero que pueda ayudar a otros a que lea este post buscando la solución del mismo. Si fueron capaces de arreglarlo para trabajar con la nueva api – le agradecería si usted iba a actualizar mi POST (stackoverflow.com/questions/16473727/…) con su solución. Si funciona voy a crédito su contribución como respuesta correcta! Gracias.
    • y tayler, usted puede manejar objeto anidado fusión mediante la configuración de la objectmapper con objectMapper.setDefaultMergeable(Boolean.TRUE) o si prefiere configurar la mezcla en el nivel de campo se puede establecer la JsonMerge anotación. Esto funcionará para los objetos y también para las matrices cuando se agregan nuevos elementos. sin embargo, si el cambio implica la eliminación de los elementos, la combinación no funciona correctamente.
    • sólo 6 años más tarde.. 🙂
    • lool, sí. pero todavía es muy pertinente la pregunta. sólo vine a través de dicho problema de la combinación de códigos, recientemente, por muy experimentados consultores. espero que la respuesta sea útil a alguien.
    • Gracias por compartir @abdel, apreciado.

  2. 2

    Estamos con @ModelAttribute para conseguir lo que quieres hacer.

    • Crear un método anotado [email protected] que carga un usuario basado en un pathvariable throguh un repositorio.

    • crear un método @Requestmapping con una param @modelattribute

    El punto aquí es que el @modelattribute método es el inicializador para el modelo. Luego de la primavera se funde la solicitud con este modelo ya que declararlo en el @requestmapping método.

    Esto le da a usted parcial de la funcionalidad de la actualización.

    Algunos , o incluso mucho? 😉 diría que esta es una mala práctica, de todos modos ya que el uso de nuestros DAOs directamente en el controlador y no hacer esta mezcla en una capa de servicio. Actualmente no tenía problemas porque de este abordaje.

    • Gracias Martin, pero ya he intentado esto. El problema con este método es que no parece tomar en sólo @RequestParam para la actualización de los campos. Queremos una forma de duplicar esta funcionalidad exacta que usted ha mencionado, pero trabajar con JSON como la de entrada. Estoy seguro de que la Primavera se ha construido en la funcionalidad de este lugar, pero no he corrido en ella todavía.
    • Esto debería funcionar similares. Es cierto que uso actualmente con datos post, pero voy a intentar hacerlo con un objeto json. En el final de la primavera, es el manejo de este muy similares. Nada (requestparam, json props ) que coincidan con el modelo arributes deberían fusionarse. Voy a intentar hacer un breve ejemplo de mañana ya que estoy en un ipad atm.
    • Más de 3 semanas de retraso de mi respuesta.. Pero por fin he tenido algo de tiempo para retirar un poco. Hoàng Largo es a la derecha. No hay modo más limpio de manejo de json requestbodys. Parece que nos falta un requestbody de fusión 🙂 Podría ser una primavera solicitud?
    • ¿Alguna vez has enfoque de la Primavera de la comunidad con esta solicitud? Tengo un problema similar ahora. Me gustaría ser capaz de hacer una petición post con JSON tipo de solicitud, y tiene un objeto de Comando actualiza con los valores que fueron alterados como resultado de la petición POST. Si está interesado o tiene tiempo, por favor, consulte stackoverflow.com/questions/16473727/…
    • OK, he abierto una JIRA por: jira.springsource.org/browse/SPR-10552
  3. 2

    Voy a construir una API que combinar objetos de la vista con entidades antes de que la llamada persiste o de fusión o actualización.

    Es una primera versión, pero creo que Es un comienzo.

    Sólo el uso de la anotación UIAttribute en su POJO campos, a continuación, utilizar:

    MergerProcessor.merge(pojoUi, pojoDb);

    Funciona de forma nativa con los Atributos y a la Recolección.

    git: https://github.com/nfrpaiva/ui-merge

  4. 2

    Siguiente enfoque podría ser utilizado.

    Para este escenario, el PARCHE método sería más apropiado, ya que la entidad estará parcialmente actualizada.

    En el método de controlador, tomar el cuerpo de solicitud como de la cadena.

    Convertir esa Cadena JSONObject. Luego iterar a través de las teclas y la actualización de la coincidencia de la variable con los datos entrantes.

    import org.json.JSONObject;
    
    @RequestMapping(value = "/{id}", method = RequestMethod.PATCH )
    public ResponseEntity<?> updateUserPartially(@RequestBody String rawJson, @PathVariable long id){
    
        dbUser = userRepository.findOne(id);
    
        JSONObject json = new JSONObject(rawJson);
    
        Iterator<String> it = json.keySet().iterator();
        while(it.hasNext()){
            String key = it.next();
            switch(key){
                case "displayName":
                    dbUser.setDisplayName(json.get(key));
                    break;
                case "....":
                    ....
            }
        }
        userRepository.save(dbUser);
        ...
    }

    Desventaja de este enfoque es, usted tiene que validar manualmente los valores entrantes.

    • Hola! Gracias por la respuesta. Sí, en retrospectiva PARCHE parece la solución natural, pero supongo que ninguno de nosotros considera que desde que fue implementado un par de meses atrás como nuevo Framework Spring funcionalidad en la versión 3.2.
  5. 0

    El problema principal reside en su siguiente código:

    @RequestMapping(value = "/{id}", method = RequestMethod.POST )
    public @ResponseBody ResponseEntity<User> update(@RequestBody User updateUser) {
        dbUser = userRepository.findOne(updateUser.getId());
        customObjectMerger(updateUser, dbUser);
        userRepository.saveAndFlush(updateUuser);
        ...
    }

    En las funciones anteriores, llamar a algunas de sus funciones privadas & clases (userRepository, customObjectMerger, …), pero no dan una explicación de cómo funciona o cómo esas funciones parecer. Así que sólo puedo adivinar:

    CustomObjectMerger combina este updateUser con la correspondiente
    dbUser de la base de datos, la actualización de los únicos campos que se incluyen en
    updateUser.

    Aquí no sabemos lo que pasó en CustomObjectMerger (esa es su función, y no lo muestran). Pero de lo que usted describe, puedo hacer una conjetura: copiar todas las propiedades de updateUser a su objeto en la base de datos. Esto es absolutamente un camino equivocado, desde cuando la Primavera mapa el objeto, se va a llenar todos los datos. Y sólo desea actualizar algunas de las propiedades específicas.

    Hay 2 opciones disponibles en su caso:

    1) el Envío de todas las propiedades (incluyendo el invariable propiedades) para el servidor. Esto puede costar un poco más de ancho de banda, pero aún así mantener su forma

    2) Usted debe establecer algunos valores especiales como el valor predeterminado para el objeto de Usuario (por ejemplo, id = -1, edad = -1…). A continuación, en customObjectMerger que acaba de establecer el valor que no es -1.

    Si usted siente que los 2 anteriores soluciones no están satisfechos, considere la posibilidad de analizar la petición json a sí mismo, y no se molestan con la Primavera objeto mecanismo de mapeo. A veces sólo se confunden mucho.

    • Hola Hoang, gracias por tu respuesta. @RequestBody vueltas mi carga en una nueva instancia de la User objeto. Las vísceras de los métodos privados no son importantes a la pregunta en cuestión. Por favor, mirar a Martin Fey la respuesta y mi actualización para ver cómo @ModelAttribute trabaja para servir a la misma funcionalidad que estoy buscando, pero funciona con RequestParams que viene en la solicitud. Usando este método, la Primavera se enlaza automáticamente la nueva información de la RequestParams a un objeto existente que obtiene de la base de datos. He estado rastreando la Primavera Docs para una manera de hacer esto con un JSON de carga.
    • Entiendo Fey método y su pregunta. En mi opinión, Fey es inteligente, pero no es una buena práctica. Yo de mi mismo la experiencia de algunos problemas cuando se trabaja con ModelAttribute antes, sobre todo, como él mismo comentario, que es trabajar con objetos DAO en el controlador.
    • Oh, no entendí tu pregunta. Escribí RequestBody pero de alguna manera creo que te refieres ResponseBody. Lo mal que mis ojos son @[email protected] he actualizado la respuesta a quitar esa parte pertinente.
    • Gracias Hoang, sí, el @RequestBody utiliza un ObjectMapper para enlazar los datos de entrada de la solicitud a una nueva instancia del objeto especificado.
  6. 0

    He personalizado y solución sucia emplea java.lang.reflejar paquete. Mi solución ha funcionado bien durante 3 años sin ningún problema.

    Mi método recibe 2 argumentos, objectFromRequest y objectFromDatabase ambos tienen el tipo de Objeto.

    El código simplemente hace:

    if(objectFromRequest.getMyValue() == null){
       objectFromDatabase.setMyValue(objectFromDatabase.getMyValue); //change nothing
    } else {
       objectFromDatabase.setMyValue(objectFromRequest.getMyValue); //set the new value
    }

    Un «valor» null » en un campo de solicitar significa «no cambies!».

    -1 valor para una columna de referencia que tienen nombre termina con «Id»: «Establece a null».

    También puede agregar muchas modificaciones personalizadas para sus diferentes escenarios.

    public static void partialUpdateFields(Object objectFromRequest, Object objectFromDatabase) {
    try {
    Method[] methods = objectFromRequest.getClass().getDeclaredMethods();
    for (Method method : methods) {
    Object newValue = null;
    Object oldValue = null;
    Method setter = null;
    Class valueClass = null;
    String methodName = method.getName();
    if (methodName.startsWith("get") || methodName.startsWith("is")) {
    newValue = method.invoke(objectFromRequest, null);
    oldValue = method.invoke(objectFromDatabase, null);
    if (newValue != null) {
    valueClass = newValue.getClass();
    } else if (oldValue != null) {
    valueClass = oldValue.getClass();
    } else {
    continue;
    }
    if (valueClass == Timestamp.class) {
    valueClass = Date.class;
    }
    if (methodName.startsWith("get")) {
    setter = objectFromRequest.getClass().getDeclaredMethod(methodName.replace("get", "set"),
    valueClass);
    } else {
    setter = objectFromRequest.getClass().getDeclaredMethod(methodName.replace("is", "set"),
    valueClass);
    }
    if (newValue == null) {
    newValue = oldValue;
    }
    if (methodName.endsWith("Id")
    && (valueClass == Number.class || valueClass == Integer.class || valueClass == Long.class)
    && newValue.equals(-1)) {
    setter.invoke(objectFromDatabase, new Object[] { null });
    } else if (methodName.endsWith("Date") && valueClass == Date.class
    && ((Date) newValue).getTime() == 0l) {
    setter.invoke(objectFromDatabase, new Object[] { null });
    } 
    else {
    setter.invoke(objectFromDatabase, newValue);
    }
    }
    }
    } catch (Exception e) {
    e.printStackTrace();
    }
    }

    En mi clase DAO, simcardToUpdate viene de la petición http:

    simcardUpdated = (Simcard) session.get(Simcard.class, simcardToUpdate.getId());
    MyUtil.partialUpdateFields(simcardToUpdate, simcardUpdated);
    updatedEntities = Integer.parseInt(session.save(simcardUpdated).toString());
  7. 0

    He hecho esto con un java Mapa y alguna reflexión magia:

    public static Entidade setFieldsByMap(Map<String, Object> dados, Entidade entidade) {
    dados.entrySet().stream().
    filter(e -> e.getValue() != null).
    forEach(e -> {
    try {
    Method setter = entidade.getClass().
    getMethod("set"+ Strings.capitalize(e.getKey()),
    Class.forName(e.getValue().getClass().getTypeName()));
    setter.invoke(entidade, e.getValue());
    } catch (Exception ex) { //a lot of exceptions
    throw new WebServiceRuntimeException("ws.reflection.error", ex);
    }
    });
    return entidade;
    }

    Y el punto de entrada:

        @Transactional
    @PatchMapping("/{id}")
    public ResponseEntity<EntityOutput> partialUpdate(@PathVariable String entity,
    @PathVariable Long id, @RequestBody Map<String, Object> data) {
    //...
    return new ResponseEntity<>(obj, HttpStatus.OK);
    }

Dejar respuesta

Please enter your comment!
Please enter your name here