refactor: lock currency through current ownership
parent
4cacb89813
commit
c5e5256395
@ -0,0 +1,41 @@
|
||||
package com.yau.digitalrmb.shared.currency;
|
||||
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public class CurrencyOwnershipLockRepository {
|
||||
private final JdbcTemplate jdbc;
|
||||
|
||||
public CurrencyOwnershipLockRepository(JdbcTemplate jdbc) {
|
||||
this.jdbc = jdbc;
|
||||
}
|
||||
|
||||
public boolean tryLock(String currencyId, String ownerType, String ownerId,
|
||||
String businessType, String businessId) {
|
||||
return jdbc.update("UPDATE central_bank_currency_ownership " +
|
||||
"SET status='LOCKED',lock_business_type=?,lock_business_id=?,updated_at=CURRENT_TIMESTAMP " +
|
||||
"WHERE currency_id=? AND owner_type=? AND owner_id=? AND status='AVAILABLE'",
|
||||
businessType, businessId, currencyId, ownerType, ownerId) == 1;
|
||||
}
|
||||
|
||||
public boolean transferLocked(String currencyId, String ownerType, String ownerId,
|
||||
String businessType, String businessId,
|
||||
String newOwnerType, String newOwnerId, String transactionId) {
|
||||
return jdbc.update("UPDATE central_bank_currency_ownership SET owner_type=?,owner_id=?,status='AVAILABLE'," +
|
||||
"lock_business_type=NULL,lock_business_id=NULL,last_transaction_id=?,updated_at=CURRENT_TIMESTAMP " +
|
||||
"WHERE currency_id=? AND owner_type=? AND owner_id=? AND status='LOCKED' " +
|
||||
"AND lock_business_type=? AND lock_business_id=?",
|
||||
newOwnerType, newOwnerId, transactionId, currencyId, ownerType, ownerId,
|
||||
businessType, businessId) == 1;
|
||||
}
|
||||
|
||||
public boolean release(String currencyId, String ownerType, String ownerId,
|
||||
String businessType, String businessId) {
|
||||
return jdbc.update("UPDATE central_bank_currency_ownership SET status='AVAILABLE'," +
|
||||
"lock_business_type=NULL,lock_business_id=NULL,updated_at=CURRENT_TIMESTAMP " +
|
||||
"WHERE currency_id=? AND owner_type=? AND owner_id=? AND status='LOCKED' " +
|
||||
"AND lock_business_type=? AND lock_business_id=?",
|
||||
currencyId, ownerType, ownerId, businessType, businessId) == 1;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,77 @@
|
||||
package com.yau.digitalrmb.shared.currency;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@SpringBootTest(properties = "file.path=target/test-files")
|
||||
@ActiveProfiles("test")
|
||||
class CurrencyOwnershipConcurrencyTest {
|
||||
private static final String CURRENCY_ID = "DC_OWNERSHIP_CONCURRENCY";
|
||||
|
||||
@Autowired private JdbcTemplate jdbc;
|
||||
@Autowired private TransactionTemplate transactions;
|
||||
@Autowired private CurrencyOwnershipLockRepository ownershipLocks;
|
||||
|
||||
@BeforeEach
|
||||
void seedCurrencyOwnership() {
|
||||
jdbc.update("INSERT INTO commercial_bank_currency (currency_id,source_currency_id,source_batch_id," +
|
||||
"issuance_request_id,bank_code,organization_id,denomination,currency,complete_currency," +
|
||||
"issuance_ownership_original_text,issuance_ownership_signature,status,received_at," +
|
||||
"received_by_user_id,received_by) VALUES (?,998801,998800,?,'BK_TEST','ORG_TEST',100,'DC'," +
|
||||
"'COMPLETE','OWNERSHIP','SIGNATURE','ISSUED',CURRENT_TIMESTAMP,'test-user','test')",
|
||||
CURRENCY_ID, "99880000-0000-0000-0000-000000000001");
|
||||
jdbc.update("INSERT INTO central_bank_currency_ownership " +
|
||||
"(currency_id,owner_type,owner_id,status,lock_business_type,lock_business_id,last_transaction_id,updated_at) " +
|
||||
"VALUES (?,'WALLET','WALLET_TEST','AVAILABLE',NULL,NULL,NULL,CURRENT_TIMESTAMP)", CURRENCY_ID);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void cleanCurrencyOwnership() {
|
||||
jdbc.update("DELETE FROM central_bank_currency_ownership WHERE currency_id=?", CURRENCY_ID);
|
||||
jdbc.update("DELETE FROM commercial_bank_currency WHERE currency_id=?", CURRENCY_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
void allowsExactlyOneConcurrentBusinessToLockAnAvailableOwnershipRow() throws Exception {
|
||||
ExecutorService executor = Executors.newFixedThreadPool(2);
|
||||
CountDownLatch ready = new CountDownLatch(2);
|
||||
CountDownLatch start = new CountDownLatch(1);
|
||||
try {
|
||||
Future<Boolean> first = executor.submit(() -> lockAfterBarrier("PAYMENT_A", ready, start));
|
||||
Future<Boolean> second = executor.submit(() -> lockAfterBarrier("PAYMENT_B", ready, start));
|
||||
ready.await();
|
||||
start.countDown();
|
||||
|
||||
List<Boolean> results = Arrays.asList(first.get(), second.get());
|
||||
assertThat(results).containsExactlyInAnyOrder(true, false);
|
||||
assertThat(jdbc.queryForObject("SELECT status FROM central_bank_currency_ownership WHERE currency_id=?",
|
||||
String.class, CURRENCY_ID)).isEqualTo("LOCKED");
|
||||
assertThat(jdbc.queryForObject("SELECT lock_business_id FROM central_bank_currency_ownership WHERE currency_id=?",
|
||||
String.class, CURRENCY_ID)).isIn("PAYMENT_A", "PAYMENT_B");
|
||||
} finally {
|
||||
executor.shutdownNow();
|
||||
}
|
||||
}
|
||||
|
||||
private Boolean lockAfterBarrier(String businessId, CountDownLatch ready, CountDownLatch start) throws Exception {
|
||||
ready.countDown();
|
||||
start.await();
|
||||
return transactions.execute(status -> ownershipLocks.tryLock(
|
||||
CURRENCY_ID, "WALLET", "WALLET_TEST", "PAYMENT", businessId));
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue