簡體   English   中英

使用@PATCH 方法進行 Spring REST 部分更新

[英]Spring REST partial update with @PATCH method

我正在嘗試基於以下內容實現 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 impl 中保存對象管理器。

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

當我保存對象時,用戶名和密碼已正確更改,但其他值為空。

所以我需要做的是更新用戶名和密碼並保留所有剩余數據。

如果您真的在使用 PATCH,那么您應該使用 RequestMethod.PATCH,而不是 RequestMethod.POST。

您的補丁映射應包含可用於檢索要修補的 Manager 對象的 ID。 此外,它應該只包含您想要更改的字段。 在您的示例中,您正在發送整個實體,因此您無法辨別實際發生變化的字段(空是否意味着不理會該字段或實際將其值更改為空)。

也許這樣的實現就是你所追求的?

@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);
}

有了這個,您可以修補您的更改

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();
}

首先,您需要知道您是在執行插入操作還是更新操作。 插入很簡單。 更新時,使用 get() 檢索實體。 然后更新任何字段。 在事務結束時,Hibernate 將刷新更改並提交。

您可以編寫僅更新特定字段的自定義更新查詢:

@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();
}

ObjectMapper.updateValue提供了使用 dto 中的值部分映射實體所需的一切。 另外,您可以在此處使用以下兩個Map<String, Object> fieldsMap<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 中存在的屬性,因此您的服務方法可能如下所示:

@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;
}

第二種解決方案允許您在映射過程中插入一些額外的業務邏輯,可能是轉換或計算等。

也不同於查找反射字段Field field = ReflectionUtils.findField(Foo.class, k); 通過名稱而不是使其可訪問,查找屬性的 setter 實際上調用 setter 方法,該方法可能包含要執行的附加邏輯並防止將值設置為私有屬性。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM