(JPA+Boot)서비스 구현
스프링 서비스 계층에서 트랜잭션 관리, 캐싱, 스케줄링을 효과적으로 구현하고 Java Config를 활용한 빈 등록 방법을 상세히 설명하는 가이드입니다.
(서비스) Service + Java Config(빈 등록 예)
서비스는 레포지토리 <-> 컨트롤러 이어주는 역할
엔티티에서 비즈니스로직 많이하다 보니 서비스 단에 비즈니스 로직도 별로 없는편
“트랜잭션, 캐시, 스케줄링”을 주로 보자.
서비스 단에서는 인터페이스 잘 사용 안 하긴 함, 아래는 일부러 보여주려고 예시코드 작성
모니터링 비지니스 로직을 위한 “수동 빈 등록” 을 잠시 보자.
-
@Service 는 자동 빈 등록, 이걸 대신하는 수동 빈 등록은? -> Java Config 방식
Java Config 방식의 빈 등록 방법 2가지:
- @Component 로 빈 등록
- @Service, @Repository 처럼 클래스 단에서 자동 빈 등록
- @Configuration 과 @Bean 조합
- @Bean을 메소드 단에 적용하여 자동 빈 등록 -> 메소드 1개면 생략 가능
- @Configuration에 @Component가 포함되어 클래스 단도 자동 빈 등록
- @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() {
}
댓글남기기