(6) 서비스와 질문 상세 기능 추가

서비스에 대해 알아보고 질문 상세 기능을 추가하자


시작하기 전에

개요 : 질문과 답변을 할 수 있는 게시판 서비스를 스프링부트를 통해 만들어 본다.

학습사이트 : https://wikidocs.net/book/7601

예제 코드 : https://github.com/pahkey/sbb


ROOT URL 변경

package com.mysite.sbb;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class MainController {
	
	@RequestMapping("/sbb")
	@ResponseBody
	public String index() {
		return "안녕하세요 sbb에 오신것을 환영합니다";
	}
	
	// root url 매핑
	@RequestMapping("/")
	public String root() {
		return "redirect:/question/list";
	}
}
  • redirect : URL로 리다이렉트(완전히 새로운 URL로 요청 된다)

서비스

  • 스프링에서 데이터 처리를 위해 작성하는 클래스


서비스가 필요한 이유

현재 작성한 Question, Answer 클래스는 엔티티 클래스이다. 엔티티 클래스는 데이터베이스와 직접 맞닿아 있는 클래스이기 때문에 컨트롤러나 템플릿에 바로 전달하여 사용하는 것은 좋지 않다. 엔티티를 직접 사용할 경우 데이터베이스가 변경될 가능성이 크기때문이다.

이러한 이유로 엔티티 클래스는 컨트롤러에서 사용할수 없게끔 설계하는 것이 좋다. 그러기 위해서는 Question, Answer 대신 사용할 DTO(Data Transfer Object) 클래스가 필요하다. 그리고 엔티티 객체를 DTO 객체로 변환하는 작업도 필요하다.

여기서 엔티티 객체를 DTO로 변환하는 일을 해주는 것이 서비스이다. 서비스는 컨트롤러와 리포지터리의 중간자적인 입장에서 엔티티 객체와 DTO 객체를 서로 변환하여 양방향에 전달하는 역할을 한다.

점프 투 스프링부트에서는 별도의 DTO를 만들지 않고 사용하지만 실제 업무에서는 직접 만들어 사용하기를 권한다고 한다.


서비스 만들기


  • QuestionService 작성

      // 경로 : sbb/src/main/java/com/mysite/sbb/question/QuestionService.java
      package com.mysite.sbb.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();
          }
      }
    
    • QuestionController에서 리포지터리를 사용했던 부분을 옮겨준다


  • QuestionController 수정

      // 경로 : sbb/src/main/java/com/mysite/sbb/question/QuestionController.java
      package com.mysite.sbb.question;
        
      import java.util.List;
        
      import org.springframework.stereotype.Controller;
      import org.springframework.ui.Model;
      import org.springframework.web.bind.annotation.RequestMapping;
        
      import lombok.RequiredArgsConstructor;
        
      @RequiredArgsConstructor
      @Controller
      public class QuestionController {
        	
      	private final QuestionService questionService;
        	
      	@RequestMapping("/question/list")
      	public String list(Model model) {
      		List<Question> questionList = this.questionService.getList();
      		model.addAttribute("questionList", questionList);
      		return "question_list";
      	}
      }
    
    • 이제 QuestionController는 데이터에 직접 접근하지 않고 getList() 메서드를 통해 Service를사용하여 데이터를 처리한다

질문 상세 기능 추가하기


질문 상세 링크 추가하기

  • 질문 제목을 클릭했을 때 질문 상세 화면이 호출되도록 링크를 추가
  • 템플릿 수정

      <!-- 경로 : sbb/src/main/resources/templates/question_list.html -->
      <table>
        <thead>
            <tr>
                <th>제목</th>
                <th>작성일시</th>
            </tr>
       </thead>
        <tbody>
            <tr th:each="question, index : ${questionList}">
                <td>
                    <a th:href="@{|/question/detail/${question.id}|}" th:text="${question.subject}"></a>
                </td>
                <td th:text="${question.createDate}"></td>
            </tr>
        </tbody>
      </table>
    
    • th:href="@{|/question/detail/${question.id}|}"
      • 링크를 추가하는 코드
      • “/question/detail/”과 같은 문자열과 ${[question.id](http://question.id/)}}와 같은 자바 객체의 값을 더할 때는 | 기호로 좌우를 감싸주어야 한다
      • | : 문자열 연결할때 사용 (타임리프)


질문 상세 컨트롤러/템플릿 만들기

  • 링크를 추가해 줬으니 그에 맞는 컨트롤러와 템플릿을 추가하자
  • 컨트롤러 수정

      // 경로 : sbb/src/main/java/com/mysite/sbb/question/QuestionController.java
      package com.mysite.sbb.question;
        
      import java.util.List;
        
      import org.springframework.stereotype.Controller;
      import org.springframework.ui.Model;
      import org.springframework.web.bind.annotation.PathVariable;
      import org.springframework.web.bind.annotation.RequestMapping;
        
      import lombok.RequiredArgsConstructor;
        
      @RequiredArgsConstructor
      @Controller
      public class QuestionController {
        	
      	private final QuestionService questionService;
        	
      	// 질문 페이지 매핑
      	@RequestMapping("/question/list")
      	public String list(Model model) {
      		List<Question> questionList = this.questionService.getList();
      		model.addAttribute("questionList", questionList);
      		return "question_list";
      	}
        	
      	// 질문 상세 페이지 매핑
      	@RequestMapping(value = "/question/detail/{id}")
      	public String detail(Model model, @PathVariable("id") Integer id) {
      		return "question_detail";
      	}
      }
    
    • id는 질문마다 가지고있는 고유번호이기때문에, 값이 수시로 변한다.
    • 이러한 값을 얻을 때 사용하는 에너테이션 : @PathVariable


  • 템플릿 추가

      <!-- 경로 : sbb/src/main/resources/templates/question_detail.html -->
      <h1 th:text="${question.subject}"></h1>
      <div th:text="${question.content}"></div>
    
    • 당장은 데이터를 전달하는 방법을 정해놓지 않았기 때문에 에러가 난다


실제 제목과 내용을 출력하기


  • QuestionService 수정

      package com.mysite.sbb.question;
        
      import java.util.List;
      import java.util.Optional;
        
      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();
          }
            
          // id값으로 Question 데이터를 조회하는 메서드
          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값에 해당하는 Question 데이터가 없을 경우 DataNotFoundException 예외처리를 발생시켜 데이터가 없다는 것을 알림
    • DataNotFoundException 클래스가 없으므로 컴파일은 오류


  • DataNotFoundException 클래스 작성

      // 경로 : sbb/src/main/java/com/mysite/sbb/DataNotFoundException.java
      package com.mysite.sbb;
        
      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);
      	}
      }
    
    • RuntimeException을 상속
    • DataNotFoundException 발생시 @ResponseStatus 애너테이션에 의해 404 오류 발생


  • QuestionController 수정
// 경로 : sbb/src/main/java/com/mysite/sbb/question/QuestionController.java
package com.mysite.sbb.question;

import java.util.List;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@Controller
public class QuestionController {
	
	private final QuestionService questionService;
	
	// 질문 페이지 매핑
	@RequestMapping("/question/list")
	public String list(Model model) {
		List<Question> questionList = this.questionService.getList();
		model.addAttribute("questionList", questionList);
		return "question_list";
	}
	
	// 질문 상세 페이지 매핑
	@RequestMapping(value = "/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";
	}

}


결과 확인

  • 질문 상세 확인

    1

    • 정상적으로 제목과 내용이 호출


  • DataNotFoundException 확인

    2

    • 질문id : 10597 = 없는 질문
    • 따라서 DataNotFoundException 호출



Leave a comment