본문 바로가기
스프링/MyBatis

MyBatis 1차 캐시와 2차캐시

by 진믈리 2024. 9. 1.
MyBatis에서는 1차 캐시와 2차캐시 두가지 레벨의 캐시를 제공한다. 1차 캐시는 공식문서에 잘 나와있지 않아 테스트코드를 통해 한번 확인해 보자.

이글에서는 스프링부트 2.7x , java11 환경에서 테스트 함을 미리 알린다.

 

1차캐시 (Local cache)

1차 캐시는 MyBatis 에서 별도의 설정을 하지 않아도 활성화 되어 있다. 1차캐시는 세션 내에서만 유효하고 같은 세션 내에서 동일한 쿼리를 여러번 실행할 경우 데이터 베이스에 다시 접근하지 않고 캐시된 결과를 사용한다. 

 

트랜잭션을 적용한 cacheTest

@Test
@DisplayName("cacheTest")
@Transactional
public void cacheTest1(){

    Member member = Member.builder()
            .username("cacheName")
            .build();

    memberMapper.save(member);


    Member member1 = memberMapper.findById(1L);
    System.out.println("첫 번째 쿼리 실행 완료");

    // 동일한 트랜젝션에서 동일한 쿼리 실행
    Member member2 = memberMapper.findById(1L);
    System.out.println("두 번째 쿼리 실행 완료");
    
    assertThat(member1).isSameAs(member2);
}

@Trabsactional 어노테이션으로 인해 cacheTest1() 테스트 코드에서는 동일한 SqlSession이 적용될것이다. 따라서 첫 번째 쿼리 실행 후 같은 세션에서는 동일한 findById 쿼리를 실행할 때 MyBatis의 1차캐시가 작동하여 두번째 쿼리는 실제 데이터베이스에 접근하지 않고 캐시된 결과를 반환할 것이다.

 

테스트 코드를 실행해 보면 member1을 찾는 첫번째 쿼리 호출만 한번 실행되고 member2를 찾는 쿼리는 실행되지 않고 출력문이 나온것을 확인 할 수 있다. 그리고 테스트 코드도 성공했음으로 member1 과 member2가 같은 객체임을 확인할 수 있다.

 

Session을 활용한 캐시 테스트

@Test
@DisplayName("cacheTest")
public void cacheTest2(){

    Member member = Member.builder()
            .username("cacheName")
            .build();

    memberMapper.save(member);

    try (SqlSession session = sqlSessionTemplate.getSqlSessionFactory().openSession()) {
        MemberMapper mapper = session.getMapper(MemberMapper.class);
        Member member1 = mapper.findById(member.getId());
        System.out.println("첫 번째 쿼리 실행 완료");

        // 동일한 세션에서 동일한 쿼리 실행
        Member member2 = mapper.findById(member.getId());
        System.out.println("두 번째 쿼리 실행 완료");

        // 두 객체가 동일한지 확인
        assertThat(member1).isSameAs(member2);
    }
}

이번에는 Session을 활용한 테스트도 진행해 보았다. 이것도 역시 같은 결과가 나온다.

 

이처럼 1차캐시는 동일한 SqlSession 내에서만 적용되고 즉 같은 SqlSession 내에서 동일한 쿼리를 반복 실행하면 1차 캐시에 저장된 결과가 반환된다. 그리고 SqlSession이 종료되면 1차 캐시는 종료된다.

 

 

2차 캐시

2차캐시는 기본적으로 비활성화 되어 있으며 XML 매핑파일에서 <cache> 요소를 추가하여 활성화 할 수 있다. 

공식문서의 말을 따르면 매핑 구문 파일 내 select 구문의 모든 결과가 캐시된다고 한다.

이처럼 2차 캐시는 MyBatis의 Mapper 수준에서 동작하며 동일한 Mapper에 대해 여러 SqlSession이 공유한다. 즉 같은 Mapper에서 발생하는 SQL 쿼리의 결과를 다른 SqlSession에서도 재 사용할 수 있는것이다.

설정방법

<mapper namespace="com.example.mapper.UserMapper">
    <cache 
        eviction="FIFO" 
        flushInterval="60000" 
        size="512" 
        readOnly="true"/>
</mapper>

이런식으로 <mapper>안에  cache 태그를 사용하면 된다.

많은 프로퍼티가 셋팅된 이 설정은 60초 마다 캐시를 지우는 FIFO 캐시를 생성한다. 이 캐시는 결과 객체 또는 결과 리스트를 512개 까지 저장하고 각 객체는 읽기 전용이다. 캐시 데이터를 변경하는 것을 개별 쓰레드에서 호출자간의 충돌을 야기 할 수 있다.

 

eviction="FIFO" 이 부분에서 사용가능한 캐시 전략은 4가지 이다.

  • LRU - Least Recently Used: 가장 오랜시간 사용하지 않는 객체를 제거
  • FIFO - First In First Out : 캐시에 들어온 순서대로 객체를 제거
  • SOFT - Soft Reference: 가비지 컬렉터의 상태와 강하지 않은 참조(Soft References)의 규칙에 기초하여 객체를 제거
  • WEAK - Weak Reference : 가비지 컬렉터의 상태와 약한 참조(Weak References)의 규칙에 기초하여 점진적으로 객체 제거

디폴트 값은 LRU 이다.

 

테스트 코드를 통해 확인해 보자

@Test
@DisplayName("2차캐시 테스트")
public void secondLevelCacheTest(){
    Member member = Member.builder()
            .username("secondCache").build();

    memberMapper.save(member);

    Member findMember1 = memberMapper.findById(member.getId());
    System.out.println("첫 번째 쿼리 실행 완료");
    System.out.println(findMember1.toString());

    //동일한 쿼리 실행 - 캐시에서 조회
    Member findMember2 = memberMapper.findById(member.getId());
    System.out.println("두 번째 쿼리 실행 완료");
    System.out.println(findMember2.toString());

    System.out.println("member1 == member2 " + findMember1.equals(findMember2));

    // assert로 객체가 동일한지 확인할 수도 있음
    assertThat(findMember1).isSameAs(findMember2);
}