简体   繁体   中英

Insert/update entitiy from two different threads at the same time

I have the following method that adds a player to the database or updates it, should it already exist:

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

This method is sometimes called by two different threads and therefore executed two times at almost the same time. Sometimes I get the following exception:

org.h2.jdbc.JdbcSQLIntegrityConstraintViolationException: Unique index or primary key violation: "PUBLIC.PRIMARY_KEY_B42 ON PUBLIC.PLAYER(ID) VALUES 3";

This makes sense to me because both methods check in the beginning if the player is in the database and it is not in both cases. Then both try to insert and the slower one runs into the constraint.

I want to lock the player table during the execution of this method, so if it is called multiple times, they have to wait for each other.

My player repository looks like this:

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

I also tried to increase isolation level on the transaction:

@Transactional(isolation = Isolation.SERIALIZABLE)

However, I still get this exception, so apparently the inserts are still running concurrently. What am I doing wrong? Or is there a different way to achieve what i am trying here?

After some more research, I think I understand why neither @Lock nor @Transactional(isolation = Isolation.SERIALIZABLE) are doing anything. They basically work by locking records in a database table for the duration of the transaction, but since there are no records to lock in my case, its not doing anything.

So, I guess I would have to lock the whole table for that transaction. Is that even possible with Spring JPA? And since that doesn't seem smart performance wise, is there an other way to tackle this problem?

You could use @SQLInsert for this purpose to change the SQL that is used for the insert operation. Depending on your database, it might be possible to switch to some database native conflict/merge handling. See Hibernate Transactions and Concurrency Using attachDirty (saveOrUpdate)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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