3 분 소요

Spring 공부-3


findBySubjectLike 메서드를 사용할 때 데이터 조회를 위한 조건이 되는 문자열로 sbb%와 같이 %를 적어 주어야 한다. %는 표기하는 위치에 따라 의미가 달라진다. 다음 표를 살펴보자.

|표기 예|표기 위치에 따른 의미| |—|—| |sbb%|’sbb’로 시작하는 문자열| |%sbb|’sbb’로 끝나는 문자열| |%sbb%|’sbb’를 포함하는 문자열| —–QuestionRepository가 findById 메서드를 통해 Question 객체를 조회하고 나면 DB 세션이 끊어지기 때문이다.——

DB 세션이란 스프링 부트 애플리케이션과 데이터베이스 간의 연결을 뜻한다.

그래서 그 이후에 실행되는 q.getAnswerList() 메서드(Question 객체로부터 answer 리스트를 구하는 메서드)는 세션이 종료되어 오류가 발생한다. answerList는 앞서 q 객체를 조회할 때가 아니라 q.getAnswerList() 메서드를 호출하는 시점에 가져오기 때문에 이와 같이 오류가 발생한 것이다.

이렇게 데이터를 필요한 시점에 가져오는 방식을 지연(Lazy) 방식이라고 한다. 이와 반대로 q 객체를 조회할 때 미리 answer 리스트를 모두 가져오는 방식은 즉시(Eager) 방식이라고 한다. @OneToMany@ManyToOne 애너테이션의 옵션으로 fetch=FetchType.LAZY 또는 fetch=FetchType.EAGER처럼 가져오는 방식을 설정할 수 있는데, 따로 지정하지 않고 항상 기본값(디폴트값)을 사용한다.

사실 이 문제는 테스트 코드에서만 발생한다. 실제 서버에서 JPA 프로그램들을 실행할 때는 DB 세션이 종료되지 않아 이와 같은 오류가 발생하지 않는다.

!!!!테스트 코드를 수행할 때 이런 오류를 방지할 수 있는 가장 간단한 방법은 다음과 같이 @Transactional 애너테이션을 사용하는 것이다. @Transactional 애너테이션을 사용하면 메서드가 종료될 때까지 DB 세션이 유지된다.!!!!


서비스가 필요한 이유

서비스란 무엇일까? 서비스(service)는 간단히 말해 스프링에서 데이터 처리를 위해 작성하는 클래스이다. 우리는 그동안 서비스 없이도 웹 프로그램을 동작시키는 데 문제가 없었다. 그런데 굳이 서비스를 사용해야 할까? 서비스가 필요한 이유를 좀 더 자세히 알아보자.

복잡한 코드를 모듈화할 수 있다

예를 들어 A라는 컨트롤러가 어떤 기능을 수행하기 위해 C라는 리포지터리의 메서드 a, b, c를 순서대로 실행해야 한다고 가정해 보자. 그리고 B라는 컨트롤러도 A 컨트롤러와 동일한 기능을 수행해야 한다면 A, B 컨트롤러가 C 리포지터리의 메서드 a, b, c를 호출해 사용하는 중복된 코드를 가지게 된다. 이런 경우 C 리포지터리의 a, b, c 메서드를 호출하는 기능을 서비스로 만들고 컨트롤러에서 이 서비스를 호출하여 사용할 수 있다. 즉, 서비스를 사용하면 이와 같은 모듈화가 가능하다.


엔티티 객체를 DTO 객체로 변환할 수 있다.

우리가 앞에서 작성한 Question, Answer 클래스는 모두 엔티티 클래스이다. 엔티티 클래스는 데이터베이스와 직접 맞닿아 있는 클래스이므로 컨트롤러 또는 타임리프와 같은 템플릿 엔진에 전달해 사용하는 것은 좋지 않다. 왜냐하면 엔티티 객체에는 민감한 데이터가 포함될 수 있는데, 타임리프에서 엔티티 객체를 직접 사용하면 민감한 데이터가 노출될 위험이 있기 때문이다.

이러한 이유로 Question, Answer 같은 엔티티 클래스는 컨트롤러에서 사용하지 않도록 설계하는 것이 좋다. 그래서 Question, Answer를 대신해 사용할 DTO (Data Transfer Object) 클래스가 필요하다. 그리고 Question, Answer 등의 엔티티 객체를 DTO 객체로 변환하는 작업도 필요하다. 그러면 엔티티 객체를 DTO 객체로 변환하는 일은 어디서 처리해야 할까? 이때도 서비스가 필요하다. 서비스는 컨트롤러와 리포지터리의 중간에서 엔티티 객체와 DTO 객체를 서로 변환하여 양방향에 전달하는 역할을 한다.


card 컴포넌트를 비롯하여 질문 상세 템플릿에서 부트스트랩 클래스를 많이 사용했다. 다음 표를 통해 살펴보자.

부트스트랩 클래스 설명
cardcard-bodycard-text card 컴포넌트를 적용하는 클래스들이다.
badge badge 컴포넌트를 적용하는 클래스이다.
form-control 텍스트 창에 form 컴포넌트를 적용하는 클래스이다.
border-bottom 아래 방향 테두리 선을 만드는 클래스이다.
my-3 상하 마진값으로 3을 지정하는 클래스이다.
py-2 상하 패딩값으로 2를 지정하는 클래스이다.
p-2 상하좌우 패딩값으로 2를 지정하는 클래스이다.
d-flex justify-content-end HTML 요소를 오른쪽으로 정렬하는 클래스이다.
bg-light 연회색으로 배경을 지정하는 클래스이다.
text-dark 글자색을 검은색으로 지정하는 클래스이다.
text-start 글자를 왼쪽으로 정렬하는 클래스이다.
btn btn-primary 버튼 컴포넌트를 적용하는 클래스이다.

Spring Boot Validation 라이브러리를 설치하면 다음과 같은 애너테이션을 사용하여 사용자가 입력한 값을 검증할 수 있다.

항목 설명
@Size 문자 길이를 제한한다.
@NotNull Null을 허용하지 않는다.
@NotEmpty Null 또는 빈 문자열(““)을 허용하지 않는다.
@Past 과거 날짜만 입력할 수 있다.
@Future 미래 날짜만 입력할 수 있다.
@FutureOrPresent 미래 또는 오늘 날짜만 입력할 수 있다.
@Max 최댓값 이하의 값만 입력할 수 있도록 제한한다.
@Min 최솟값 이상의 값만 입력할 수 있도록 제한한다.
@Pattern 입력값을 정규식 패턴으로 검증한다.

package com.mysite.sbb.question;

import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.Size;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class QuestionForm {
    @NotEmpty(message="제목은 필수항목입니다.")
    @Size(max=200)
    private String subject;

    @NotEmpty(message="내용은 필수항목입니다.")
    private String content;
}

subject 속성에는 @NotEmpty와 @Size 애너테이션이 적용되었다. @NotEmpty는 해당 값이 Null 또는 빈 문자열(““)을 허용하지 않음을 의미한다. 그리고 여기에 사용한 message는 검증이 실패할 경우 화면에 표시할 오류 메시지이다. @Size(max=200)은 최대 길이가 200 바이트(byte)를 넘으면 안 된다는 의미로, 이와 같이 설정하면 길이가 200 바이트보다 큰 제목이 입력되면 오류가 발생한다. content 속성 역시 @NotEmpty 애너테이션을 적용하여 빈 값을 허용하지 않도록 했다.

폼 클래스는 입력값 검증할 때뿐만 아니라 입력 항목을 바인딩할 때도 사용한다. 즉, question_form.html 템플릿의 입력 항목인 subject와 content가 폼 클래스의 subjectcontent 속성과 바인딩된다. 여기서 바인딩이란 템플릿의 항목과 form 클래스의 속성이 매핑되는 과정을 말한다.

태그:

카테고리:

업데이트:

댓글남기기