繁体   English   中英

JPA:仅更新特定字段

[英]JPA: update only specific fields

有没有办法使用从Spring Data JPA save的方法更新实体对象的某些字段

例如,我有一个这样的 JPA 实体:

@Entity
public class User {

  @Id
  private Long id;

  @NotNull
  private String login;

  @Id
  private String name;

  // getter / setter
  // ...
}

使用其 CRUD 存储库:

public interface UserRepository extends CrudRepository<User, Long> { }

Spring MVC 中,我有一个控制器可以获取一个User对象来更新它:

@RequestMapping(value = "/rest/user", method = RequestMethod.PUT, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public ResponseEntity<?> updateUser(@RequestBody User user) {

   // Assuming that user have its id and it is already stored in the database,
   // and user.login is null since I don't want to change it,
   // while user.name have the new value

   // I would update only its name while the login value should keep the value 
   // in the database
   userRepository.save(user);

   // ...
}

我知道我可以使用findOne加载用户,然后更改其名称并使用save更新它......但是如果我有 100 个字段并且我想更新其中的 50 个,那么更改每个值可能会非常烦人..

有没有办法告诉“保存对象时跳过所有空值”之类的东西?

我有同样的问题,正如 Deinum 先生指出的那样,答案是否定的,您不能使用 save。 主要问题是 Spring Data 不知道如何处理空值。 是没有设置空值还是因为需要删除而设置?

现在从你的问题来看,我假设你也有和我一样的想法,那就是 save 可以让我避免手动设置所有更改的值。

那么是否有可能避免所有的手动映射呢? 好吧,如果您选择遵守空值始终表示“未设置”的约定,并且您拥有原始模型 ID,那么是的。 您可以使用 Springs BeanUtils 自己避免任何映射。

您可以执行以下操作:

  1. 读取现有对象
  2. 使用 BeanUtils 复制值
  3. 保存对象

现在,Spring 的 BeanUtils 实际不支持不复制 null 值,因此它将覆盖现有模型对象上未设置为 null 的任何值。 幸运的是,这里有一个解决方案:

如何使用 springframework BeanUtils copyProperties 忽略空值?

所以把它们放在一起你最终会得到这样的东西

@RequestMapping(value = "/rest/user", method = RequestMethod.PUT, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public ResponseEntity<?> updateUser(@RequestBody User user) {

   User existing = userRepository.read(user.getId());
   copyNonNullProperties(user, existing);
   userRepository.save(existing);

   // ...
}

public static void copyNonNullProperties(Object src, Object target) {
    BeanUtils.copyProperties(src, target, getNullPropertyNames(src));
}

public static String[] getNullPropertyNames (Object source) {
    final BeanWrapper src = new BeanWrapperImpl(source);
    java.beans.PropertyDescriptor[] pds = src.getPropertyDescriptors();

    Set<String> emptyNames = new HashSet<String>();
    for(java.beans.PropertyDescriptor pd : pds) {
        Object srcValue = src.getPropertyValue(pd.getName());
        if (srcValue == null) emptyNames.add(pd.getName());
    }
    String[] result = new String[emptyNames.size()];
    return emptyNames.toArray(result);
}

使用 JPA 你可以这样做。

CriteriaBuilder builder = session.getCriteriaBuilder();
CriteriaUpdate<User> criteria = builder.createCriteriaUpdate(User.class);
Root<User> root = criteria.from(User.class);
criteria.set(root.get("lastSeen"), date);
criteria.where(builder.equal(root.get("id"), user.getId()));
session.createQuery(criteria).executeUpdate();

如果您将请求作为 JSON 字符串读取,则可以使用 Jackson API 来完成。 这是下面的代码。 代码比较现有的 POJO 元素并创建具有更新字段的新元素。 使用新的 POJO 进行持久化。

public class TestJacksonUpdate {

class Person implements Serializable {
    private static final long serialVersionUID = -7207591780123645266L;
    public String code = "1000";
    public String firstNm = "John";
    public String lastNm;
    public Integer age;
    public String comments = "Old Comments";

    @Override
    public String toString() {
        return "Person [code=" + code + ", firstNm=" + firstNm + ", lastNm=" + lastNm + ", age=" + age
                + ", comments=" + comments + "]";
    }
}

public static void main(String[] args) throws JsonProcessingException, IOException {
    TestJacksonUpdate o = new TestJacksonUpdate();

    String input = "{\"code\":\"1000\",\"lastNm\":\"Smith\",\"comments\":\"Jackson Update WOW\"}";
    Person persist = o.new Person();

    System.out.println("persist: " + persist);

    ObjectMapper mapper = new ObjectMapper();
    Person finalPerson = mapper.readerForUpdating(persist).readValue(input);

    System.out.println("Final: " + finalPerson);
}}

最终输出将是,注意只有 lastNm 和 Comments 反映了更改。

persist: Person [code=1000, firstNm=John, lastNm=null, age=null, comments=Old Comments]
Final: Person [code=1000, firstNm=John, lastNm=Smith, age=null, comments=Jackson Update WOW]

你可以写一些类似的东西

@Modifying
    @Query("update StudentXGroup iSxG set iSxG.deleteStatute = 1 where iSxG.groupId = ?1")
    Integer deleteStudnetsFromDeltedGroup(Integer groupId);

或者如果您只想更新已修改的字段,您可以使用注释

@DynamicUpdate

代码示例:

@Entity
@Table(name = "lesson", schema = "oma")
@Where(clause = "delete_statute = 0")
@DynamicUpdate
@SQLDelete(sql = "update oma.lesson set delete_statute = 1, "
        + "delete_date = CURRENT_TIMESTAMP, "
        + "delete_user = '@currentUser' "
        + "where lesson_id = ?")
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})

保存对象时跳过所有空值

正如其他人所指出的,JPA 中没有直接的解决方案。

但是开箱即用,您可以使用 MapStruct。

这意味着您使用正确的find()方法从数据库中获取要更新的对象,仅覆盖非空属性,然后保存该对象。

您可以使用 JPA,并在 Spring 中像这样使用 MapStruct 来仅更新数据库中对象的非空属性:

@Mapper(componentModel = "spring")
public interface HolidayDTOMapper {

    /**
     * Null values in the fields of the DTO will not be set as null in the target. They will be ignored instead.
     *
     * @return The target Holiday object
     */
    @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
    Holiday updateWithNullAsNoChange(HolidayDTO holidayDTO, @MappingTarget Holiday holiday);

}

有关详细信息请参阅MapStruct 文档

您可以HolidayDTOMapper使用其他 bean(@ @Autowired 、Lombok、...)一样注入HolidayDTOMapper

问题不是与 spring 数据 jpa 相关,而是与您正在使用的 jpa 实现库相关。 在休眠的情况下,您可以查看:

http://www.mkyong.com/hibernate/hibernate-dynamic-update-attribute-example/

对于long、int等类型; 您可以使用以下代码;

            if (srcValue == null|(src.getPropertyTypeDescriptor(pd.getName()).getType().equals(long.class) && srcValue.toString().equals("0")))
            emptyNames.add(pd.getName());

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM