(JPA+Boot)서비스 구현

스프링 서비스 계층에서 트랜잭션 관리, 캐싱, 스케줄링을 효과적으로 구현하고 Java Config를 활용한 빈 등록 방법을 상세히 설명하는 가이드입니다.



(서비스) Service + Java Config(빈 등록 예)

서비스는 레포지토리 <-> 컨트롤러 이어주는 역할
엔티티에서 비즈니스로직 많이하다 보니 서비스 단에 비즈니스 로직도 별로 없는편
“트랜잭션, 캐시, 스케줄링”을 주로 보자.

서비스 단에서는 인터페이스 잘 사용 안 하긴 함, 아래는 일부러 보여주려고 예시코드 작성

모니터링 비지니스 로직을 위한 “수동 빈 등록” 을 잠시 보자.

  • @Service 는 자동 빈 등록, 이걸 대신하는 수동 빈 등록은? -> Java Config 방식

    Java Config 방식의 빈 등록 방법 2가지:


    • @Component 로 빈 등록
      • @Service, @Repository 처럼 클래스 단에서 자동 빈 등록
    • @Configuration 과 @Bean 조합
      • @Bean을 메소드 단에 적용하여 자동 빈 등록 -> 메소드 1개면 생략 가능
      • @Configuration에 @Component가 포함되어 클래스 단도 자동 빈 등록
  • 아래 예시코드는 빈 등록 방법 중 “@Configuration 과 @Bean” 조합 방법을 사용

    예시 코드 보기 - 서비스


    TaskService 인터페이스 + TaskServiceV2 클래스(구현체) + TaskConfigV2 클래스(설정-빈 등록)
    TaskServiceV2 를 보면 정말 레포지토리 <-> 컨트롤러 이어주는 역할 하는 느낌이다.

    /**
     * TaskService 인터페이스
     */
    public interface TaskService {
      void join(Task task); // 일정 등록
      Task findOne(Long taskId);
      Task findOneWithMember(Long memberId, Long taskId);
      void remove(Task task);
      void update(Task task, String content, LocalDateTime startTime, LocalDateTime endTime);
      void updateStatus(Task task, Boolean completedStatus, Boolean timerOnOff,
          Long remainTime); // 일정 완료
      void updateAll(List<Task> taskList, String content, LocalDateTime startTime, LocalDateTime endTime);
    }
    //
    /*
    @Timed 사용 시 아래와같은 빌더 작성을 생략가능
    Counter.builder("my.task")
            .tag("class", this.getClass().getName())
            .tag("method", "addTask")
            .description("task")
            .register(registry).increment();
     */
    //TaskServiceV2 클래스 -> 인터페이스 구현체 (@Override 필수)
    @Timed("my.task") // 모니터링
    @Transactional(readOnly = true) // 읽기모드 기본 사용
    @RequiredArgsConstructor // 생성자 주입
    public class TaskServiceV2 implements TaskService {
      private final TaskRepository taskRepository;
      private final MeterRegistry registry;
      @Override
      @Transactional // 쓰기모드 사용 위해
      public void join(Task task) {
        taskRepository.save(task);
        sleep(500);
      }
      @Override
      @Transactional // 쓰기모드 사용 위해
      public void remove(Task task) {
        taskRepository.remove(task);
      }
      @Override
      @Transactional // 더티체킹 - db 적용
      public void update(Task task, String content, LocalDateTime startTime, LocalDateTime endTime) {
        task.updateTask(content, startTime, endTime);
      }
      //...
    }
    //TaskConfigV2 클래스 -> 수동 빈 등록 설정
    @Configuration
    public class TaskConfigV2 {
      // 서비스 사용 위해 빈 등록
      @Bean 
      TaskService taskService(TaskRepository taskRepository, MeterRegistry registry) {
        return new TaskServiceV2(taskRepository, registry);
      }
      // 위에서 사용한 @Timed 사용 위해 빈 등록 -> AOP 사용
      @Bean
      public TimedAspect timedAspect(MeterRegistry registry) {
        return new TimedAspect(registry); 
      }
      // TaskRepository는 이미 @Repository로 빈 등록되어 있어서 생략
    }
    


서비스 단에서 @Transactional(readOnly=true) 전역 사용 추천

  • 트랜잭션 매니저 등록 안했는데, @Transactional 바로 사용가능한 이유는?
    • @Transactional 은 “빈에 반드시 TransactionManager 가 필요”
    • 스프링 부트자동으로 TransactionManager 등 을 “빈에 등록” -> “자동 구성”
  • 쓰기모드 필요할때만 @Transactional 추가 선언 -> readOnly=false 해야해서
  • 중첩(전파) 되고, 피하는것도 된다. 자세한건 “스프링DB 정리” 파트 참고

캐시와 스케줄: @Cacheable(), @Scheduled() -> 자세한건 “리팩토링” 파트 참고

예시 코드 보기 - 캐시와 스케줄


Cacheable, Scheduled, CacheEvict 예시

// value랑 cacheNames는 동일. 둘다 "캐시이름"의미. 하나 생략 가능.
// key는 말 그대로 구분하는 키값
// 만약 설정한 캐시매니저가 따로 있으면 cacheManager = "cacheManager2" 속성 추가
@Cacheable(value = "members", key = "#pageId", cacheNames = "members") // [캐시 없으면 저장] 조회
public List<FindMemberResponseDto> findAllWithPage(int pageId) {
  return memberRepository.findAllWithPage(pageId); //반환값이 캐싱
}
// 캐시에 저장된 값 제거 -> 30분 마다 실행하겠다.
// 초(0-59) 분(0-59) 시간(0-23) 일(1-31) 월(1-12) 요일(0-6) : "00 30 * * * *"
// - 요일? (0: 일, 1: 월, 2:화, 3:수, 4:목, 5:금, 6:토)
@Scheduled(cron = "00 30 * * * *") // 30분 00초 마다 수행
@CacheEvict(value = "members", allEntries = true)
public void initCacheMembers() {
}



댓글남기기