2015-12-11 45 views
0

我有這兩個簡單的實體:如何部分更新實體及其在了Spring Web +關係JPA

@Entity 
public class User { 
    @Id 
    @GeneratedValue 
    private Long Id; 

    private String username; 

    private String firstName; 

    @ManyToOne 
    private Role role;   

    //Getters and setters... 
} 

@Entity 
public class Role { 
    @Id 
    @GeneratedValue 
    private Long Id; 

    private String name; 

    @OneToMany(mappedBy="role") 
    Set<User> users = new HashSet<>(); 

    //Getters and setters... 
} 

在我的控制,我需要使用下面的HTTP請求

curl -X PATCH \ 
    -H "Content-Type: application/json" \ 
    -d \ 
    '{ 
     "username": null, 
     "role": 1 
    }' http://localhost:8080/users/1 
更新用戶實體

正如你可以看到我想要的一個字段可以設置爲null,即從請求缺少某個字段不應更新一個關係式C可以使用相關型號ID進行設置。

這是我嘗試在控制器

@RestController 
@RequestMapping("/users") 
public class UserRestController { 

    private final UserRepository userRepository; 
    private final RoleRepository roleRepository; 

    @Autowired 
    UserRestController (UserRepository userRepository, RoleRepository roleRepository) { 
     this.userRepository = userRepository; 
     this.roleRepository = roleRepository; 
    } 

    @RequestMapping(value = "/{userId}", method = {RequestMethod.PATCH, RequestMethod.PUT}) 
    ResponseEntity<?> updateUser(@PathVariable Long userId, HttpServletRequest request) { 

     User userToUpdate = userRepository.findOne(userId); 

     ObjectMapper mapper = new ObjectMapper(); 
     User updatedUser = mapper.readerForUpdating(userToUpdate).readValue(request.getReader()); 
     User user = userRepository.save(updatedUser); 

     return new ResponseEntity<>(user, new HttpHeaders(), HttpStatus.CREATED); 
    } 
} 

我設法實現的唯一的事情就是部分更新和空更新領域實現這一點。但是,這樣,我無法僅通過ID更新關係。 請問有人可以幫忙嗎?

UPDATE

由於@Naros更新後,我重寫了整個控制器來處理角色ID更新

@RestController 
@RequestMapping("/users") 
public class UserRestController { 

    @Autowired 
    private UserRepository userRepository; 

    @Autowired 
    private RoleRepository roleRepository; 

    @Autowired 
    private UserService userService; 

    @RequestMapping(value = "/{userId}", method = {RequestMethod.PATCH, RequestMethod.PUT}) 
    ResponseEntity<?> updateUser(@PathVariable Long userId, @RequestBody ObjectNode requestJsonNode) throws IOException { 

     User userToUpdate = userRepository.findOne(userId); 

     RestMerger restMerger = new RestMerger(); 
     restMerger.getAssociations().put("role", userService); 

     User user = (User) restMerger.merge(userToUpdate, requestJsonNode); 
     userRepository.save(user); 

     return new ResponseEntity<>(user, new HttpHeaders(), HttpStatus.CREATED); 
    } 

} 

我產生UserService類來處理ID實體合併功能。

@Service 
public class UserService implements AssociationResolver { 
    public static final String ROLE_FIELD = "role"; 

    @Autowired 
    private RoleRepository roleRepository;  

    @Override 
    public void resolveAssociation(String name, JsonNode objectNode, ObjectNode mainNode) throws JsonProcessingException { 
     if(name.equals(ROLE_FIELD)) 
      resolveRoleAssociation(name, objectNode.get(name).asLong(), mainNode); 
    } 

    private void resolveRoleAssociation(String name, Long id, ObjectNode mainNode) { 
     Role role = roleRepository.findOne(id); 
     ObjectMapper mapper = new ObjectMapper(); 
     mainNode.replace(name, mapper.convertValue(role, JsonNode.class)); 
    } 
} 

這種服務可以包括完整的控制器UpdateUser兩個功能和字段的驗證。 RestMerger類像ObjectMapper一樣更新實體。

public class RestMerger { 
    private HashMap<String, AssociationResolver> associations = new HashMap<>(); 

    public Object merge(Object mainObject, ObjectNode updateNode) throws JsonProcessingException { 
     ObjectMapper mapper = new ObjectMapper(); 
     ObjectNode mainNode = mapper.convertValue(mainObject, ObjectNode.class); 

     Iterator<String> fieldNames = updateNode.fieldNames(); 
     while (fieldNames.hasNext()) { 

      String fieldName = fieldNames.next(); 
      JsonNode jsonNode = mainNode.get(fieldName); 

      if(associations.keySet().contains(fieldName)) { 
       associations.get(fieldName).resolveAssociation(fieldName, updateNode, mainNode); 
      } else if (jsonNode.isObject()) 
       merge(jsonNode, updateNode); 
      else { 
       JsonNode value = updateNode.get(fieldName); 
       mainNode.replace(fieldName, value); 
      } 
     } 

     return mapper.treeToValue(mainNode, mainObject.getClass()); 
    } 

    public HashMap<String, AssociationResolver> getAssociations() { 
     return associations; 
    } 
} 

最後的AssociationResolver inteface使得通用RestMerger類的自定義實體服務

public interface AssociationResolver { 
    void resolveAssociation(String name, JsonNode value, ObjectNode mainNode) throws JsonProcessingException; 
} 

回答

0

互動。如果你願意不使用ObjectMapper,你可以重新編碼與聲明的變化控制器。我在ObjectMapper上看到的好處是,您可以精確控制RESTful界面可以修改哪些字段,以及哪些字段(如果提供的話)可以默默忽略或發出警告/錯誤。

:下面的代碼片段假定稱爲UserAttributes值對象,基本上公開控制器將接受並運行後,而不是根據你原來的代碼輸入富有活力的有效值。

@RestController 
@RequestMapping("/users") 
public class UserRestController { 

    /* other cruff */ 

    @RequestMapping(
    value = "/{userId}", 
    method = { 
     RequestMethod.PATCH, 
     RequestMethod.PUT 
    }) 
    public ResponseEntity<?> updateUser(
    @PathVariable Long userId, 
    @RequestBody UserAttributes userAttributes) { 

    User userToUpdate = userRepository.findOne(userId); 
    if(userToUpdate == null) { 
     /* handle cannot find user by userid */ 
    } 

    // update the username from the user object 
    userToUpdate.setUsername(userAttributes.getUserName()); 

    if(userAttributes.getRole() != null) { 
     Role roleToUpdate = roleRepository.findOne(userAttributes.getRole()); 
     if(roleToUpdate == null) { 
     /* handle cannot find the role */ 
     } 
     roleToUpdate.getUsers().add(userToUpdate); 
     userToUpdate.setRole(roleToUpdate); 
     roleRepository.save(roleToUpdate); 
    } 
    else { 
     userToUpdate.setRole(null); 
    } 

    userToUpdate = userRepository.save(userToUpdate); 

    return new ResponseEntity<>(
     userToUpdate, 
     new HttpHeaders(), 
     HttpStatus.CREATED); 
    } 
} 

UPDATE

特別JsonNode合併功能,可以幫助:

public static JsonNode merge(JsonNode mainNode, JsonNode updateNode) { 

    Iterator<String> fieldNames = updateNode.fieldNames(); 
    while (fieldNames.hasNext()) { 

     String fieldName = fieldNames.next(); 
     JsonNode jsonNode = mainNode.get(fieldName); 
     // if field exists and is an embedded object 
     if (jsonNode != null && jsonNode.isObject()) { 
      merge(jsonNode, updateNode.get(fieldName)); 
     } 
     else { 
      if (mainNode instanceof ObjectNode) { 
       // Overwrite field 
       JsonNode value = updateNode.get(fieldName); 
       ((ObjectNode) mainNode).put(fieldName, value); 
      } 
     } 

    } 

    return mainNode; 
} 
+0

我不想更新未設置字段,如果它沒有被要求發送,但我想更新只要該字段在請求中設置,即使它設置爲空,也使用ObjectMapper。在你的代碼片段中,我似乎明白'UserAttribute' _role_字段被設置爲空,我的請求字段爲空(正確),我的請求字段不存在(不正確)。糾正我,如果我錯了。 –

+0

@RuggeroRusso看來如果​​你想使用'ObjectMapper',你可能需要使用'JsonNode'手動執行合併。否則,使用我的原始代碼,您需要發送角色:[special null me value]來觸發刪除操作,空值跳過,設置值。 – Naros