使用@patch方法进行的Sprest部分更新

hlswsv35  于 2022-10-04  发布在  Spring
关注(0)|答案(5)|浏览(174)

我正在尝试基于以下内容实现Manager实体的部分更新:

实体

public class Manager {
    private int id;
    private String firstname;
    private String lastname;
    private String username;
    private String password;

    // getters and setters omitted
}

控制器中的SaveManager方法

@RequestMapping(value = "/save", method = RequestMethod.PATCH)
public @ResponseBody void saveManager(@RequestBody Manager manager){
    managerService.saveManager(manager);
}

在DAO实现中保存对象管理器。

@Override
public void saveManager(Manager manager) {  
    sessionFactory.getCurrentSession().saveOrUpdate(manager);
}

当我保存对象时,用户名和密码已正确更改,但其他值为空。

因此,我需要做的是更新用户名和密码,并保留所有剩余数据。

7cwmlq89

7cwmlq891#

如果您真的在使用补丁,那么您应该使用RequestMethod.PATCH,而不是RequestMethod.POST。

补丁Map应包含可用于检索要打补丁的管理器对象的ID。此外,它应该只包括您想要更改的字段。在您的示例中,您发送的是整个实体,因此您无法识别实际正在更改的字段(Empty是指不使用该字段,还是实际上将其值更改为Empty)。

也许这样的实现就是您想要的?

@RequestMapping(value = "/manager/{id}", method = RequestMethod.PATCH)
public @ResponseBody void saveManager(@PathVariable Long id, @RequestBody Map<Object, Object> fields) {
    Manager manager = someServiceToLoadManager(id);
    // Map key is field name, v is value
    fields.forEach((k, v) -> {
       // use reflection to get field k on manager and set it to value v
        Field field = ReflectionUtils.findField(Manager.class, k);
        field.setAccessible(true);
        ReflectionUtils.setField(field, manager, v);
    });
    managerService.saveManager(manager);
}

更新

我想提供这篇文章的更新,因为现在有一个简化修补过程的项目。

神器是

<dependency>
  <groupId>com.github.java-json-tools</groupId>
  <artifactId>json-patch</artifactId>
  <version>1.13</version>
</dependency>

在OP中修补Manager对象的实现如下所示:

控制器

@Operation(summary = "Patch a Manager")
@PatchMapping("/{managerId}")
public Task patchManager(@PathVariable Long managerId, @RequestBody JsonPatch jsonPatch)
    throws JsonPatchException, JsonProcessingException {
    return managerService.patch(managerId, jsonPatch);
}

服务

public Manager patch(Long managerId, JsonPatch jsonPatch) throws JsonPatchException, JsonProcessingException {
    Manager manager = managerRepository.findById(managerId).orElseThrow(EntityNotFoundException::new);
    JsonNode patched = jsonPatch.apply(objectMapper.convertValue(manager, JsonNode.class));

    return managerRepository.save(objectMapper.treeToValue(patched, Manager.class));
}

补丁请求遵循RFC 6092中的规范,因此这是一个真正的补丁实现。详情可在here中找到

inkz8wg9

inkz8wg92#

有了这个,您就可以修补您的更改

1. Autowire `ObjectMapper` in controller;

2. @PatchMapping("/manager/{id}")
    ResponseEntity<?> saveManager(@RequestBody Map<String, String> manager) {
        Manager toBePatchedManager = objectMapper.convertValue(manager, Manager.class);
        managerService.patch(toBePatchedManager);
    }

3. Create new method `patch` in `ManagerService`

4. Autowire `NullAwareBeanUtilsBean` in `ManagerService`

5. public void patch(Manager toBePatched) {
        Optional<Manager> optionalManager = managerRepository.findOne(toBePatched.getId());
        if (optionalManager.isPresent()) {
            Manager fromDb = optionalManager.get();
            // bean utils will copy non null values from toBePatched to fromDb manager.
            beanUtils.copyProperties(fromDb, toBePatched);
            updateManager(fromDb);
        }
    }

您必须扩展BeanUtilsBean以实现非空值行为的复制。

public class NullAwareBeanUtilsBean extends BeanUtilsBean {

    @Override
    public void copyProperty(Object dest, String name, Object value)
            throws IllegalAccessException, InvocationTargetException {
        if (value == null)
            return;
        super.copyProperty(dest, name, value);
    }
}

最后,将NullAwareBeanUtilsBean标记为@Component

NullAwareBeanUtilsBean注册为Bean

@Bean
public NullAwareBeanUtilsBean nullAwareBeanUtilsBean() {
    return new NullAwareBeanUtilsBean();
}
ej83mcc0

ej83mcc03#

首先,您需要知道您是在执行插入操作还是更新操作。INSERT很简单。在更新时,使用get()检索实体。然后更新任何字段。在事务结束时,Hibernate将刷新更改并提交。

fhg3lkii

fhg3lkii4#

您可以编写仅更新特定字段的自定义更新查询:

@Override
public void saveManager(Manager manager) {  
    Query query = sessionFactory.getCurrentSession().createQuery("update Manager set username = :username, password = :password where id = :id");
    query.setParameter("username", manager.getUsername());
    query.setParameter("password", manager.getPassword());
    query.setParameter("id", manager.getId());
    query.executeUpdate();
}
igetnqfo

igetnqfo5#

ObjectMapper.updateValue提供了将实体与从dto到的值部分Map所需的全部内容。作为补充,您可以在此处使用两种方法之一:Map<String, Object> fieldsString json,因此您的服务方法可能如下所示:

@Autowired
private ObjectMapper objectMapper;

@Override
@Transactional
public Foo save(long id, Map<String, Object> fields) throws JsonMappingException {
    Foo foo = fooRepository.findById(id)
    .orElseThrow(() -> new ResourceNotFoundException("Foo not found for this id: " + id));

    return objectMapper.updateValue(foo , fields);
}

作为Lane Maxwell回答的第二个解决方案和补充,您可以使用Reflection仅Map已发送的值Map中存在的属性,因此您的服务方法可能如下所示:

@Override
@Transactional
public Foo save(long id, Map<String, Object> fields) {

    Foo foo = fooRepository.findById(id)
    .orElseThrow(() -> new ResourceNotFoundException("Foo not found for this id: " + id));

    fields.keySet()
            .forEach(k -> {
                Method method = ReflectionUtils.findMethod(LocationProduct.class, "set" + StringUtils.capitalize(k));
                if (method != null) {
                    ReflectionUtils.invokeMethod(method, foo, fields.get(k));
                }
            });
    return foo;
}

第二个解决方案允许您在Map过程中插入一些额外的业务逻辑,可能是转换或计算等。

此外,与按名称查找反射字段Field field = ReflectionUtils.findField(Foo.class, k);并使其可访问不同,查找属性的setter实际上调用setter方法,该方法可能包含要执行的额外逻辑,并防止将值设置为私有属性。

相关问题