데이터베이스에서 모든 트랜잭션에 대해 Lock과 Unlock을 사용하는 방법은 데이터베이스 및 트랜잭션의 동작 방식에 따라 다릅니다. 일반적으로 테이블 잠금, 행 잠금, 트랜잭션 격리 수준, 명시적 잠금 해제를 조합하여 구현할 수 있습니다. Java 환경에서는 JDBC, JPA, QueryDSL 등을 활용해 이를 설정할 수 있습니다.
아래는 SELECT뿐만 아니라 INSERT, UPDATE, DELETE 등 모든 트랜잭션에서 Lock과 Unlock을 사용하는 방법을 설명합니다.
1. 테이블 잠금 (Table Lock)
테이블 단위로 잠금을 설정하여 트랜잭션이 완료되기 전까지 다른 트랜잭션의 접근을 막습니다.
예제 (JDBC 기반):
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
public class TableLockExample {
public static void main(String[] args) {
String url = "jdbc:mysql://localhost:3306/your_database";
String user = "your_username";
String password = "your_password";
try (Connection connection = DriverManager.getConnection(url, user, password)) {
// 트랜잭션 시작
connection.setAutoCommit(false);
// 테이블 잠금
try (Statement statement = connection.createStatement()) {
statement.execute("LOCK TABLES your_table WRITE");
// INSERT, UPDATE, DELETE 수행
statement.executeUpdate("INSERT INTO your_table (column1) VALUES ('value')");
statement.executeUpdate("UPDATE your_table SET column1 = 'new_value' WHERE column2 = 'some_value'");
statement.executeUpdate("DELETE FROM your_table WHERE column2 = 'some_value'");
// 커밋 및 잠금 해제
connection.commit();
statement.execute("UNLOCK TABLES");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
주요 포인트:
LOCK TABLES your_table WRITE: 테이블 전체에 쓰기 잠금 설정.UNLOCK TABLES: 잠금 해제.
2. 행 잠금 (Row Lock)
특정 행에 대해 잠금을 설정하여 다른 트랜잭션이 동시에 해당 행을 수정하거나 삭제하지 못하도록 설정합니다.
예제 (JPA 기반):
import jakarta.persistence.LockModeType;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class RowLockExample {
private final JPAQueryFactory queryFactory;
public RowLockExample(EntityManager entityManager) {
this.queryFactory = new JPAQueryFactory(entityManager);
}
@Transactional
public void updateWithRowLock(Long id, String newValue) {
QYourTable yourTable = QYourTable.yourTable;
// 행 잠금 설정
YourTable record = queryFactory.selectFrom(yourTable)
.where(yourTable.id.eq(id))
.setLockMode(LockModeType.PESSIMISTIC_WRITE)
.fetchOne();
if (record != null) {
record.setColumn1(newValue);
queryFactory.getEntityManager().merge(record);
}
}
}
주요 포인트:
LockModeType.PESSIMISTIC_WRITE: 특정 행을 잠그고 다른 트랜잭션의 쓰기 작업을 차단.
3. 트랜잭션 격리 수준
격리 수준을 설정하면 모든 트랜잭션에서 잠금 동작을 관리할 수 있습니다.
트랜잭션 격리 수준 설정:
@Service
public class IsolationExample {
@Transactional(isolation = Isolation.SERIALIZABLE)
public void performTransaction(Long id) {
// 트랜잭션 내 작업
}
}
Isolation 옵션:
- READ_UNCOMMITTED: 데이터 잠금을 사용하지 않음.
- READ_COMMITTED: 커밋된 데이터만 읽을 수 있음.
- REPEATABLE_READ: 트랜잭션 내 동일 데이터가 변경되지 않음.
- SERIALIZABLE: 트랜잭션을 순차적으로 실행하며, 가장 높은 격리 수준.
4. 명시적 Lock 및 Unlock
명시적으로 LOCK과 UNLOCK을 수행하여 트랜잭션 작업을 관리할 수 있습니다.
예제 (Native Query):
@Service
public class ExplicitLockExample {
@PersistenceContext
private EntityManager entityManager;
@Transactional
public void performTransactionWithLock() {
// 명시적으로 테이블 잠금
entityManager.createNativeQuery("LOCK TABLES your_table WRITE").executeUpdate();
// 작업 수행
entityManager.createNativeQuery("INSERT INTO your_table (column1) VALUES ('value')").executeUpdate();
// 명시적으로 잠금 해제
entityManager.createNativeQuery("UNLOCK TABLES").executeUpdate();
}
}
5. QueryDSL 기반 잠금 처리
QueryDSL을 사용해 모든 트랜잭션에서 잠금을 관리할 수 있습니다.
QueryDSL에서 Lock 설정:
@Transactional
public void performQueryWithLock(Long id) {
QYourTable yourTable = QYourTable.yourTable;
// Pessimistic Lock 설정
YourTable record = queryFactory.selectFrom(yourTable)
.where(yourTable.id.eq(id))
.setLockMode(LockModeType.PESSIMISTIC_WRITE)
.fetchOne();
if (record != null) {
// 데이터 작업 수행
record.setColumn1("new_value");
queryFactory.getEntityManager().merge(record);
}
}
6. Lock 적용 시 고려 사항
- 잠금 범위 제한: 테이블 전체보다는 가능한 행 수준에서 잠금을 설정.
- Deadlock 방지: 여러 트랜잭션이 교착 상태에 빠지지 않도록 잠금 순서를 설계.
- 성능 고려: 높은 트랜잭션 격리 수준이나 광범위한 잠금은 성능에 영향을 줄 수 있음.
- 트랜잭션 종료 시 잠금 해제: 트랜잭션 커밋 또는 롤백 시 자동으로 잠금 해제되도록 트랜잭션 관리에 유의.
요약
- 테이블 잠금:
LOCK TABLES와UNLOCK TABLES. - 행 잠금:
LockModeType.PESSIMISTIC_WRITE또는FOR UPDATE. - 트랜잭션 격리 수준:
@Transactional(isolation = ...). - QueryDSL:
setLockMode를 사용해 잠금 처리. - 명시적 잠금: Native Query로 제어.
상황에 맞는 잠금 전략을 선택하고 성능 및 동시성 문제를 최소화하도록 설계하세요.