неправильная последовательность SELECT и UPDATES в хранилище доступа к Spring-Service - PullRequest
0 голосов
/ 28 декабря 2018

У меня есть все простые классы, но у меня проблема с сервисом spring-boot и хранилищем.Как будто у меня есть тестовый класс со следующим тестом и необходимым методом execut ():

@Test
public void deposit() throws Exception {
    long balance = accountService.getBalance(accountNr, pin);
    execute(() -> accountService.deposit(accountNr, amount), INVOCATIONS);
    long newBalance = accountService.getBalance(accountNr, pin);
    assertEquals(balance + INVOCATIONS * amount, newBalance);
}

public static void execute(Task task, int times) throws InterruptedException 
{
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < times; i++) {
            executorService.submit(() -> {
                try {
                    task.run();
                } catch (Exception ex) {
                    throw new RuntimeException(ex);
                }
            });
        }
        executorService.shutdown();
        executorService.awaitTermination(1, TimeUnit.HOURS);
    }

Тогда есть очень простая сущность с четырьмя атрибутами:

@Entity
public class Account {

    @Id
    @GeneratedValue
    private Integer nr;

    @Version
    private Integer version;
    private String pin;
    private long balance;
...

Службаесть метод, который сначала ищет учетную запись, изменяет значение и пытается сохранить его в базе данных:

public void deposit(int accountNr, long amount) throws InvalidCredentials, InvalidTransaction {
    Account account = accountRepository.getAccountByNr(accountNr);
    account.deposit(amount);
    accountRepository.saveAndFlush(account);
}

Когда я сейчас выполняю тест, SELECT и UPDATES перепутаны, так что правильныев конце значение отсутствует в базе данных.

Затем я предоставил методу сервиса @Transactional (изоляция = Isolation.READ_COMMITTED, распространение = Propagation.REQUIRES_NEW), который тоже не помог.

У кого-нибудь есть идеи?

Вывод журнала после весны - баннер:

 INFO 20320 --- [           main] o.e.b.a.AccountServiceConcurrentIT       : Started AccountServiceConcurrentIT in 9.063 seconds (JVM running for 11.324)
DEBUG 20320 --- [           main] org.hibernate.SQL : select nextval ('hibernate_sequence')
DEBUG 20320 --- [           main] org.hibernate.SQL : insert into account (balance, pin, version, nr) values (?, ?, ?, ?)
DEBUG 20320 --- [           main] org.hibernate.SQL : select account0_.balance as col_0_0_ from account account0_ where account0_.nr=? and account0_.pin=?
DEBUG 20320 --- [pool-1-thread-8] org.hibernate.SQL : select account0_.nr as nr1_0_, account0_.balance as balance2_0_, account0_.pin as pin3_0_, account0_.version as version4_0_ from account account0_ where account0_.nr=?
DEBUG 20320 --- [pool-1-thread-2] org.hibernate.SQL : select account0_.nr as nr1_0_, account0_.balance as balance2_0_, account0_.pin as pin3_0_, account0_.version as version4_0_ from account account0_ where account0_.nr=?
DEBUG 20320 --- [pool-1-thread-6] org.hibernate.SQL : select account0_.nr as nr1_0_, account0_.balance as balance2_0_, account0_.pin as pin3_0_, account0_.version as version4_0_ from account account0_ where account0_.nr=?
DEBUG 20320 --- [pool-1-thread-9] org.hibernate.SQL : select account0_.nr as nr1_0_, account0_.balance as balance2_0_, account0_.pin as pin3_0_, account0_.version as version4_0_ from account account0_ where account0_.nr=?
DEBUG 20320 --- [pool-1-thread-2] org.hibernate.SQL : update account set balance=?, pin=?, version=? where nr=?
DEBUG 20320 --- [pool-1-thread-6] org.hibernate.SQL : update account set balance=?, pin=?, version=? where nr=?
DEBUG 20320 --- [pool-1-thread-8] org.hibernate.SQL : update account set balance=?, pin=?, version=? where nr=?
DEBUG 20320 --- [pool-1-thread-4] org.hibernate.SQL : select account0_.nr as nr1_0_, account0_.balance as balance2_0_, account0_.pin as pin3_0_, account0_.version as version4_0_ from account account0_ where account0_.nr=?
DEBUG 20320 --- [pool-1-thread-4] org.hibernate.SQL : update account set balance=?, pin=?, version=? where nr=?
DEBUG 20320 --- [pool-1-thread-9] org.hibernate.SQL : update account set balance=?, pin=?, version=? where nr=?
DEBUG 20320 --- [pool-1-thread-3] org.hibernate.SQL : select account0_.nr as nr1_0_, account0_.balance as balance2_0_, account0_.pin as pin3_0_, account0_.version as version4_0_ from account account0_ where account0_.nr=?
DEBUG 20320 --- [pool-1-thread-5] org.hibernate.SQL : select account0_.nr as nr1_0_, account0_.balance as balance2_0_, account0_.pin as pin3_0_, account0_.version as version4_0_ from account account0_ where account0_.nr=?
DEBUG 20320 --- [ool-1-thread-10] org.hibernate.SQL : select account0_.nr as nr1_0_, account0_.balance as balance2_0_, account0_.pin as pin3_0_, account0_.version as version4_0_ from account account0_ where account0_.nr=?
DEBUG 20320 --- [pool-1-thread-1] org.hibernate.SQL : select account0_.nr as nr1_0_, account0_.balance as balance2_0_, account0_.pin as pin3_0_, account0_.version as version4_0_ from account account0_ where account0_.nr=?
DEBUG 20320 --- [pool-1-thread-7] org.hibernate.SQL : select account0_.nr as nr1_0_, account0_.balance as balance2_0_, account0_.pin as pin3_0_, account0_.version as version4_0_ from account account0_ where account0_.nr=?
DEBUG 20320 --- [pool-1-thread-7] org.hibernate.SQL : update account set balance=?, pin=?, version=? where nr=?
DEBUG 20320 --- [ool-1-thread-10] org.hibernate.SQL : update account set balance=?, pin=?, version=? where nr=?
DEBUG 20320 --- [pool-1-thread-5] org.hibernate.SQL : update account set balance=?, pin=?, version=? where nr=?
DEBUG 20320 --- [pool-1-thread-3] org.hibernate.SQL : update account set balance=?, pin=?, version=? where nr=?
DEBUG 20320 --- [pool-1-thread-1] org.hibernate.SQL : update account set balance=?, pin=?, version=? where nr=?
DEBUG 20320 --- [           main] org.hibernate.SQL : select account0_.balance as col_0_0_ from account account0_ where account0_.nr=? and account0_.pin=?

java.lang.AssertionError: 
Expected :10000
Actual   :1000

1 Ответ

0 голосов
/ 30 декабря 2018

Вы используете ExecutorService для запуска 10 потоков.

Эти 10 потоков работают независимо.Это означает, что нет предсказуемого порядка, какой поток запускается первым.Как вы видите в журнале:

[pool-1-thread-8]
[pool-1-thread-2]
[pool-1-thread-6]
[pool-1-thread-9]
[pool-1-thread-2]
[pool-1-thread-6]
[pool-1-thread-8]
[pool-1-thread-4]
[pool-1-thread-4]
[pool-1-thread-9]
[pool-1-thread-3]
[pool-1-thread-5]
[ool-1-thread-10]
[pool-1-thread-1]
[pool-1-thread-7]
[pool-1-thread-7]
[ool-1-thread-10]
[pool-1-thread-5]
[pool-1-thread-3]
[pool-1-thread-1]

Чтобы избежать перезаписи баланса вашей учетной записи, вы должны использовать пессимистическую блокировку.

Это может быть достигнуто в хранилище с помощью аннотации @Lock:

@Lock(LockModeType.PESSIMISTIC_WRITE)
Account account = accountRepository.getAccountByNr(accountNr);

Необходимо убедиться, что метод депозита является транзакционным для запуска всего кода в одной и той же транзакции:

@Transactional
public void deposit(int accountNr, long amount) throws InvalidCredentials, InvalidTransaction {
    Account account = accountRepository.getAccountByNr(accountNr);
    account.deposit(amount);
    accountRepository.saveAndFlush(account);
}

Так что каждый вызов getAccountByNr будет блокировать запись иконец транзакции снимет блокировку.

...