【问题标题】:Insert/update entitiy from two different threads at the same time同时从两个不同的线程插入/更新实体
【发布时间】:2021-09-29 15:11:16
【问题描述】:

如果它已经存在,我有以下方法可以将玩家添加到数据库或更新它:

@Transactional(isolation = Isolation.SERIALIZABLE)
public Player addOrUpdatePlayer(String playerId, String playerName) {
    Optional<Player> playerOptional = playerRepository.findById(playerId);
    if (playerOptional.isPresent()) {
        Player player = playerOptional.get();
        player.setName(playerName);
        return playerRepository.save(player);
    } else {
        Player newPlayer = Player.builder()
            .id(playerId)
            .name(playerName)
            .build();
        return playerRepository.save(newPlayer);
    }
}

这个方法有时被两个不同的线程调用,因此几乎同时执行了两次。有时我会遇到以下异常:

org.h2.jdbc.JdbcSQLIntegrityConstraintViolationException:唯一索引 或主键违规:“PUBLIC.PRIMARY_KEY_B42 ON PUBLIC.PLAYER(ID) 值 3";

这对我来说很有意义,因为这两种方法都会在开始时检查玩家是否在数据库中,而这两种情况都不是。然后两者都尝试插入,较慢的插入约束。

我想在这个方法执行的过程中锁定播放器表,所以如果多次调用,就得互相等待。

我的播放器存储库如下所示:

public interface PlayerRepository extends JpaRepository<Player, String> {

    @Lock(LockModeType.PESSIMISTIC_WRITE)
    Optional<Player> findById(String var1);

    @Lock(LockModeType.PESSIMISTIC_WRITE)
    <S extends Player> S save(S var1);
}

我还尝试增加事务的隔离级别:

@Transactional(isolation = Isolation.SERIALIZABLE)

但是,我仍然遇到此异常,因此显然插入仍在同时运行。我究竟做错了什么?还是有不同的方法来实现我在这里尝试的目标?

经过更多研究,我想我明白为什么 @Lock 和 @Transactional(isolation = Isolation.SERIALIZABLE) 都没有做任何事情。它们基本上通过在事务期间锁定数据库表中的记录来工作,但由于在我的情况下没有要锁定的记录,所以它什么也不做。

所以,我想我必须为该事务锁定整个表。 Spring JPA 甚至可以实现这一点吗? 既然这似乎并不明智,那么还有其他方法可以解决这个问题吗?

【问题讨论】:

  • 您需要修正您的 id 生成策略 - 标记为重复。
  • 这能回答你的问题吗? JPA primary key auto generate
  • 也许我不明白你的意思,但我没有生成我的 ID。在我的情况下,我有必要设置一个自定义 id (playerId)
  • 可能更好的方法是重试工作单元。
  • @MarkRotteveel 您的意思是我可以捕获 DataIntegrityViolationException 并再次尝试,然后它会更新现有记录?这是可能的,但这种情况经常发生。所以我会多次捕捉到这个异常,它不再是“执行”,而是我的业务逻辑的一部分。这对我来说似乎不正确,也可能对性能不利。

标签: java spring hibernate jpa spring-data-jpa


【解决方案1】:

您可以为此目的使用@SQLInsert 来更改用于插入操作的SQL。根据您的数据库,可能会切换到某些数据库本机冲突/合并处理。见Hibernate Transactions and Concurrency Using attachDirty (saveOrUpdate)

【讨论】:

  • 谢谢克里斯蒂安!与您的建议类似,我最终定义了一个本地查询以在我的存储库中进行更新插入,如下所示:@Query(value = "INSERT INTO PLAYER (...) VALUES(...) ON CONFLICT (ID) DO UPDATE ....", nativeQuery = true)。但是@SQLInsert 看起来也是一个优雅的解决方案,我以前不知道。
猜你喜欢
  • 1970-01-01
  • 2017-05-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-18
  • 1970-01-01
相关资源
最近更新 更多