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