Skip to content

Instantly share code, notes, and snippets.

@rponte
Last active December 4, 2023 15:07
Show Gist options
  • Save rponte/6b2a81ddc7cdf1c06ec62aa4f0eb6002 to your computer and use it in GitHub Desktop.
Save rponte/6b2a81ddc7cdf1c06ec62aa4f0eb6002 to your computer and use it in GitHub Desktop.
Spring: You should not mix @transactional with synchronized
@Service
public class NaiveJavaSynchronizedATMService {
@Autowired
private AccountRepository repository;
@Transactional
public synchronized void withdraw(Long accountId, double amount) {
Account account = repository.findById(accountId).orElseThrow(() -> {
throw new IllegalStateException("account does not exist: " + accountId);
});
double newBalance = (account.getBalance() - amount);
if (newBalance < 0) {
throw new IllegalStateException("there's not enough balance");
}
account.setBalance(newBalance);
}
}
class NaiveJavaSynchronizedATMServiceTest extends SpringBootIntegrationTest {
@Autowired
private AccountRepository accountRepository;
@Autowired
private TransactionRepository transactionRepository;
@Autowired
private NaiveJavaSynchronizedATMService naiveJavaSynchronizedATMService;
private Account ACCOUNT;
@BeforeEach
public void setUp() {
transactionRepository.deleteAll();
accountRepository.deleteAll();
this.ACCOUNT = accountRepository
.save(new Account("Jordi", 100.0));
}
@Test
@DisplayName("should withdraw money from account concurrently")
public void t2() throws InterruptedException {
doSyncAndConcurrently(10, s -> {
naiveJavaSynchronizedATMService
.withdraw(ACCOUNT.getId(), 20.0);
});
assertAll("account and transaction states",
() -> assertEquals(0.0, accountRepository.getBalance(ACCOUNT.getId()), "account balance"),
() -> assertEquals(5, transactionRepository.countByAccount(ACCOUNT), "number of transactions")
);
}
}
@nilson-aguiar
Copy link

is the doSyncAndConcurrently from a lib?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment