【发布时间】: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