【问题标题】:How to update the @Version field when executing a bulk update through Spring Data JPA通过 Spring Data JPA 执行批量更新时如何更新 @Version 字段
【发布时间】:2014-03-01 19:33:31
【问题描述】:

我正在使用 Spring 数据存储库,但遇到一个问题,我找不到答案。我的存储库查询是:

@Modifying
@Query("UPDATE User u SET u.firstName = 'blabla' WHERE u.loginName = 'admin'")
public int test();

而实体User 有一个javax.persistence.Version 注释字段:

 @Version
 private Long version;

当我执行查询时,版本字段不会更新,但如果我不更新查询:

User user = this.userRepository.findUserById(1L);
user.setFirstName("blabla");
this.userRepository.save(user);

版本字段已更新。为什么?

【问题讨论】:

    标签: jpa version jpa-2.0 spring-data spring-data-jpa


    【解决方案1】:

    JPA 2.0 规范的第 4.10 节明确指出:

    批量更新直接映射到数据库更新操作,绕过乐观锁定检查。如果需要,便携式应用程序必须手动更新版本列的值,和/或手动验证版本列的值。

    一般来说,批量更新和删除几乎绕过了持久性提供程序应用的许多功能,您在简单地保存实体时可能会习惯这些功能。除了乐观锁定之外,这还包括持久性提供程序管理的持久性操作级联。

    【讨论】:

      【解决方案2】:

      为了使用乐观锁,首先需要一个version 列。

      现在,如果批量更新语句不增加 version 列,则可能会发生丢失更新异常:

      现在,为了避免丢失更新,您只需增加 version 列。

      您可以使用任何类型的查询来做到这一点。

      JPQL

      int updateCount = entityManager
      .createQuery(
          "update Post " +
          "set " +
          "   status = :newStatus," +
          "   version = version + 1 " +
          "where " +
          "   status = :oldStatus and " +
          "   lower(title) like :pattern")
      .setParameter("oldStatus", PostStatus.PENDING)
      .setParameter("newStatus", PostStatus.SPAM)
      .setParameter("pattern", "%spam%")
      .executeUpdate();
      

      标准 API

      CriteriaBuilder builder = entityManager
      .getCriteriaBuilder();
       
      CriteriaUpdate<Post> update = builder
      .createCriteriaUpdate(Post.class);
       
      Root<Post> root = update.from(Post.class);
       
      Expression<Boolean> wherePredicate = builder
      .and(
          builder.equal(
              root.get("status"),
              PostStatus.PENDING
          ),
          builder.like(
              builder.lower(root.get("title")),
              "%spam%"
          )
      );
       
      Path<Short> versionPath = root.get("version");
      Expression<Short> incrementVersion = builder
      .sum((short) 1, versionPath);
       
      update
      .set(root.get("status"), PostStatus.SPAM)
      .set(versionPath, incrementVersion)
      .where(wherePredicate);
       
      int updateCount = entityManager
      .createQuery(update)
      .executeUpdate();
      

      原生 SQL

      UPDATE post
      SET
        status = 2,
        version = version + 1
      WHERE
        status = 0 AND 
        lower(title) LIKE '%spam%'
      

      一旦您增加了version 列,将在加载由批量语句更新的实体的先前版本的并发事务中防止丢失更新:

      【讨论】:

        猜你喜欢
        • 2017-10-07
        • 2016-07-21
        • 1970-01-01
        • 2021-08-13
        • 2012-12-23
        • 2015-05-25
        • 2023-03-29
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多