공공부하자개발 · 영어 학습 노트
자바
실무 확장Excel · 파일 업로드 · DB 연동0/22 완료
  • 01Excel(XLSX) 구조와 순수 JDK로 읽기/쓰기
  • 02Apache POI로 Excel 업로드/다운로드
  • 03파일 업로드/다운로드 서버 (HttpServer)
  • 04JDBC 기초와 트랜잭션 (H2)
  • 05MyBatis 어노테이션 매퍼로 쿼리 연동
  • 06MyBatis XML 매퍼 · Oracle 방언 · PageHelper · Spring Boot
  • 07REST API 서버와 JSON
  • 08Vue 3 SPA 와 Java 서버 연동
  • 09@Scheduled 운영
  • 10로깅 실무: 레벨·계층, MDC 추적, 예외·성능, 마스킹, 롤링, JSON 로그
  • 11외부 API 연동
  • 12테스트 실무
  • 13암호화·개인정보 보호
  • 14인코딩·한글 실무
  • 15@Transactional 심화
  • 16긴 작업 비동기 처리와 진행률
  • 17SFTP·FTP 파일 연계
  • 18로컬 캐시와 @Cacheable
  • 19메일·알림 발송
  • 20웹 보안 체크리스트
  • 21빌드 도구와 폐쇄망 의존성 반입
  • 22성능 측정: p50·p95·p99, 측정 계층, JFR, JMH 함정, 자체 부하 테스트, 병목 순위
사이트 소개개인정보처리방침연락처
© 2026 공부하자
홈 › 실무 확장 › 18 / 22

로컬 캐시와 @Cacheable

TTL, 크기 제한, 스탬피드 방지, 무효화, Caffeine 설정
섹션 7진행 0 / 22
1왜 배우는가2핵심 원리3코드 예제4응용 변형 예제5자주 하는 실수 (Tip)6연습 문제7정리‹ 이전다음 ›

4. 응용 변형 예제

4.1 Spring @Cacheable 로 코드 테이블 캐싱

java
@Service
public class CodeService {

    @Cacheable(cacheNames = "codes", key = "#groupCode")
    public List<CodeDto> findByGroup(String groupCode) {
        return codeMapper.selectByGroup(groupCode); // MyBatis 조회
    }

    @CacheEvict(cacheNames = "codes", key = "#groupCode")
    public void update(String groupCode, CodeDto dto) {
        codeMapper.update(dto);
    }

    @CacheEvict(cacheNames = "codes", allEntries = true)
    public void reloadAll() {
        // 배치로 코드 테이블을 통째로 갱신한 뒤 호출
    }
}

findByGroup 은 최초 호출만 MyBatis 조회를 타고, 이후 같은 groupCode 는 캐시에서 반환됩니다. update 는 evict 전략으로 해당 그룹만 지우고, reloadAll 은 clear 전략으로 전체를 비웁니다.

4.2 음수 캐시로 존재하지 않는 사용자 반복 조회 막기

java
@Cacheable(cacheNames = "userExists", unless = "#result == null")
public UserDto findUser(String userId) {
    UserDto found = userMapper.selectById(userId);
    return found; // null 이면 Spring 기본 설정상 캐시 안 됨(unless 없어도 동일)
}

// null 도 캐시하고 싶다면 별도 캐시에 Optional 로 감싸 저장한다.
@Cacheable(cacheNames = "userExistsNegative")
public Optional<UserDto> findUserSafe(String userId) {
    return Optional.ofNullable(userMapper.selectById(userId));
}

findUserSafe 는 결과가 없어도 Optional.empty() 라는 "값"을 캐시하므로, 존재하지 않는 ID 를 반복 조회해도 DB 에 매번 가지 않습니다. 이 캐시는 TTL 을 짧게(예: 1분) 두어야 실제로 데이터가 생겼을 때 빨리 반영됩니다.

4.3 self-invocation 함정 재현과 회피

java
@Service
public class ReportService {

    @Autowired
    private ReportService self; // 프록시 주입으로 우회

    public void generate() {
        // this.summary(...) 로 직접 호출하면 캐시가 동작하지 않는다.
        self.summary("2026-09"); // 프록시를 거치므로 캐시가 동작한다.
    }

    @Cacheable(cacheNames = "reportSummary")
    public String summary(String month) {
        return heavyAggregate(month);
    }
}

같은 클래스 안에서 this.summary(...) 로 호출하면 Spring AOP 프록시를 거치지 않아 @Cacheable 이 무시됩니다. 자기 자신을 빈으로 주입받아 self.summary(...) 처럼 호출하면 프록시를 통과해 캐시가 정상 동작합니다.

응용 변형 예제
  • 4.1 Spring @Cacheable 로 코드 테이블 캐싱
  • 4.2 음수 캐시로 존재하지 않는 사용자 반복 조회 막기
  • 4.3 self-invocation 함정 재현과 회피
이전 섹션3 코드 예제4 / 7다음 섹션5 자주 하는 실수 (Tip)