서비스가 필요한 이유
모듈화
예를들어 어떤 컨트롤러가 여러개의 리포지터리를 사용하여 데이터를 조회한후 가공하여 리턴한다고 가정해 보자. 이러한 기능을 서비스로 만들어 두면 컨트롤러에서는 해당 서비스를 호출하여 사용하면 된다. 하지만 서비스로 만들지 않고 컨트롤러에서 구현하려 한다면 해당 기능을 필요로 하는 모든 컨트롤러가 동일한 기능을 중복으로 구현해야 한다. 이러한 이유로 서비스는 모듈화를 위해서 필요하다.
보안
컨트롤러는 리포지터리 없이 서비스를 통해서만 데이터베이스에 접근하도록 구현하는 것이 보안상 안전하다. 이렇게 하면 어떤 해커가 해킹을 통해 컨트롤러를 제어할 수 있게 되더라도 리포지터리에 직접 접근할 수는 없게 된다.
엔티티 객체와 DTO 객체의 변환
우리가 작성한 Question, Answer 클래스는 엔티티(Entity) 클래스이다. 엔티티 클래스는 데이터베이스와 직접 맞닿아 있는 클래스이기 때문에 컨트롤러나 타임리프 같은 템플릿 엔진에 전달하여 사용하는 것은 좋지 않다. 컨트롤러나 타임리프에서 사용하는 데이터 객체는 속성을 변경하여 비즈니스적인 요구를 처리해야 하는 경우가 많은데 엔티티를 직접 사용하여 속성을 변경한다면 테이블 컬럼이 변경되어 엉망이 될수도 있기 때문이다.
이러한 이유로 Question, Answer 같은 엔티티 클래스는 컨트롤러에서 사용할수 없게끔 설계하는 것이 좋다. 그러기 위해서는 Question, Answer 대신 사용할 DTO(Data Transfer Object) 클래스가 필요하다. 그리고 Question, Answer 등의 엔티티 객체를 DTO 객체로 변환하는 작업도 필요하다. 그러면 엔티티 객체를 DTO 객체로 변환하는 일은 어디서 처리해야 할까? 그렇다. 바로 서비스이다. 서비스는 컨트롤러와 리포지터리의 중간자적인 입장에서 엔티티 객체와 DTO 객체를 서로 변환하여 양방향에 전달하는 역할을 한다.
package com.mysite.sbb.question;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class QuestionService {
@Autowired
private QuestionRepository qRepo;
public List<Question> getList(){
return this.qRepo.findAll(); //모든 질문 리스트
}
}
QuestionController- 수정
package com.mysite.sbb.question;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class QuestionController {
@Autowired
private QuestionService qService;
@GetMapping("/question/list")
public String list(Model model) {
List<Question> qList = this.qService.getList();
model.addAttribute("qList", qList);
return "question_list";
}
}
테스트하기 이상없음
컨트롤러 : 서비스객체 주입해서 getList() 메소드 호출
서비스 : 리포지토리 주입해서 findAll() 메소드 호출
리포지토리 : JPA 하이버네이트 에서 자동으로 구현된 findAll()메소드로 DB에서 질문데이터들을 가져옴
html 수정
제목을 누르면 해당 글의 상세보기로 이동할거임
th:href
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>질문 리스트</title>
</head>
<body>
<h2>헬로우 타임리프</h2>
<table>
<thead>
<tr>
<th>제목</th>
<th>작성일시</th>
</tr>
</thead>
<tbody>
<tr th:each="question : ${qList}">
<td>
<!-- localhost8080/contextpath까지 적용 -->
<a
th:href="@{/question/detail/__${question.id}__}"
th:text="${question.subject}"
></a>
</td>
<td th:text="${question.createDate}"></td>
</tr>
</tbody>
</table>
</body>
</html>
클릭했을 경우 question id 들어감
확인해보니 알맞게 2 들어간 것 확인됨
QuestionController에서 detail 페이지 추가
//상세보기 페이지
@GetMapping("/question/detail/{id}")
public String detail(Model model ,@PathVariable("id") int id) {
return "question_detail";
}
테스트용
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>질문 디테일</title>
</head>
<body>
<h1>제목</h1>
<div>내용</div>
</body>
</html>
QuestionService
package com.mysite.sbb.question;
import java.util.List;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class QuestionService {
@Autowired
private QuestionRepository qRepo;
public List<Question> getList(){
return this.qRepo.findAll(); //모든 질문 리스트
}
public Question getQuestion(int id) {
Optional<Question> question = qRepo.findById(id);
if(question.isPresent()) {
return question.get();
}else {
//새 에러를 실행
throw new DataNotFoundException("question not found");
}
}
}
DataNotFoundException
런타임예외 DataNotFoundException 만들어서 id로 질문을 찾지 못한다면 404 에러 발생
package com.mysite.sbb.question;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
//404 Not Found code
@ResponseStatus(value = HttpStatus.NOT_FOUND)
public class DataNotFoundException extends RuntimeException {
private static final long serialVersionUID = 1L;
public DataNotFoundException(String message) {
super(message);
}
}
QuestionController
//상세보기 페이지
@GetMapping("/question/detail/{id}")
public String detail(Model model ,@PathVariable("id") int id) {
Question question = qService.getQuestion(id);
model.addAttribute("question", question);
return "question_detail";
}
question_detail
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>질문 디테일</title>
</head>
<body>
<h1 th:text="${question.subject}"></h1>
<div th:text="${question.content}"></div>
</body>
</html>
테스트하기
만약 없는 아이디로 검색하면?
컨트롤러에 requestMapping추가로 좀 더 경로 단순하게 수정함
package com.mysite.sbb.question;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/question")
public class QuestionController {
@Autowired
private QuestionService qService;
@GetMapping("/list")
public String list(Model model) {
List<Question> qList = this.qService.getList();
model.addAttribute("qList", qList);
return "question_list";
}
//상세보기 페이지
@GetMapping("/detail/{id}")
public String detail(Model model ,@PathVariable("id") int id) {
Question question = qService.getQuestion(id);
model.addAttribute("question", question);
return "question_detail";
}
}
'BACKEND > SpringBoot' 카테고리의 다른 글
스태틱 디렉터리와 스타일시트 (0) | 2023.11.09 |
---|---|
답변 등록하기 (0) | 2023.11.09 |
Question 컨트롤러 생성 (0) | 2023.11.08 |
패키지 도메인 별 분리하기 (0) | 2023.11.08 |
답변 AnswerRepository 생성 및 데이터 저장 ,검색 (0) | 2023.11.08 |