본문 바로가기

SpringBoot

[Do it] 2장 스프링 부트 기본 기능 익히기 (4)

1. 서비스 활용하기 

 

- 대부분의 규모 있는 스프링 부트 프로젝트는 컨트롤러에서 리포지터리를 직접 호출하지 않고 중간에 서비스를 두어 데이터를 처리 

 

1) 서비스가 필요한 이유

 

1-1) 복잡한 코드 모듈화 가능

 

 - A,B 컨트롤러가 C 리포지터리의 메서드 a,b,c를 호출해 사용하는 중복된 코드를 가지게 되는 경우 

 - C 리포지터리의 a,b,c 메서드를 호출하는 기능을 서비스로 만들고 컨트롤러에서 이 서비스를 호출하여 사용할 수 있음 

 

1-2) 엔티티 객체를 DTO 객체로 변환 가능 

 

 - 엔티티 클래스는 컨트롤러에서 사용하지 않도록 설계 

 - 엔티티 클래스를 대신해 사용할 DTO(Data Transfer Obejct) 클래스 필요 

 - 서비스는 컨트롤러와 리포지터리의 중간에서 엔티티 객체와 DTO 객체를 서로 변환하여 양방향에 전달하는 역할 

 

 

2) 서비스 만들기 

 

QuestionService.java

package com.example.demo.question;

import java.util.List;
import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor;


@RequiredArgsConstructor
@Service
public class QuestionService {
	
	private final QuestionRepository questionRepository;
	
	public List<Question> getList() {
		return this.questionRepository.findAll();
	}
}

 

- 스프링 부트는 @Service를 붙은 클래스는 서비스로 인식

- 질문 목록 데이터를 조회하여 리턴하는 getList 메서드를 추가 

 

 

3) 컨트롤러에서 서비스 사용하기 

 

QuestionController.java

 

 

2. 상세 페이지 만들기 

 

1) 질문 목록에 링크 추가하기 

 

- 질문 목록의 제목을 클릭하면 상세화면이 호출되도록 제목에 링크 추가 

 

/templates/question_list.html

<table>
	<thead>
		<tr>
			<th>제목</th>
			<th>작성일시</th>
		</tr>
	</thead>
	<tbody>
		<tr th:each="question:${questionList}">
			<td>
				<a th:href="@{|/question/detail/${question.id}|}"
				th:text="${question.subject}"></a>
			</td>
			<td th:text="${question.createDate}"></td>
		</tr>
	</tbody>
</table>

 

- 제목에 상세 페이지 URL을 연결하기 위해 타임리프의 th:herf 속성을 사용 

- URL은 반드시 @와 { 와 } 문자 사이에 입력해야 함 

- 타임 리프에서는 /question/detail/과 같은 문자열과 ${question.id}와 같은 자바 객체의 값을 더할 때는 반드시 |로 좌우를 감싸 주어야 함 

 

 

2) 상세 페이지 컨트롤러 만들기 

 

QuestionController.java

- 질문 상세 페이지 URL을 매핑 

package com.example.demo.question;

import java.util.List;

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 lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@Controller
public class QuestionController{
	
	private final QuestionService questionService;
	
	@GetMapping("/question/list")
	public String list(Model model) {
		List<Question> questionList = this.questionService.getList();
		model.addAttribute("questionList",questionList);
		return "question_list";
	}
	
	@GetMapping("/question/detail/{id}")
	public String detail(Model model, @PathVariable("id") Integer id) {
		return "question_detail";
	}
}

 

- 변하는 id값을 얻을 때에는 @PathVariable을 사용 

- @GetMapping(value="/question/detail/{id}")에서 사용한 id와 @PathVariable("id")의 매개변수 이름이 이와 같이 동일해야 함 

 

 

templates/question_detail.html

<h1>제목</h1>
<div>내용</div>

 

 

3) 상세 페이지에 서비스 사용하기 

 

- 질문 데이터의 제목(subject)과 내용(content) 출력

 

QuestionService.java

package com.example.demo.question;

import java.util.List;
import java.util.Optional;

import com.example.demo.DataNotFoundException;
import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor;


@RequiredArgsConstructor
@Service
public class QuestionService {
	
	private final QuestionRepository questionRepository;
	
	public List<Question> getList() {
		return this.questionRepository.findAll();
	}
	
	public Question getQuestion(Integer id) {
		Optional<Question> question = this.questionRepository.findById(id);
		if(question.isPresent()) {
			return question.get();
		}else {
			throw new DataNotFoundException("question not found");
		}
		
	}
}

 

- id값으로 질문 데이터를 조회하기 위해서 getQuestion 메서드를 추가 

- 리포지터리 Question 객체는 Optional 객체이므로 if~else 문을 통해 isPresent 메서드로 해당 데이터가 존재하는지 검사하는 과정 필요 

- 만약 id값에 해당하는 질문 데이터가 없을 경우에는 예외 클래스인 DataNotFoundException이 실행되도록 함 

 

 

DataNotFoundException.java

package com.example.demo;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(value=HttpStatus.NOT_FOUND, reason="entity not found")
public class DataNotFoundException extends RuntimeException{
	private static final long serialVersionUID = 1L;
	public DataNotFoundException(String message) {
		super(message);
	}
	
}

 

- 데이터베이스에서 특정 엔티티 또는 데이터를 찾을 수 없을 때 발생시키는 예외 클래스로 만듬 

- 이 예외가 발생하면 스프링 부트는 설정된 HTTP 상태 코드(HttpStatus.NOT_FOUND)와 이유("entity not found")를 포함한 응답을 생성하여 클라이언트에게 반환 

- RuntimeException 클래스를 상속하는 것은 사용자 정의 예외 클래스를 정의하는 방법 중 하나 

 

 

QuestionController.java

...
	@GetMapping("/question/detail/{id}")
	public String detail(Model model, @PathVariable("id") Integer id) {
		Question question = this.questionService.getQuestion(id);
		model.addAttribute("question", question);
		return "question_detail";
	}
}

 

- QuestionService의 getQuestion 메서드를 호출하여 Question 객체를 템플릿에 전달

 

 

4) 상세 페이지 출력하기 

 

templates/question_detail.html

<h1 th:text="${question.subject}"></h1>
<div th:text="${question.content}"></div>