Read Committed
정도로 가정하며 더 높은 격리 수준이 필요한 경우 낙관적, 혹은 비관적 락 중 하나를 사용하면 된다.두 번의 갱신 분실 문제 second lost updates problem
가 있다.트랜잭션 A, B가 동시에 같은 데이터를 수정하고 A가 먼저 수정 완료한 후 B가 수정을 완료할 때
먼저 완료한 A의 수정 사항은 사라지고 나중에 완료한 B의 수정 사항만 남는 것
- 마지막 커밋만 인정 : A의 수정은 무시, B가 수정한 것만 인정
- 최초 커밋만 인정 : A가 이미 수정을 완료했으므로 B가 수정 완료할 때 오류 발생
- 충돌하는 갱신 내용 병합 : A, B의 수정 사항을 병합
Long(long)
Integer(int)
Short(short)
Timestamp
@Entity
public class Board {
@Id
private Long id;
private String title;
@Version
private Integer version;
...
}
최초 커밋만 인정하기
를 적용할 수 있음UPDATE board
SET title = ?,
version = ? #version + 1
WHERE
id = ?
AND version = ? #version 비교
JPA 사용시 추천 전략은 Read Committed + 낙관적 버전 관리
EntityManager.lock(), EntityManager.find(), EntityManager.refresh()
Query.setLockMode() //TypeQuery 포함
@NamedQuery
Board board = em.find(Board.class, id, LockModeType.OPTIMISTIC);
Board board = em.find(Board.class, id);
...
em.lock(board, LockModeType.OPTIMISTIC);
...
javax.persistence.LockModeType
에 정의된 Lock 옵션@Version
만으로도 낙관적 락이 적용되지만 락 옵션을 사용하면 더 세밀하게 제어 가능두 번의 갱신 분실 문제
예방Dirty Read
, Non-Repeatable Read
방지select for update
구문을 사용해 시작하고 버전 정보는 사용하지 않음PESSIMISTIC_WRITE
옵션 사용select for update
를 사용해 락을 건다.Non-Repeatable Read
를 방지lock in share mode
for share
nowait
을 지원하는 DB에 대해 for update nowait
옵션을 적용for update nowait
for update nowait
for update
사용비관적 락을 사용하면 락을 획득할 때까지 트랜잭션이 대기하는데 무한정 기다릴 수 없기 때문에 타임아웃 시간을 줄 수 있음
지정한 시간을 초과 후 응답이 없으면 javax.persistence.LockTimeoutException
예외 발생
Map<String, Object> properties = new HashMap<>();
//타임아웃 10초까지 대기 설정
properties.put("javax.persistence.lock.timeout", 10000);
Board board = em.find(Board.class, "boardId",
LockModeType.PESSIMISTIC_WRITE, properties);
@Lock(LockModeType.PESSIMISTIC_WRITE)
@QueryHints({@QueryHint(name="javax.persistence.lock.timeout", value="10000")})
Optional<Board> findById(Long boardId);
타임아웃은 DB에 따라 동작하지 않을 수도 있음