점프 투 스프링 TIL 230328

주바나·2023년 3월 28일
0

Spring Boot

목록 보기
10/14
post-thumbnail

ROOT URL


루트 URL은 http://localhost:8080 처럼 도메인명과 포트 뒤에 아무것도 붙이지 않은 URL을 말한다.

redirect:<URL> - URL로 리다이렉트 (리다이렉트는 완전히 새로운 URL로 요청이 된다.)
forward:<URL> - URL로 포워드 (포워드는 기존 요청 값들이 유지된 상태로 URL이 전환된다.)

서비스


  • 서비스로 만들지 않고 컨트롤러에서 구현하려 한다면 해당 기능을 필요로 하는 모든 컨트롤러가 동일한 기능을 중복으로 구현해야 한다.

  • 컨트롤러는 리포지터리 없이 서비스를 통해서만 데이터베이스에 접근하도록 구현하는 것이 보안상 안전하다. 이렇게 하면 어떤 해커가 해킹을 통해 컨트롤러를 제어할 수 있게 되더라도 리포지터리에 직접 접근할 수는 없게 된다.

  • 엔티티 클래스는 데이터베이스와 직접 맞닿아 있는 클래스이기 때문에 컨트롤러나 타임리프 같은 템플릿 엔진에 전달하여 사용하는 것은 좋지 않다.
    엔티티 클래스는 컨트롤러에서 사용할수 없게끔 설계하는 것이 좋다. 그러기 위해서는 Question, Answer 대신 사용할 DTO(Data Transfer Object) 클래스가 필요하다. 그리고 Question, Answer 등의 엔티티 객체를 DTO 객체로 변환하는 작업도 필요하다.
    그러면 엔티티 객체를 DTO 객체로 변환하는 일은 어디서 처리해야 할까? 그렇다. 바로 서비스이다. 서비스는 컨트롤러와 리포지터리의 중간자적인 입장에서 엔티티 객체와 DTO 객체를 서로 변환하여 양방향에 전달하는 역할을 한다.

링크 추가


  • 타임리프에서 링크의 주소는 th:href 속성을 사용한다.
    타임리프에서 th:href 처럼 URL 주소를 나타낼때는 반드시 @{ 문자와 } 문자 사이에 입력해야 한다.

  • 자바 객체의 값을 더할 때는 반드시 다음처럼 |과 | 기호로 좌우를 감싸 주어야 한다.
    타임리프는 문자열을 연결(concatenation)할 때 | 문자를 사용한다.
    <a th:href="@{|/question/detail/${question.id}|}"th:text="${question.subject}"></a>

Exception


@ResponseStatus:Controller나 Exception에 사용하여 status 정보를 설정하여 리턴해 준다.

@PathVariable


@PostMapping("/create/{id}")
    public String createAnswer(Model model, @PathVariable("id") Integer id, @RequestParam String content) {
        Question question = this.questionService.getQuestion(id);
        // TODO: 답변을 저장한다. 
        return String.format("redirect:/question/detail/%s", id);
    }

{id}에 들어가는게 Integer id 임을 PathVariable을 통해서 알려주는거임.

답변개수 표시


#lists.size(question.answerList)}는 답변 개수를 의미한다.
#lists.size(이터러블객체)는 타임리프가 제공하는 유틸리티로 객체의 길이를 반환한다.

템플릿 상속


  • 템플릿 파일들을 모두 표준 HTML 구조로 변경하면 body 엘리먼트 바깥 부분(head 엘리먼트 등)은 모두 같은 내용으로 중복된다. -> layout.html 생성해서 상속시킴

  • layout.html 템플릿을 상속하기 위해 <html layout:decorate="~{layout}"> 처럼 사용했다. 타임리프의 layout:decorate 속성은 템플릿의 레이아웃(부모 템플릿)으로 사용할 템플릿을 설정한다. 속성의 값인 ~{layout}은 layout.html 파일을 의미한다.

  • 글씨체는 css에 넣어줌/네이게이션 바도 모든 페이지에서 공통적으로 보여야함으로 추가함.

Spring Boot Validation


  • implementation 'org.springframework.boot:spring-boot-starter-validation' 의존성 추가

  • 화면에서 전달받은 입력 값 검증을 위해 넣음.
    ( 질문 등록 시 내용이 비어있으면 등록 못하게 하려고)
    항목 설명

    @Size 문자 길이를 제한한다.
    @NotNull Null을 허용하지 않는다.
    @NotEmpty Null 또는 빈 문자열("")을 허용하지 않는다.
    @Past 과거 날짜만 가능
    @Future 미래 날짜만 가능
    @FutureOrPresent 미래 또는 오늘날짜만 가능
    @Max 최대값
    @Min 최소값
    @Pattern 정규식으로 검증

@Valid


 @PostMapping("/create")
    public String questionCreate(@Valid QuestionForm questionForm, BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            return "question_form";
        }
        this.questionService.create(questionForm.getSubject(), questionForm.getContent());
        return "redirect:/question/list";
    }

@Valid 애너테이션을 적용하면 QuestionForm의 @NotEmpty, @Size 등으로 설정한 검증 기능이 동작한다. 그리고 이어지는 BindingResult 매개변수는 @Valid 애너테이션으로 인해 검증이 수행된 결과를 의미하는 객체이다.

BindingResult 매개변수는 항상 @Valid 매개변수 바로 뒤에 위치해야 한다. 만약 2개의 매개변수의 위치가 정확하지 않다면 @Valid만 적용이 되어 입력값 검증 실패 시 400 오류가 발생한다.

오류 메세지 띄우기


 <h5 class="my-3 border-bottom pb-2">질문등록</h5>
    <form th:action="@{/question/create}" th:object="${questionForm}" method="post">
        <div class="alert alert-danger" role="alert" th:if="${#fields.hasAnyErrors()}">
            <div th:each="err : ${#fields.allErrors()}" th:text="${err}" />
        </div>

오류를 표시하기 위해서는 타임리프의 th:object 속성이 반드시 필요하다.
th:object를 사용하여 폼의 속성들이 QuestionForm의 속성들로 구성된다는 점을 타임리프 엔진에 알려줘야 하기 때문이다.

#fields.hasAnyErrors가 true인 경우는 QuestionForm 검증이 실패한 경우이다. QuestionForm에서 검증에 실패한 오류 메시지는 #fields.allErrors()로 구할 수 있다.

페이징


public class QuestionService {

    (... 생략 ...)

    public Page<Question> getList(int page) {
        Pageable pageable = PageRequest.of(page, 10);
        return this.questionRepository.findAll(pageable);
    }

PageRequest.of(page, 10)에서 page는 조회할 페이지의 번호이고 10은 한 페이지에 보여줄 게시물의 갯수를 의미한다. 이렇게 하면 데이터 전체를 조회하지 않고 해당 페이지의 데이터만 조회하도록 쿼리가 변경된다.

페이징-타임리프


이전 페이지가 없으면 비활성화 th:classappend="${!paging.hasPrevious} ? 'disabled'"
다음 페이지가 없으면 비활성화 th:classappend="${!paging.hasNext} ? 'disabled'"
이전 페이지 링크 th:href="@{|?page=${paging.number-1}|}"
다음 페이지 링크 th:href="@{|?page=${paging.number+1}|}"
페이지 리스트 루프 th:each="page: ${#numbers.sequence(0, paging.totalPages-1)}"
현재 페이지와 같으면 active 적용 th:classappend="${page == paging.number} ? 'active'"

페이징-역순으로 조회하기


게시물 list를 가져올 때 역순(desc)로 정렬을 해서 보낸다.

  public Page<Question> getList(int page) {
        List<Sort.Order> sorts = new ArrayList<>();
        sorts.add(Sort.Order.desc("createDate"));
        Pageable pageable = PageRequest.of(page, 10, Sort.by(sorts));
        return this.questionRepository.findAll(pageable);
    }

게시물을 역순으로 조회하기 위해서는 위와 같이 PageRequest.of 메서드의 세번째 파라미터로 Sort 객체를 전달해야 한다.
Sort.Order 객체로 구성된 리스트에 Sort.Order 객체를 추가하고 Sort.by(소트리스트)로 소트 객체를 생성할 수 있다. 작성일시(createDate)를 역순(Desc)으로 조회하려면 Sort.Order.desc("createDate") 같이 작성한다.

만약 작성일시 외에 추가로 정렬조건이 필요할 경우에는 sorts 리스트에 추가하면 된다.

profile
BE -JAVA,Spring boot

0개의 댓글