프로젝트 개요 : Jump to SpringBoot 클론코딩 - 회원제 QnA 게시판 웹
프로젝트 환경 : IntelliJ, SpringBoot, JPA, H2
프로젝트 코드 : https://github.com/smkim9202/sbb
Service
서비스가 필요한 이유?
컨트롤러에서 리포지터리를 직접 호출하지 않고 중간에 서비스(Service)를 두어 데이터를 처리한다.
서비스는 스프링에서 데이터 처리를 위해 작성하는 클래스다.
리포지토리는 DB접근, 도메인 객체(엔티티)를 DB에 저장하고 관리하는 CRUD 위주의 작업을 한다.
서비스에서는 핵심 비즈니스 로직을 구현한다. 회원가입시 중복회원 조회 등 데이터를 조회한후 가공하여 리턴하는 역할을 한다.
모듈화
한 컨트롤러에서 여러개의 리포지터리를 사용하여 데이터를 조회한 후 가공하여 리턴하는 기능을 서비스로 만들어 두면, 컨트롤러에서는 해당 서비스를 호출하여 사용하면 된다. 서비스 없이 컨트롤러에서 직접 구현하려면 해당 기능이 필요로 하는 모든 컨트롤러가 동일한 기능을 중복으로 구현해야한다. 그렇기 때문에 모듈화를 위해서 필요하다.
보안
컨트롤러는 리포지터리 없이 서비스를 통해 DB에서 접근하도록 구현해야 컨트롤러가 해킹을 당해도 DB에는 접근 할 수 없게 된다. DB 보안상 서비스를 거치는 것이 안전하다.
엔티티 객체와 DTO 객체의 변환
엔티티(Entity) 클래스는 DB와 직접 맞닿아 있는 클래스이기 때문에 컨트롤러나 템플릿엔진에 직접 전달하여 사용하는 것은 좋지 않다. 속성을 변경하여 비즈니스적인 요구를 처리 할 때 엔티티를 직접 사용하면 테이블 컬럼이 변경되거나 엉망이 될 수 있기 때문이다.
그렇기 때문에 엔티티 클래스는 컨트롤러에서 사용할수 없게끔 설계하는 것이 좋다. 엔티티 클래스를 대신 사용할 DTO(Data Transfer Object) 클래스가 필요한데 이때 엔티티 객체를 DTO 객체로 변환하는 일을 처리하는 곳이 서비스이다. 서비스는 컨틀롤러와 리포지터리를 연결해주는 중간자적인 입장에서 엔티티 객체와 DTO 객체를 서로 변환하여 양방향에 전달하는 역할을 한다.
DTO(Data Transfer Object)
계층간 데이터 교환을 위한 자바빈즈.
로직을 가지고 있지 않은 순수한 데이터 객체.
객체의 속성과 그 속성의 접근을 위한 getter, setter 메소드만 가지고 있음.
DTO 속성은 엔티티의 속성과 동일하게 작성 할수도 있고, 비즈니스 요건에 따라 자유롭게 추가 또는 삭제하여 변경해서 사용 가능 => 엔티티 속성 중 감추고 싶은 속성은 DTO에서 제외
컨트롤러에서 비즈니스 로직 분리하기
DTO 작성
파일명: QuestionDto.java
package com.mysite.sbb.question;
import com.mysite.sbb.answer.AnswerDto;
import lombok.Getter;
import lombok.Setter;
import java.time.LocalDateTime;
import java.util.List;
@Getter
@Setter
public class QuestionDto {
private Integer id;
private String subject;
private String content;
private LocalDateTime createDate;
private List<AnswerDto> answerList;
}
파일명: AnswerDto.java
package com.mysite.sbb.answer;
import com.mysite.sbb.question.QuestionDto;
import lombok.Getter;
import lombok.Setter;
import java.time.LocalDateTime;
@Getter
@Setter
public class AnswerDto {
private Integer id;
private String content;
private LocalDateTime createDate;
private QuestionDto question;
}
서비스 작성
파일명: QuestionService.java
package com.mysite.sbb.question;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;
@RequiredArgsConstructor //questionRepository 생성자 방식으로 의존성 주입
@Service //클래스를 서비스로 인식하는 애너테이션
public class QuestionService {
private final QuestionRepository questionRepository;
public List<Question> getList(){
List<Question> questionList = this.questionRepository.findAll();
return questionList;
}
}
ModelMapper 라이브러리 설치
파일명: build.gradle
dependencies {
(... 생략 ...)
implementation 'org.modelmapper:modelmapper:3.0.0'
}
버전 정보 명시하는 이유
스프링부트가 내부적으로 관리하는 라이브러리에 포함되면 버전정보를 따로 입력하지 않아도 알아서 가장 궁합이 잘맞는 버전으로 자동으로 선택한다.
스프링부트 내부라이브러리에 포함되지 않으면 버전 정보가 필요하다. ModelMapper는 내부라이브러리가 아니기 때문에 버전 정보를 명시해야 한다.
ModelMapper 스프링 빈 등록
ModelMapper를 서비스에서 경제적으로 사용하려면 스프링의 빈(Bean)으로 등록해야 한다. 필요할 때마다 매번 객체를 생성하여 사용할 수 있지만 비효율적이고 시간이 오래 걸린다는 단점이 있다.
빈으로 등록하기 위해서는 @Configuration이 적용된 클래스가 필요하다.
파일명: src/main/java/패키지명/sbbConfig.java
package com.mysite.sbb;
import org.modelmapper.ModelMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration //스프링의 설정을 담당하는 클래스를 의미하는 애너테이션
public class SbbConfig {
@Bean
//ModelMapper 빈을 생성하는 메서드 애너테이션으로
//@Configuration이 적용된 클래스에서 작성한다
public ModelMapper modelMapper() {
return new ModelMapper();
}
}
서비스 수정
파일명: QuestionService.java
package com.mysite.sbb.question;
import lombok.RequiredArgsConstructor;
import org.modelmapper.ModelMapper;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.stream.Collectors;
@RequiredArgsConstructor //생성자로 의존성 주입
@Service //클래스를 서비스로 인식
public class QuestionService {
private final QuestionRepository questionRepository;
private final ModelMapper modelMapper;
private QuestionDto of(Question question) {
// {} Question 객체를 QuestionDto로 변경
return modelMapper.map(question, QuestionDto.class);
}
public List<QuestionDto> getList(){
List<Question> questionList = this.questionRepository.findAll();
List<QuestionDto> questionDtoList = questionList.stream().map(q -> of(q)).collect(Collectors.toList());
return questionDtoList;
}
}
컨트롤러 수정
파일명: QuestionController.java
package com.mysite.sbb.question;
import com.mysite.sbb.question.Question;
import com.mysite.sbb.question.QuestionRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.List;
@RequiredArgsConstructor //생성자를 통해 의존성 주입
@Controller
public class QuestionController {
//private final QuestionRepository questionRepository;
private final QuestionService questionService;
@RequestMapping("/question/list")
public String list(Model model){//Model 클래스 사용하여 데이터 전달
//리포지토리 직접 사용에서 서비스를 통하는것으로 변경
//Cntroller -> Service -> Repository 구조로 데이터 처리
// List<Question> questionList = this.questionRepository.findAll();
List<QuestionDto> questionList = this.questionService.getList();
model.addAttribute("questionList",questionList);
return "question_list";
}
}
'프로젝트 > clone coding' 카테고리의 다른 글
[Jump to SpringBoot : SBB] 스프링 시큐리티 (0) | 2022.04.26 |
---|---|
[Jump to SpringBoot : SBB] 스프링부트 windows cmd창으로 빌드하고 실행 (0) | 2022.04.21 |
[Jump to SpringBoot : SBB] thymeleaf 템플릿 엔진(+ROOT URL) (0) | 2022.04.19 |
[Jump to SpringBoot : SBB] JpaRepository 상속 받은 Repository 테스트해보기(+JUnit 프레임워크) (0) | 2022.04.19 |
[Jump to SpringBoot : SBB] SpringBoot 기본 요소(Controller, JPA, Entity, Repository) (0) | 2022.04.19 |