JPA:仅更新特定字段

And*_*rea 51 java spring jpa spring-data-jpa

是否有更新的方式只有一些领域使用该方法的实体对象save春数据JPA

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

@Entity
public class User {

  @Id
  private Long id;

  @NotNull
  private String login;

  @Id
  private String name;

  // getter / setter
  // ...
}
Run Code Online (Sandbox Code Playgroud)

凭借其CRUD回购:

public interface UserRepository extends CrudRepository<User, Long> { }
Run Code Online (Sandbox Code Playgroud)

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

   // ...
}
Run Code Online (Sandbox Code Playgroud)

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

有没有办法说出" 保存对象时跳过所有空值 "之类的内容?

小智 36

我有同样的问题,正如M. Deinum指出的那样,答案是否定的,你不能使用保存.主要问题是Spring Data不知道如何处理null.是否设置了空值或是否设置了因为需要删除它?

从你的问题判断,我认为你也有同样的想法,这就是保存将允许我避免手动设置所有更改的值.

那么有可能避免所有的manuel映射吗?好吧,如果你选择遵守约定,nulls总是意味着'not set'并且你有原始的模型id,那么是的.您可以使用Springs BeanUtils避免任何映射.

您可以执行以下操作:

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

现在,Spring的BeanUtils实际上不支持不复制空值,因此它将覆盖现有模型对象上未使用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);
}
Run Code Online (Sandbox Code Playgroud)

  • 我相信它应该是`userRepository.save(existing);`not` #save(user);` (4认同)
  • 如果使用版本,此解决方案容易出现并发问题或 OptimisticLockException。请参阅@arjun-nayak 答案以获得正确的解决方案 (2认同)

Arj*_*yak 12

使用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();
Run Code Online (Sandbox Code Playgroud)

  • 为什么这不是公认的答案?其他解决方案也可能出现争用情况:如果一个线程更新列A和C,而另一个更新B和D,则此解决方案可以正常工作,但其他解决方案可能导致仅A + C或B + D被更新为第二次更新将覆盖较早版本的效果。是的,您当然可以添加一个@Version字段并处理优化锁异常。但是只更新您想要的列就容易得多。 (2认同)
  • 谢谢您的回答。令我感到震惊的是,有90%的开发人员(基于答案)不关心并发问题。 (2认同)

小智 6

你可以写类似的东西

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

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

@DynamicUpdate
Run Code Online (Sandbox Code Playgroud)

代码示例:

@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"})
Run Code Online (Sandbox Code Playgroud)


Pir*_*ino 1

问题不在于 spring data jpa 相关,而是与您正在使用的 jpa 实现库相关。如果处于休眠状态,您可以查看:

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