컬렉션과 부가 기능
- 컬렉션
- JPA는 자바에서 기본으로 제공하는 Collection, List, Set, Map 컬렉션을 지원하며
일대다나 다대다 엔티티 관계를 매핑할 때, 값 타입을 하나 이상 보관할 때 사용함 - 하이버네이트는 엔티티를 영속 상태로 만들 때 컬렉션을 효율적으로 관리하기 위해
원본 컬렉션을 감싸고 있는 내장 컬렉션을 생성한 후 생성된 내장 컬렉션을 사용하도록 참조를 변경하도록 함
이렇게 하이버네이트가 제공하는 내장 컬렉션은 원본 컬렉션을 감싸고 있어서 래퍼 컬렉션으로도 불림 - 컬렉션 인터페이스가 Collection, List일 때는
내장 컬렉션으로 PersistentBag을 사용하며 중복을 허용하고 순서를 보장하지 않음 - 컬렉션 인터페이스가 Set일 때는
내장 컬렉션으로 PersistentSet을 사용하며 중복을 허용하지 않고 순서를 보장하지 않음 - 컬렉션 인터페이스가 List + @OrderColumn일 때는
내장 컬렉션으로 PersistentList를 사용하며 중복을 허용하고 순서를 보장함
하지만 @OrderColumn을 사용할 경우 UPDATE SQL이 추가 되는 단점이 존재하므로
순서용 컬럼 매핑 대신 @OrderBy를 사용해 컬렉션을 정렬하는 것을 권장함
- JPA는 자바에서 기본으로 제공하는 Collection, List, Set, Map 컬렉션을 지원하며
- @Converter
- 컨버터를 사용하면 엔티티의 데이터를 변환해서 데이터베이스에 저장할 수 있음
- 예) @Converter 어노테이션을 사용하고 AttributeConverter 인터페이스를 구현한 후
제네릭에 현재 타입인 Boolean과 반환할 타입인 String을 지정하면
데이터베이스에 저장되기 직전에 컨버터가 동작하여 숫자(0, 1) 대신 문자(Y, N)으로 저장할 수 있음 - 만약 모든 Boolean 타입에 컨버터를 적용하려고 한다면 @Converter(autoApply = true) 옵션을 통해 글로벌 설정을 함
- 리스너
- JPA 리스너 기능을 사용해 엔티티의 생명주기에 따른 이벤트를 처리할 수 있음
- 이벤트를 잘 활용하면 대부분의 엔티티에 공통으로 적용하는 등록 일자, 수정 일자 처리와
해당 엔티티를 누가 등록하고 수정했는지에 대한 기록을 리스너 하나로 처리할 수 있음 - 이벤트 종류에는
엔티티가 영속성 컨텍스트에서 조회된 직후 또는 refresh를 호출한 후인 PostLoad,
엔티티를 영속성 컨텍스트에 관리/수정/삭제하기 직전에 호출하는 PrePersist, PreUpdate, PreRemove,
엔티티를 데이터베이스에 저장/수정/삭제한 직후에 호출하는 PostPersist, PostUpdate, PostRemove가 존재 - 이벤트는 어노테이션을 지정해 엔티티에서 직접 받거나 별도의 리스너를 등록해서 받을 수 있음
- 엔티티 그래프
- 엔티티를 조회할 때 연관된 엔티티들을 함께 조회하려면 FetchType.EAGER로 설정하거나 JPQL에서 페치 조인을 사용
하지만 FetchType.EAGER를 통한 글로벌 fetch 옵션은 애플리케이션 전체에 영향을 주고 변경할 수 없는 단점이 존재함
그러므로 일반적으로 글로벌 fetch 옵션은 FetchType.LAZY를 사용하고
엔티티를 조회할 때 연관된 엔티티를 함께 조회할 필요가 있으면 JPQL의 페치 조인을 사용하도록 함 - 하지만 페치 조인을 사용하면 JPQL이 데이터를 조회하는 기능 뿐만 아니라
연관된 엔티티를 함께 조회하는 기능을 제공할 때 같은 JPQL을 중복으로 작성하는 경우가 발생하므로
엔티티 그래프 기능을 사용해 엔티티를 조회하는 시점에 함께 조회할 연관된 엔티티를 선택하도록 하여
JPQL은 데이터를 조회하는 기능만 수행하고 연관된 엔티티를 함께 조회하는 기능에는 엔티티 그래프를 사용하도록 함 - Named 엔티티 그래프의 어노테이션인 @NamedEntityGraph를 사용하고 그 값으로 함께 조회할 속성을 선택하면
지연 로딩으로 설정되어 있어도 엔티티 그래프에서 함께 조회할 속성으로 선택되었으므로 엔티티를 조회할 때 함께함
이후 Named 엔티티 그래프를 사용하려면 em.getEntityGraph()와 힌트를 통해 정의한 엔티티 그래프를 찾아오면 됨
이 때 힌트의 키로 javax.persistence.fetchgraph를 사용하여 힌트의 값으로 찾아온 엔티티 그래프를 사용하게 됨 - 만약 하나의 엔티티 그래프만이 아닌 Order → OrderItem → Item처럼 함께 조회하는 경우라면
subgraph 속성의 어노테이션인 @NamedSubgraph를 사용해서 서브 그래프가 item까지 조회하도록 정의함 - JPQL에서 엔티티 그래프를 사용하기 위해서는 em.find()와 동일하게 힌트만 추가하면 됨
- 엔티티 그래프를 동적으로 구성하려면 em.createEntityGraph()를 이용해 동적으로 엔티티 그래프를 만든 후
graph.addAttributeNodes()를 사용해서 함께 조회할 속성을 엔티티 그래프에 포함하도록 하고 힌트를 추가함
마찬가지로 subgraph의 경우 graph.addSubgraph()를 통해 서브 그래프를 만들고 속성을 포함하도록 함
- 엔티티를 조회할 때 연관된 엔티티들을 함께 조회하려면 FetchType.EAGER로 설정하거나 JPQL에서 페치 조인을 사용
고급 주제와 성능 최적화
- 예외 처리
- JPA 표준 예외들은 PersistencException의 자식 클래스이며 이 예외 클래스는 RuntimeException의 자식임
따라서 JPA 예외들은 언체크 예외이며 트랜잭션 롤백을 표시하는 예외와 트랜잭션 롤백을 표시하지 않는 예외로 구분됨
트랜잭션 롤백을 표시하는 예외의 경우 심각한 예외이므로 복구해서는 안되며 강제로 커밋할 수 없지만
트랜잭션 롤백을 표시하지 않는 예외의 경우 심각한 예외가 아니므로 개발자가 커밋할지 롤백할지를 판단하면 됨 - 서비스 계층에서 JPA 예외를 직접 사용하면 JPA에 의존하게 되므로 스프링 프레임워크를 이를 해결하기 위해
데이터 접근 계층에 대한 예외를 추상화하여 JPA 예외가 스프링 예외로 변환되어 개발자에게 제공함 - JPA 예외를 스프링 프레임워크가 제공하는 추상화된 예외로 변경하기 위해서는
PersistenceExceptionTranslationPostProcessor를 스프링 빈으로 등록하면 됨
이를 등록하게 되면 @Repository 어노테이션을 사용한 곳에 예외 변환 AOP를 적용해서 스프링 예외로 변환이 됨 - 트랜잭션 롤백을 하면 데이터베이스의 반영사항만 롤백될 뿐 수정된 상태의 객체는 영속성 컨텍스트에 남아있게 됨
트랜잭션 당 영속성 컨텍스트 전략을 사용할 경우 트랜잭션 AOP 종료 시점에 트랜잭션을 롤백하면서
영속성 컨텍스트도 함께 종료가 되므로 문제가 발생하지 않지만
영속성 컨텍스트 범위를 트랜잭션 범위보다 넓게 사용해 여러 트랜잭션이 하나의 영속성 컨텍스트를 사용하는 OSIV의 경우
트랜잭션을 롤백해서 영속성 컨텍스트에 이상이 발생해도 다른 트랜잭션에서 해당 영속성 컨텍스트를 그대로 사용하므로
이 경우 롤백 시 EntityManager.clear()로 영속성 컨텍스트를 초기화해서 잘못된 영속성 컨텍스트를 사용하지 않도록 함
- JPA 표준 예외들은 PersistencException의 자식 클래스이며 이 예외 클래스는 RuntimeException의 자식임
- 엔티티 비교
- 엔티티의 비교를 위해서는 동일성(==), 동등성(equals()), 데이터베이스 동등성 방법이 존재함
- 테스트가 트랜잭션 안에서 시작하여 테스트와 트랜잭션의 범위가 같을 경우, 테스트 전체는 같은 영속성 컨텍스트에 접근함
그러므로 영속성 컨텍스트가 같은 엔티티의 비교는 같은 영속성 컨텍스트를 보장하므로 동일성 비교만으로 충분함 - 하지만 트랜잭션 범위와 영속성 컨텍스트의 범위가 다르면
같은 데이터베이스 로우를 가리키고 있어 사실상 같은 엔티티지만 영속성 컨텍스트가 달라 동일성 비교에 실패하게 됨
그러므로 영속성 컨텍스트가 다를 때 엔티티 비교는 동등성 비교인 equals()를 오버라이딩하면서
비즈니스 키가 되는 필드를 선택하여 비교하도록 함
- 프록시 심화 주제
- 프록시는 원본 엔티티를 상속받아서 만들어지므로 원본 엔티티인지 프록시인지 구분하지 않고 사용할 수 있으며
지연 로딩을 하려고 프록시로 변경해도 클라이언트의 비즈니스 로직을 수정하지 않아도 됨
하지만 프록시를 사용하는 방식의 기술적인 한계로 예상하지 못한 문제들이 발생하기도 함 - 영속성 컨텍스트는 자신이 관리하는 영속 엔티티의 동일성을 보장하며 프록시로 조회한 엔티티의 동일성도 보장하므로
프록시를 먼저 조회하고 원본 엔티티를 조회할 경우 원본 엔티티가 아닌 처음 조회한 프록시를 반환하며
반대로 원본 엔티티를 먼저 조회하고 프록시를 조회해도 이미 데이터베이스에서 원본 엔티티를 조회했으므로
프록시를 반환할 이유가 없어 프록시가 아닌 원본을 반환하므로 영속 엔티티의 동일성이 보장됨 - 프록시는 원본 엔티티를 상속 받아서 만들어지므로 프록시로 조회한 엔티티의 타입을 비교할 때는 instanceof를 사용
- 엔티티의 동등성을 비교하려면 비즈니스 키를 사용해서 equals() 메소드를 오버라이딩하여 비교하게 되는데
이 때 비교 대상이 원본 엔티티가 아니라 프록시라면 동등성 비교의 경우 == 가 아닌 instanceof를 사용해야 하며
프록시는 실제 데이터를 가지고 있지 않으므로 아무값도 조회가 불가능하므로 멤버변수에 직접 접근하지 않고
접근자 메소드를 사용하여 프록시의 데이터를 조회하도록 하여 비교해야 함 - 상속관계를 프록시로 조회하기 위해 부모 타입으로 조회하게 될 경우
instanceof 연산을 사용할 수 없고 하위 타입으로 다운캐스팅을 할 수 없게 되어 다형성을 다룰 수 없게 됨
이를 해결하기 위해서는 처음부터 자식 타입으로 직접 조회해서 필요한 연산을 사용하여 다형성을 활용하거나
하이버네이트가 제공하는 unProxy() 메소드 기능을 통해 프록시를 벗겨 원본 엔티티를 찾아와 사용하도록 함
또는 특정 기능을 제공하는 별도의 인터페이스를 제공하여 각각의 클래스가 자신에 맞는 메소드 기능을 구현하도록 함
또한 비지터 패턴을 사용해 Visitor와 Visitor를 받아들이는 대상 클래스를 구성하면
프록시 걱정 없이 원본 엔티티에 접근할 수 있지만 이해하기 어려우며 객체 구조가 변경되면 모든 Visistor를 수정해야 함
- 프록시는 원본 엔티티를 상속받아서 만들어지므로 원본 엔티티인지 프록시인지 구분하지 않고 사용할 수 있으며
- 성능 최적화
- JPA로 애플리케이션을 개발할 때 발생하는 다양한 성능 문제
1) N+1 문제
2) 읽기 전용 쿼리의 성능 최적화
3) 배치 처리
4) SQL 쿼리 힌트 사용
5) 트랜잭션을 지원하는 쓰기 지연과 성능 최적화
- JPA로 애플리케이션을 개발할 때 발생하는 다양한 성능 문제
- N+1
- 즉시 로딩을 설정하면 조인을 사용해서 한 번의 SQL로 함께 엔티티를 조회하게 됨
이 후 JPQL을 사용하면 로딩 설정에 대해 신경 쓰지 않고 JPQL만을 사용해서 SQL을 생성하므로
연관된 컬렉션을 즉시 로딩하기 위해 조회된 결과 수 만큼 SQL을 추가로 실행하게 되는 N+1 문제가 발생함 - 지연 로딩을 설정하면 JPQL에서 N+1 문제가 발생하지 않지만
비즈니스 로직에서 실제 컬렉션을 사용할 때 컬렉션을 초기화하는 만큼의 SQL이 실행되는 N+1 문제가 발생함 - 이를 해결하기 위해서는 페치 조인을 사용해 SQL 조인을 사용해 연관된 엔티티를 함께 조회하거나
하이버네이트의 @BatchSize 어노테이션을 사용해 연관된 엔티티를 조회할 때 지정된 size를 설정하여 조회도록 함
또는 하이버네이트의 @Fetch 어노테이션을 사용해 연관된 데이터를 서브 쿼리로 조회하도록 함 - 이런 N+1 문제를 해결하기 위해서는 지연 로딩을 설정하고 성능 최적화가 꼭 필요한 곳에 JPQL 페치 조인을 사용하도록 함
- 즉시 로딩을 설정하면 조인을 사용해서 한 번의 SQL로 함께 엔티티를 조회하게 됨
- 읽기 전용 쿼리의 성능 최적화
- 엔티티가 영속성 컨텍스트에 관리되면 여러 혜택이 많지만
영속성 컨텍스트는 변경 감지를 위해 스냅샷 인스턴스를 보관해야 하므로 더 많은 메모리를 사용해야 하는 단점이 존재함
이를 위해 읽기 전용으로 엔티티를 조회하면 메모리 사용량을 최적화할 수 있음 - 엔티티가 아닌 스칼라 타입으로 모든 필드를 조회할 경우
스칼라 타입은 영속성 컨텍스트가 결과를 관리하지 않으므로 메모리 사용량을 최적화할 수 있음 - 하이버네이트 전용 힌트인 readOnly를 사용하여 엔티티를 읽기 전용으로 조회할 경우
읽기 전용이므로 영속성 컨텍스트는 스냅샷을 보관하지 않아 메모리 사용량을 최적화할 수 있음
하지만 스냅샷이 없어 엔티티를 수정해도 데이터베이스에 반영되지 않음 - 스프링 프레임워크를 사용하면 트랜잭션에 readOnly=true 옵션을 주어 트랜잭션을 읽기 전용 모드로 설정할 수 있어
강제로 플러시를 호출하지 않는 한 플러시할 때 일어나는 스냅샷 비교와 같은 무거운 로직들을 수행하지 않으므로
플러시 호출을 막아서 속도를 최적화할 수 있음 - 트랜잭션 없이 엔티티를 조회하기 위해 트랜잭션 밖에서 읽기를 한다면 플러시가 일어나지 않아 속도가 최적화됨
- 그러므로 성능을 최대한으로 최적화하기 위해서는
플러시 호출을 막아서 작동하지 않도록 해서 속도 성능을 최적화하는 읽기 전용 트랜잭션(또는 트랜잭션 밖에서 읽기)과
엔티티를 읽기 전용으로 조회해서 메모리를 최적화하는 읽기 전용 쿼리 힌트(또는 스칼라 타입으로 조회)를 동시에 사용
- 엔티티가 영속성 컨텍스트에 관리되면 여러 혜택이 많지만
- 배치 처리
- 수백만 건의 데이터를 배치 처리해야 하는 상황에서 엔티티를 계속 조회하면 영속성 컨텍스트에 많은 엔티티가 쌓여
메모리 부족 문제가 발생하므로 적절한 단위로 영속성 컨텍스트를 초기화해야 함 - JPA 등록 배치의 경우 아주 많은 엔티티를 한 번에 등록할 때 영속성 컨텍스트에 엔티티가 계속 쌓이지 않도록
일정 단위마다 영속성 컨텍스트의 엔티티를 데이터베이스에 플러시하고 영속성 컨텍스트를 초기화해야 함 - JPA 수정 배치의 경우 아주 많은 데이터를 조회해서 수정하므로 수많은 데이터를 한 번에 메모리에 올릴 수 없으므로
페이징 처리를 사용해 페이지 단위마다 영속성 컨텍스트를 플러시하고 초기화하거나
커서 기능을 가진 하이버네이트의 scroll을 사용해 엔티티를 하나씩 조회하도록 반복함 - 또한 하이버네이트는 무상태 세션 기능을 제공하므로 영속성 컨텍스트와 2차 캐시를 사용하지 않는 대신
엔티티를 수정하기 위해 update() 메소드를 직접 호출해서 사용할 수 있음
- 수백만 건의 데이터를 배치 처리해야 하는 상황에서 엔티티를 계속 조회하면 영속성 컨텍스트에 많은 엔티티가 쌓여
- SQL 쿼리 힌트 사용
- JPA는 데이터베이스 SQL 힌트를 제공하지 않으므로
하이버네이트를 사용하여 하이버네이트 쿼리가 제공하는 addQueryHint() 메소드를 사용함 - 하이버네이트는 오라클 방언에만 힌트가 적용되어 있으므로
다른 데이터베이스에서 SQL을 사용하려면 각 방언에서 Dialect에 있는 메소드를 오버라이딩해서 기능을 구현해야 함
- JPA는 데이터베이스 SQL 힌트를 제공하지 않으므로
- 트랜잭션을 지원하는 쓰기 지연과 성능 최적화
- 배치를 사용할 때 JPA를 사용하지 않고 SQL을 직접 다루면 SQL을 실행할 때 데이터베이스 테이블 로우에 락을 걸게 되고
비즈니스 로직을 모두 수행하고 커밋을 호출할 때까지 유지하게 되므로 다른 트랜잭션은 이 락이 풀릴 때까지 대기하게 됨 - 하지만 트랜잭션을 지원하는 쓰기 지연과 배치를 활용하면 커밋을 호출할 때 데이터베이스에 SQL 쿼리를 모아 보내고
바로 트랜잭션을 커밋하므로 데이터베이스에 락이 걸리는 시간을 최적화해 애플리케이션 성능을 최적화할 수 있음 - 그러므로 데이터베이스에 락이 걸리는 시간을 최소화해서 동시에 더 많은 트랜잭션을 처리할 수 있음
- 배치를 사용할 때 JPA를 사용하지 않고 SQL을 직접 다루면 SQL을 실행할 때 데이터베이스 테이블 로우에 락을 걸게 되고
트랜잭션과 락, 2차 캐시
- 트랜잭션과 락
- 트랜잭션은 ACID라고 하는 원자성, 일관성, 격리성, 지속성을 보장해야 함
트랜잭션 간에 격리성을 완벽히 보장하기 위해 트랜잭션을 거의 차례대로 실행할 경우 동시성 처리 성능이 매우 나빠짐
이를 위해 ANSI 표준은 트랜잭션의 격리 수준을 4단계로 나누어 정의함 - 트랜잭션 격리 수준에는 4가지가 존재하며 격리 수준이 낮을수록 동시성은 증가하지만 격리 수준에 따른 문제가 발생함
1) DIRTY READ, NON-RETEATABLE READ, PHANTOM READ를 허용하는 READ UNCOMMITED (커밋되지 않은 읽기)
2) NON-RETEATABLE READ, PHANTOM READ를 허용하는 READ COMMITED (커밋된 읽기)
3) PHANTOM READ를 허용하는 REPEATABLE READ (반복 가능한 읽기)
4) 모두 허용하지 않아 가장 엄격한 트랜잭션 격리 수준인 SERIALZABLE (직렬화 기능) - JPA의 영속성 컨텍스트를 적절히 활용하면 READ UNCOMMITED 격리 수준이어도 반복 가능한 읽기가 가능하므로
JPA는 데이터베이스 격리 수준을 READ UNCOMMITED 정도로 가정하고
더 높은 격리 수준이 필요하다면 낙관적 락과 비판적 락 중 하나를 사용하면 됨 - JPA가 제공하는 락은 EntityManager.lock(), EntityManager.find(), EntityManager.refresh(),
Query.setLockMode(), @NamedQuery 위치에 적용할 수 있으며
즉시 락을 걸 수도 있고 필요할 때 락을 걸 수도 있음 - 낙관적 락은 트랜잭션 대부분은 충돌이 발생하지 않는다고 낙관적으로 가정하는 방법으로
데이터베이스가 제공하는 락 기능을 사용하는 것이 아니라 JPA가 제공하는 버전 관리 기능을 사용하므로
트랜잭션을 커밋하기 전까지는 트랜잭션의 충돌을 알 수 없음
낙관적 락을 사용하기 위해서는 엔티티에 버전 관리용 필드를 하나 추가하고 @Version 어노테이션을 적용하며
엔티티를 수정할 때 조회 시점의 버전과 수정 시점의 버전이 다르면 예외가 발생하게 됨
또한 버전 정보를 사용하면 최초 커밋만 인정하기가 적용됨
낙관적 락은 락 옵션 없이 @Version만 있어도 적용되지만 락 옵션을 사용하면 락을 더 세밀하게 제어할 수 있음
1) NONE은 락 옵션을 적용하지 않는 것으로 엔티티에 @Version이 적용된 필드만 있으면 낙관적 락이 적용됨
2) OPTIMISTIC은 엔티티를 수정하지 않고 오직 조회만 하더라도 버전을 체크하게 됨
3) OPTIMISTIC_FORCE_INCREMENT는 강제로 버전을 증가해서 논리적인 단위의 엔티티 묶음을 버전 관리할 수 있음 - 비관적 락은 트랜잭션의 충돌이 발생한다고 가정하고 우선 락을 거는 방법으로 데이터베이스가 제공하는 락 기능을 사용함
비관적 락은 데이터를 수정하는 즉시 트랜잭션 충돌을 감지할 수 있으며 낙관적 락과 달리 버전 정보를 사용하지 않음
비관적 락도 락을 더 세밀하게 제어할 수 있는 락 옵션이 존재함
1) PESSIMISTIC_WRITE는 데이터베이스에 쓰기를 할 때 락을 걸며 비관적 락에서 일반적으로 사용함
2) PESSIMISTIC_READ는 데이터를 반복 읽기만 하고 수정하지 않는 용도로 사용할 때 락을 걸며 잘 사용하지 않음
3) PESSIMISTIC_FORCE_INCREMENT는 비관적 락 중 유일하게 버전 관리를 사용하며 버전 정보를 강제로 증가시킴
또한 비관적 락을 사용하면 락을 획득할 때까지 트랜잭션이 대기하게 되는데,
이를 무한정 기다릴 수 없으므로 타임아웃 시간을 지정할 수 있음
- 트랜잭션은 ACID라고 하는 원자성, 일관성, 격리성, 지속성을 보장해야 함
- 2차 캐시
- 네트워크를 통해 데이터베이스에 접근하는 비용은 애플리케이션 서버에서 내부 메모리에 접근하는 비용보다 훨씬 비쌈
따라서 조회한 데이터를 메모리에 캐시해서 데이터베이스 접근 횟수를 줄이면 애플리케이션 성능을 개선할 수 있음 - 영속성 컨텍스트 내부에는 엔티티를 보관하는 저장소인 1차 캐시가 존재하지만
트랜잭션을 시작하고 종료할 때까지만 1차 캐시가 유효하므로
애플리케이션 전체로 보면 데이터베이스 접근 횟수를 획기적으로 줄이지 못함 - 그러므로 하이버네이트를 포함한 대부분의 JPA 구현체들은 애플리케이션 범위의 캐시인 공유 캐시(2차 캐시)를 지원
- 2차 캐시는 애플리케이션에서 공유하는 캐시를 뜻하며 애플리케이션 범위이므로 애플리케이션을 종료할 때까지 유지됨
그러므로 2차 캐시를 적용하면 엔티티 매니저를 통해 데이터를 조회할 때 우선 2차 캐시에서 찾고
없으면 데이터베이스에서 찾으므로 2차 캐시를 적절히 활용하면 데이터베이스 조회 횟수를 획기적으로 줄일 수 있음 - JPA에서 2차 캐시를 사용하려면 엔티티에 @Cacheable 어노테이션을 사용한 후
persistence.xml에 shared-cache-mode를 설정해서 애플리케이션 전체에 캐시를 어떻게 적용할지 옵션을 결정함
1) ALL은 모든 엔티티를 캐시함
2) NONE은 캐시를 사용하지 않음
3) ENABLE_SELECTIVE는 Cacheable(true)로 설정된 엔티티만 캐시를 적용함
4) DISABLE_SELECTIVE는 모든 엔티티를 캐시하지만 Cacheable(false)로 명시된 엔티티는 캐시하지 않음
5) UNSPECIFIED는 JPA 구현체가 정의한 설정을 따름 - 캐시 조회 모드와 캐시 보관 모드를 사용하면
캐시를 조회하거나 저장할 때 캐시가 아닌 데이터베이스에서 직접 조회하거나 갱신하도록 설정할 수 있음
또한 더 세밀하게 EntityManager.find(), EntityManager.refresh()에 설정도 가능하며 Query.setHint()에도 사용 가능
캐시를 관리하기 위해서는 EntityManagerFactory로부터 캐시 관리 API인 Cache 인터페이스를 제공받을 수 있음 - JPA 캐시 표준은 여러 구현체가 공통 사용하는 부분만 표준화하므로 세밀한 설정을 하려면 구현체에 의존적인 기능을 사용
- 구현체인 하이버네이트에 EHCACHE 사용을 통해 2차 캐시를 적용할 수 있으며
이를 사용하기 위해서는 pom.xml에 hibernate-ehcache 라이브러리를 추가하고
ehcache.xml을 설정 파일로 사용하여 캐시 정책을 정의한 후 persistence.xml에 캐시 정보를 추가하도록 함
하이버네이트가 지원하는 캐시에는 엔티티 캐시, 컬렉션 캐시, 쿼리 캐시 3가지가 존재함
캐시를 위해서는 @Cacheable 어노테이션을 적용한 후
구현체인 하이버네이트와 관련된 더 세밀한 설정을 하기 위해 하이버네이트 전용 어노테이션인 @Cache를 사용함
1) CacheConcurrencyStrategy.NONE은 캐시를 설정하지 않음
2) CacheConcurrencyStrategy.READ_ONLY는읽기전용으로 설정해 삭제는 가능하지만 수정은 불가능함
3) CacheConcurrencyStrategy.NONSTRICT_READ_WRITE는 동시에 같은 엔티티가 수정되면 캐시 데이터를 무효화함
4) CacheConcurrencyStrategy.READ_WRITE는 READ COMMITED를 보장
5) CacheConcurrencyStrategy.TRANSACTIONAL은 컨테이너 환경에서 사용 가능하며 REPEATABLE READ를 보장
6) region은 저장될 캐시 지역을 직접 지정하여 설정할 수 있음
7) include는 연관 객체를 캐시에 포함할지 선택할 수 있음 - 쿼리 캐시는 쿼리와 파라미터 정보를 키로 사용해서 쿼리 결과를 캐시하는 방법으로
쿼리 캐시를 적용하려면 영속성 유닛을 설정해 hibernate.cache.use_query_cache 옵션을 true로 설정한 후
쿼리 캐시를 적용하려는 쿼리마다 org.hibernate.cacheable을 true로 설정하도록 힌트를 주면 됨
쿼리 캐시는 캐시한 데이터 집합을 최신 데이터로 유지하기 위해
쿼리 캐시를 실행한 시간과 사용한 테이블이 변경된 시간을 비교하고
조금이라도 변경이 있으면 데이터베이스에서 데이터를 읽어와서 쿼리 결과를 다시 캐시하게 됨
빈번한 변경이 있는 테이블에 쿼리 캐시를 사용할 경우 오히려 성능이 저하되므로 수정이 거의 읽어나지 않는 테이블에 사용 - 쿼리 캐시나 컬렉션 캐시를 사용할 때 엔티티 캐시를 사용하지 않으면
최악의 상황에 따라 결과 집합 수만큼 SQL이 실행되므로
쿼리 캐시나 컬렉션 캐시를 사용할 때는 결과 대상 엔티티에 꼭 엔티티 캐시를 적용해야 함
- 네트워크를 통해 데이터베이스에 접근하는 비용은 애플리케이션 서버에서 내부 메모리에 접근하는 비용보다 훨씬 비쌈
+) 자세한 자료 설명
'Community > SOLUX' 카테고리의 다른 글
[230104] 2022 2학기 프로젝트 - 7차 회의 (0) | 2023.01.06 |
---|---|
[221228] 2022 2학기 프로젝트 - 6차 회의 (0) | 2023.01.03 |
[221215-221221] 2022 2학기 프로젝트 - 4차 스터디 (0) | 2022.12.21 |
[221208-221214] 2022 2학기 프로젝트 - 3차 스터디 (0) | 2022.12.14 |
[221201-221207] 2022 2학기 프로젝트 - 2차 스터디 (0) | 2022.12.05 |