본 글은 김영한님의 <자바 ORM 표준 JPA 프로그래밍>을 읽고 공부한 내용을 정리한 글입니다.
원자성(Atomicity) : 트랜잭션 내에서 실행된 작업들은 마치 하나의 작업처럼 모두 성공하거나 모두 실패해야 한다.
일관성(Consistency) : 데이터베이스에서 정한 무결성 제약조건을 항상 만족해야 한다.
격리성(Isolation) : 동시에 실행되는 트랜잭션들이 서로에게 영향을 미치지 않도록 격리한다. 예를 들어, 동시에 데이터를 수정하지 못하도록 한다.
지속성(Durability) : 트랜잭션을 성공적으로 끝내면 그 결과가 항상 기록되어야 한다.
트랜잭션은 원자성,일관성,지속성은 보장하지만 격리성은 보장하지 않는다.
아래로 갈수록 격리수준이 높아지고, 동시성과 문제점은 줄어든다.
READ UNCOMMITED
READ COMMITED : 다른 사용자가 데이터를 변경하는 동안 사용자는 데이터를 읽을 수 없다.
REPEATABLE READ : 선행 트랜잭션이 조회한 데이터는 트랜잭션이 종료될때까지 후행 트랜잭션은 수정,삭제가 불가능하다.
SERIALIZABLE : 동일한 데이터에 대해서 두 개의 트랜잭션이 수행될 수 없다.
애플리케이션은 동시성 처리가 중요하므로 보통 READ COMMITED를 기본으로 사용한다.
JPA의 영속성 컨텍스트를 적절히 활용하면 데이터베이스의 격리수준이 READ COMMITED어도 애플리케이션 레벨에서 반복 가능한 읽기가 가능하다.
-> 영속 엔티티 동일성 보장
일부 로직에서 더 높은 격리 수준이 필요하면 낙관적 락과 비관적 락 중 하나를 사용해야 한다.
애플리케이션, JPA가 제공하는 버전 관리 기능을 사용한다. 트랜잭션을 커밋하기 전까지는 트랜잭션의 충돌을 알 수 없다.
트랜잭션의 충돌이 발생한다고 우선 락을 걸고 보는 방법이다. 데이터베이스에서 제공하는 락 기능을 사용한다.
사용자 A,B가 동시에 이름을 수정하는 중에 사용자 A가 먼저 수정완료 버튼을 누르고, 후에 사용자 B가 버튼을 누른다면 A의 수정사항은 사라지고 B의 수정사항만 남게 된다.
데이터베이스 트랜잭션 범위를 넘어서는 문제
READ COMMITTED 격리 수준 + 낙관적 버전관리
네트워크를 통해 DB를 접근하는 방식은 서버에서 내부 메모리를 접근하는 것보다 시간비용이 수만에서 수십만 배 이상 높다.
따라서 DB를 조회해서 내부 메모리 캐시에 저장해두면 비용을 아낄 수 있다.
1차 캐시는 영속성 컨텍스트 내부에 있다. 엔티티 매니저로 조회하거나 변경하는 모든 엔티티는 1차 캐시에 저장된다.
1차 캐시는 같은 엔티티가 있으면 해당 엔티티를 그대로 반환한다. 따라서 1차 캐시는 개체 동일성(a==b)을 보장한다.
1차 캐시는 기본적으로 영속성 컨텍스트 범위의 캐시다(컨테이너 환경에서는 트랜잭션 범위의 캐시, OSIV를 적용하면 요청 범위의 캐시다).
애플리케이션에서 공유하는 캐시를 JPA는 공유 캐시(shared caches)라 하는데 일반적으로 2차 캐시(second level cache, L2 cache)라 부른다. 2차 캐시는 애플리케이션 범위의 캐시로 애플리케이션을 종료할 때까지 캐시가 유지된다.
2차 캐시는 동시성을 극대화하려고 캐시한 객체를 직접 반환하지 않고 복사본을 만들어서 반환한다.
2차 캐시는 데이터베이스 기본 키를 기준으로 캐시하지만 영속성 컨텍스트가 다르면 객체 동일성(a==b)를 보장하지 않는다.
엔티티 클래스에 @Cacheable 어노테이션을 사용한다.
캐시 모드는 javax.persistence.SharedCacheMode에 정의되어 있다.
캐시 모드에 따라 캐시를 무시할수도 있고 더 세밀하게 캐시 사용을 설정할 수 있다.
캐시를 하면 엔티티는 엔티티 캐시 영역, 연관 참조 엔티티는 컬렉션 캐시 영역에 저장된다.
엔티티 캐시 영역: [패키지 명 + 클래스 명]
컬렉션 캐시 영역: [패키지 명 + 클래스 명 + 필드 명]
쿼리 캐시는 쿼리와 파라미터 정보를 키로 활용해서 쿼리 결과를 캐시하는 방법이다.
org.hibernate.cacheable = true로 설정해야 한다.
쿼리/컬렉션 캐시는 대상 엔티티 캐시와 함께 써야한다.
-> 쿼리 캐시는 엔티티 식별자 값만 들어있기 때문에 실제 엔티티 정보는 엔티티 캐시를 통해 하나씩 조회해오기 때문이다.