如何在Spring Web + JPA

时间:2015-12-11 17:04:25

标签: spring hibernate jpa relationship patch

我有两个简单的实体:

@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

正如您所见,我希望字段可以设置为空请求中不存在的字段 关系可以使用相关的型号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更新关系。 请帮忙吗?

更新

感谢@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-entity合并功能。

@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 接口使通用 RestMerger 类与自定义实体服务进行交互

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

1 个答案:

答案 0 :(得分: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);
  }
}

<强>更新

可能有用的特殊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;
}