지금 혼자 프로젝트 하면서 트랜잭션 공부하는데

테이블에 있는 엔티티 확인한 후 조건을 충족 하면 엔티티를 생성 트랜잭션 하는데
트랜잭션이 REPEATBLE_READ나 READ_COMMITED니까

트랜잭션이 10개 동시에 실행된다고 하면 생성후 COMMIT 되기 전까지 다른 엔티티가 있는지도 확인할 수 없어서 전부 중복으로 생성이 되지


문제는 이 엔티티들이 생성되었을 때 서로 다른 엔티티의 존재조건에 위배가 되는거지


계속 생각해봤는데 생성 되기 까지는 중복 생성을 막을 수 없을것 같아서


소스는 JPA Interceptor랑 Spring Transaction 써서 구현 한 거







이렇게 생성후 후처리 하니까 동시 생성 되면 전부 취소돼서 트랙잭션 롤백되서 괜찮은 것 같은데


다른 기법 같은 거 없음?


2023-05-05 추가


결국 트랜잭션이 Serial 해야 한다는 거 같은데

인풋이 account 랑 gameId 만 안겹치면 serializable 한거 같아서

AOP랑 Lock 걸어서 해결했음.



@Component

@Aspect
class OrderCreateAspect {
static private ConcurrentHashMap<LockCondition, ReentrantLock> mutexMap = new ConcurrentHashMap<>();
@Around("execution(* com.teemo.shopping.order.service.OrderService.create(..))")
public Object Around(ProceedingJoinPoint pointcut) throws Throwable {
List<ReentrantLock> locks = new ArrayList<>();
Long accountId = (Long) pointcut.getArgs()[0];
List<Long> gameIds = ((List<GamePaymentInformation>) pointcut.getArgs()[3]).stream()
.map(gamePaymentInformation -> gamePaymentInformation.getGameId()).toList();
for (var gameId : gameIds) {
LockCondition condition = new LockCondition(accountId, gameId);
ReentrantLock lock;
synchronized (mutexMap) {
lock = mutexMap.get(new LockCondition(accountId, gameId));
if (lock == null) {
lock = new ReentrantLock();
}
mutexMap.put(condition, lock);
}
if (!lock.tryLock()) {
for (var beforeLock : locks) {
beforeLock.unlock();
}
locks.clear();
break;
} else {
locks.add(lock);
}
}
Object result;
try {
if (locks.size() == gameIds.size()) {
result = pointcut.proceed();
} else {
throw new IllegalStateException("트랜잭션 충돌");
}
} catch (Exception e) {
throw e;
} finally {
for (var lock : locks) {
lock.unlock();
}
}
return result;
}
@Getter
@AllArgsConstructor
static class LockCondition {
final private Long accountId;
final private Long gameId;
@Override
public boolean equals(Object obj) {
if (obj instanceof LockCondition) {
LockCondition condition = (LockCondition) obj;
return accountId.equals(condition.accountId) && gameId.equals(condition.gameId);
}
return false;
}
@Override
public int hashCode() {
return (int) (863743 * accountId + 1209109 * gameId);
}
}
}