Group Study (2022-2023)/Spring 입문

2주차 레퍼런스

민휘 2023. 1. 12. 18:19

1주차 레퍼런스 문서와 멤버분들이 추가 공부한 내용을 스크랩했습니다.

 

✅ JPA

💡 교재 78p ~ 84p

여러분, 1주차 레퍼런스에서 제가 스프링은 객체 지향 프로그래밍 사상을 위해 태어난 프레임워크를 말씀 드린 것 기억하시나요? JPA도 그와 같은 맥락으로 존재하는 기술입니다. JPA는 애플리케이션에서 데이터베이스를 다루는 기술이면서 코드를 객체 지향적으로 작성할 수 있도록 돕는 기술이에요. 데이터베이스를 다루는 기술이라 함은 서버 애플리케이션이 데이터베이스 서버에 SQL을 통해 데이터 보관과 관리를 요청하는 기술이라고 생각하시면 됩니다.

JPA를 사용하기 전에는 MyBatis와 같은 SQL Mapper를 사용했어요. MyBatis를 사용한 코드를 한번 볼까요?

(저는 예전에 자바 웹 개발을 배울 때 MyBatis로 프로젝트를 해봤는데요.. 정말 끔찍합니다 다시는 돌아가고 싶지 않아요)

코드를 보면 메소드 하나에서 DB Connection을 위한 빈 주입, 실행할 SQL 작성, SQL 실행, (여기엔 없지만 SQL 파라미터 매핑), SQL 실행 결과를 VO에 담는 과정을 모두 처리하고 있어요. 객체 지향 원칙의 SRP를 위반하고 있는게 우선 눈에 띄네요.

그리고 SQL을 직접 문자열로 타이핑한게 보이는데요. 이 SQL은 아주 간단하기 때문에 그리 어려워 보이지 않습니다. 하지만 실제로 개발을 해보면 저렇게 간단한 SQL만 사용하진 않아요. 여러 테이블을 조인하거나, 가져와야 하는 컬럼이 몇십개가 넘으면 아주 복잡한 SQL을 문자열로 작성해야 해요. 심지어 문자열로 쓰는거라 컴파일 시점에 SQL 오류를 잡아주지 못해요. 테이블이 수십, 수백개가 넘어가면 이러한 SQL을 무수히 많이 써야하고, 유지 보수가 어려워지며 결국은 끔찍한 코드로 변합니다.

가장 큰 문제는 SQL Mapper를 사용하면 스프링을 사용하다고 한들 코드를 객체 지향적으로 유지할 수 없다는 것입니다. 애플리케이션에서 SQL을 작성하는 비중이 커지다보니, 내가 작성한 자바 코드는 점점 SQL과 데이터베이스에 맞춰 변하게 돼요. 데이터베이스는 객체에 초점을 맞춘 스프링과 달리 데이터에 초점을 맞추고 있습니다. 클래스는 다른 객체를 필드로 포함할 수 있는 반면, 데이터베이스의 튜플은 해당 데이터베이스가 제공하는 기본 타입만 저장할 수 있는 것이 그 차이점이죠. 아무리 클래스를 아름답게 설계하더라도 데이터 전송 계층으로만 가면 객체로 잘 숨겨졌던 클래스가 기본 자료형 필드로 풀어져 버립니다. 이러면 객체 지향을 지켜 설계한 의미가 없어지겠죠?

이러한 문제들을 해결하기 위해 나온 것이 바로 JPA랍니다. JPA는 객체 지향과 데이터 지향이라는 서로 다른 패러다임 불일치를 해결해주는 어댑터 같은 존재입니다. JPA는 관계형 데이터베이스에 맞게 SQL을 대신 생성해서 실행해주기 때문에, 개발자는 객체 지향적인 코드를 표현하면서도 더 이상 SQL을 작성하는 것 때문에 스트레스 받지 않아도 됩니다.

우리가 사용할 Spring Data JPA는 Spring 진영에서 사용하는 JPA 기술입니다. 사실 JPA는 앞에서 설명한 패러다임 불일치를 해결해주는 기술의 인터페이스(명세서)이고, 이를 구현한 구현체들이 있어요. 대표적으로 Hibernate가 JPA 구현체 중 하나입니다. 개발자가 원한다면 Hibernate 외에 다른 구현체를 사용해도 됩니다. 그리고 이 Hibernate를 좀더 사용하기 편하게 추상화시킨 것이 바로 Spring Data JPA입니다. 교재에 나오는데 Spring Data Jpa는 저장소를 교체할 때 정말 편합니다. 개발 환경에서는 인메모리 DB인 h2를, 테스트나 실제 운영 환경에서는 Maria DB를 사용하고 싶으면 설정 파일에서 코드 몇줄로 바꿀 수 있어요. Spring Data JPA가 없었더라면 개발 환경을 하나 하나 세팅하고 있어야 할겁니다. 따봉 Spring Data JPA야 고마워!

JPA는 제가 개인적으로 좋아하는 주제라 이렇게 길게 설명을 해봤는데요, JPA 공부를 시작해보고 싶으신 분들께 다음과 같은 레퍼런스를 드립니다.

김영한 강사님의 무료 JPA 강의

토크ON 41차. JPA 프로그래밍 기본기 다지기 | T아카데미

 

토크ON 41차. JPA 프로그래밍 기본기 다지기 | T아카데미

T아카데미 온라인 강의- [토크ON세미나] JPA 프로그래밍 기본기 다지기 (총8강) https://tacademy.skplanet.com/live/player/onlineLectureDetail.action?seq=149 [과정 소개] 마흔 한번째 세미나 주제는 ‘JPA(Java...

www.youtube.com

유튜브에 올라온 JPA 강의인데요, 핵심적인 내용이 거의 다 들어있으니 시간 내서 들어보는걸 추천해요. 저는 이동 시간에 2배속으로 들었답니다 ㅎㅎ

김영한 - <자바 ORM 표준 JPA 프로그래밍:스프링 데이터 예제 프로젝트로 배우는 전자정부 표준 데이터베이스 프레임>

Hibernate부터 시작해서 Spring Data JPA까지 개선해나가는 교재입니다. 양이 엄청 많기 때문에 방학 중에 스터디를 만들어서 공부해보세요!

 

https://www.google.com/url?cad=rja&cd=&esrc=s&q=&rct=j&sa=t&source=web&uact=8&url=https%3A%2F%2Fwww.aladin.co.kr%2Fshop%2Fwproduct.aspx%3FItemId%3D241970388&usg=AOvVaw0q5lFCorn9_RLfSbTXFWt_&ved=2ahUKEwj5nMuvqcH8AhWutlYBHWbUAjAQFnoECA8QAQ

 

www.google.com

 

✅ Spring 웹 계층

교재에서 Spring의 웹 계층은 Web, Service, Repository 계층, 도메인 모델과 Dto로 나뉜다고 설명하고 있습니다. 실제로 Spring에서 API 개발을 하면 (1) 도메인 엔티티 만들고 (2) Repository 만들고 (3) Service 코드 쓰고 (4) Controller 쓰고 (5) Service와 Controller 사이에 데이터를 넘겨줄 Dto 클래스를 작성합니다. 게시글 등록 api를 만들어보시면 무슨 말인지 바로 아실 수 있을거에요.

이 책에서는 도메인과 서비스 분리를 강조하고 있는데요. 객체 지향이 무엇인지 모르고 코드를 짜면 서비스 계층에 모든 비즈니스 로직이 몰리는 것을 어렵지 않게 확인할 수 있습니다(제가 그랬어요) 이렇게 비즈니스 로직이 각각의 서비스 클래스에서 작성되면 코드의 중복이 발생하고, 각 객체가 이 메소드에서 무슨 일을 하는지 파악하기 어려워집니다.

제가 저번주에 슬랙에서 공유 드린 객체 지향 시리즈를 읽어보신 분들은 아실텐데요, 객체 지향의 초기 의도는 캡슐화와 메시징입니다. 교재에 나온 도메인 예시 코드가 객체 지향을 잘 지키고 있는 코드라고 할 수 있어요. 주문 도메인은 주문 객체가 하는 일을 내부 메소드로 정의해 메소드 사용 창구만 제공하고 있고, 서비스에서는 도메인의 메소드만 호출해서 순서와 트랜잭션 관리를 하고 있어요. 아름다운 코드입니다.. 물론 처음 스프링을 사용하는 분들이 이런 것까지 생각하면서 코드를 짜기는 어려워요. 좋은 코드를 많이 보면서 감각을 익히는게 우선이라고 생각합니다. (저도 아직.. 이런 코드 못 짜요..)

창시자 앨런 케이가 말하는, 객체 지향 프로그래밍의 본질

 

창시자 앨런 케이가 말하는, 객체 지향 프로그래밍의 본질

앨런 케이는 '객체 지향 프로그래밍'이라는 네이밍을 잘못 지었다고 인정했다.

velog.io

DDD(Domain Driven Design)

 

DDD(Domain Driven Design) - Incheol's TECH BLOG

 

incheol-jung.gitbook.io

 

✅ 패키지 구조

교재에서는 패키지를 계층별로 나누고 있어요. domain, service, web, config와 같은 패키지 구조를 가지고 있습니다. 이 방식을 layer 구조라고 통상적으로 불러요. 도메인 모델 위주 개발에 적합합니다. 패키지간 중복 코드 발생 가능성이 적어진다는 장점이 있지만, 기능 별로 코드를 찾아보기 불편하다는 단점이 있어요. 예를 들어 회원과 관련된 코드를 수정하려고 할 때, 패키지를 각각 열어서 MemberService, MemberController, Member Entity, Member Dto를 확인해야 하는 불편함이 있습니다.

layer 방식과 대조되는 패키지 구조가 module 구조인데요, 이 방법은 모듈 별로 관련 계층 클래스를 하나의 패키지에서 관리하는 방법입니다. member와 관련된 패키지가 하나 있고, 그 밑에 MemberService, MemberController, Member Entity, Member Dto가 모두 존재하는 방법이에요. 모듈 단위로 분리할 수 있다는 장점이 있지만, 중복 코드나 순환 참조 등 코드 상의 이슈가 발생할 가능성이 있습니다. 프로젝트 규모가 커지면 어느 하나의 모듈로 정하기 애매한 클래스도 생기고요.

패키지 구조는 정해진 정답은 없습니다. 각자 프로젝트의 상황에 맞게 선택하는 것이 좋은 방법이에요.

패키지 구조는 어떻게 구성하는 것이 좋을까?

 

패키지 구조는 어떻게 구성하는 것이 좋을까?

목차 패키지 구조별 장단점 요약 나의 경험을 바탕으로 패키지 구조에 대한 생각 정리 참고 자료 패키지 구조는 어떻게 가져가는 것이 좋을까?에 대해서 걸어둔 링크의 글을 먼저 확인해보고,

joanne.tistory.com

 

✅ 생성자 주입

교재의 코드를 보면 private final 클래스 변수; 선언과 @RequiredArgsConstructor로 빈을 주입받는 것을 볼 수 있어요.

이 부분은 스프링의 핵심 요소인 의존성 주입(DI)과 관련된 내용인데요, 빈이 무엇이고 스프링이 빈을 어떻게 관리하는지는 이 링크를 참고해 개념을 파악해보시기 바랍니다. DI는 객체 지향 원칙인 SOLID 중 OCP를 지키기 위한 방법이에요. 처음 접하시는 분들은 이게 뭐야.. 이게 뭔 소리야.. 라는 반응이 나올 수 있는데요. 지극히 정상입니다! 추상적인 개념이라 직관적으로 이해되지 않을거에요. 다른 자료들도 찾아보면서 반복해서 이 개념에 노출되는 것을 추천해요.

스프링이 빈을 주입하는 방법에는 @AutoWired, 수정자, 생성자를 이용하는 방법이 있는데요. 권장하는 방법은 생성자 주입입니다. 이 교재의 코드에서도 롬복 어노테이션으로 필요한 변수만 생성자로 만들어주고 있죠. 세 가지 방법의 차이는 이 링크를 참고하세요.

교재의 PostsService를 살펴보면 다음과 같은데요.

**@RequiredArgsConstructor**
@Service
public class PostsService {
    **private final PostsRepository postsRepository;**

    @Transactional
    public Long save(PostsSaveRequestDto requestDto) {
        return postsRepository.save(requestDto.toEntity()).getId();
    }
}

@RequiredArgsConstructor을 사용하지 않고 자바 코드를 직접 쓴다면 다음과 같습니다.

@Service
public class PostsService {
    **private PostsRepository postsRepository;

        @Autowired
        public PostsService(PostsRepository postsRepository){
            this.postsRepository = postsRepository;
        }**

    @Transactional
    public Long save(PostsSaveRequestDto requestDto) {
        return postsRepository.save(requestDto.toEntity()).getId();
    }
}

@RequiredArgsConstructor는 final 혹은 @NotNull이 붙은 필드의 생성자를 자동으로 만들어주는 어노테이션입니다. 스프링의 의존성 주입과 함께 사용하면 주입 받을 빈이 추가될 때마다 생성자에 매개변수를 추가하는 번거로움을 줄일 수 있어요.

이 이슈에서 의존성 주입을 다루었는데요, 스프링을 사용하면서 모르면 큰일 나는 개념이지만 처음 접하시는 분들은 이해하지 못했다고 너무 스트레스 받지 않으셔도 됩니다. 몰라도 일단 코드는 쓸 수 있거든요! 스프링으로 개발을 하다보면 어느 순간 내가 개념이 부족하다는 느낌을 받으실겁니다. 그때 공부해도 괜찮아요.

[Spring] 스프링 컨테이너와 빈이란?

 

[Spring] 스프링 컨테이너와 빈이란?

안녕하세요? 제이온입니다. 오늘은 스프링 컨테이너와 빈에 대해서 알아 보겠습니다. 스프링 컨테이너란? 스프링 컨테이너는 자바 객체의 생명 주기를 관리하며, 생성된 자바 객체들에게 추가

steady-coding.tistory.com

스프링 - 생성자 주입을 사용해야 하는 이유, 필드인젝션이 좋지 않은 이유

 

스프링 - 생성자 주입을 사용해야 하는 이유, 필드인젝션이 좋지 않은 이유

개요 Dependency Injection (의존관계 주입) 이란 Setter Based Injection (수정자를 통한 주입) Constructor based Injection (생성자를 통한 주입) 스프링에서 사용할 수 있는 DI 방법 세가지 생성자 주입을 이용한 순

yaboong.github.io

 

✅ @Validator

교재에서 PostsSaveRequestDto를 정의하고 PostsController에서 매개변수로 Dto 객체에 값을 받고 있어요. 클라이언트가 보낸 api 요청을 보고 이를 처리해줄 컨트롤러를 찾고, Http 요청의 헤더와 바디를 살펴보고 매개변수의 객체에 값을 매핑해줘요. 너무 편하죠! 이게 어떻게 가능한지 궁금하신 분들은 이 링크를 읽어보세요.

저희는 조금 더 나아가, HTTP 요청 정보를 매개변수의 필드에 담을 때 값을 체크해주는 @Validator을 알아볼거에요. 107p의 Dto 코드에서 String title를 받는 부분이 보이시나요? 비즈니스 요구사항에 title은 빈값(””)이면 안된다는 규칙이 추가되었다고 생각해봅시다. 해당 요구사항을 처리하려면 Controller 부분에서 매개변수를 받은 후 title이 빈값인지 조건문으로 비교해서 예외 처리를 해주어야 합니다. 만약 Dto가 여러 곳에서 쓰였다면, 해당 Dto가 사용된 모든 부분에 조건문으로 처리를 해주어야 해요. 끔찍한 상황입니다! 이럴 때 Dto 클래스에 값의 조건을 명시해주면 Http 요청의 데이터를 매개변수에 담을 때 스프링이 데이터 검사를 해줍니다. 이렇게 쓸 수 있어요.

우선 build.gradle에 빈 검증기를 주입받을 수 있는 의존성을 추가합니다.

// 객체 유효성 검증
implementation 'org.springframework.boot:spring-boot-starter-validation'

Dto에 @NotEmpty 어노테이션을 추가합니다. 매핑 과정에서 title 값이 빈값이면 NotEmpty 예외가 발생합니다. message를 사용하여 오류 메시지를 따로 지정해줄 수 있습니다.

public class PostsSaveRequestDto {
    @NotEmpty(message = "Post의 title은 null일 수 없습니다.")
    private String title;

Dto를 사용하는 Controller에 @Valid 어노테이션을 추가하여 유효성 검증을 실행합니다.

@PostMapping("/api/v1/posts")
public Long save(@RequestBody @Valid PostsSaveRequestDto requestDto) {
    return postsService.save(requestDto);
}

코드를 작성했으니 테스트를 돌려볼게요! Spring Validator Test로 구글링해서 많이 나오는 코드를 고쳐서 써봤습니다.

@Slf4j
@SpringBootTest
public class PostsValidationTest {

    private static ValidatorFactory validatorFactory;
    private static Validator validator;

    @BeforeAll
    public static void init() {
        validatorFactory = Validation.buildDefaultValidatorFactory();
        validator = validatorFactory.getValidator();
    }

    @AfterAll
    public static void close() {
        validatorFactory.close();
    }

    @Test
    public void Posts등록_title빈값이면_실패한다() {
        String emptyTitle = "";
        String content = "content";
        String author = "me";

        PostsSaveRequestDto requestDto = PostsSaveRequestDto.builder()
                .title(emptyTitle)
                .content(content)
                .author(author)
                .build();

        Set<ConstraintViolation<PostsSaveRequestDto>> constraintViolations = validator.validate(requestDto);
        assertThat(constraintViolations.size()).isEqualTo(1);
    }

}

title이 empty기 때문에 PostsSaveRequestDto를 검증하는 과정에서 한 건의 ConstraintViolation가 발생한 것을 확인할 수 있습니다.

Spring Request Body Binding 파헤쳐보기

[Spring] @Valid와 @Validated를 이용한 유효성 검증의 동작 원리 및 사용법 예시 - (1/2)

Spring에서 Custom validation 테스트하기

✅ Jpa 테스트 어노테이션

테스트 코드에서 Spring Data Jpa를 사용하기 위해 SpringBoot를 띄워 테스트를 하고 있습니다. Spring 계층에서 단위 테스트를 하려면 @SpringBootTest 어노테이션을 사용해야 합니다.

그리고 TestRestTemplate이라는 클래스는 처음 보는데요. TestRestTemplate은 서블릿 컨테이너를 직접 실행하여 테스트를 합니다. 사실 테스트 코드에서는 서블릿을 직접 실행하면 시간이 오래 걸리고 테스트 한번 할때 사용되는 자원이 많아 Mock을 사용합니다. 그럼에도 TestRestTemplate가 있는 것은 TestRestTemplate가 REST 방식으로 개발한 API의 Test를 최적화 하기 위해 만들어진 클래스이기 때문인데요, HTTP 요청 후 데이터를 응답 받을 수 있는 템플릿 객체를 제공합니다. 교재의 코드에서도 responseEntity를 받아 응답 코드와 바디를 꺼내서 비교할 수 있죠. TestRestTemplate의 사용 방법은 이 링크를 참고하세요.

[Spring Boot] ResponseEntity와 TestRestTemplate

JPA Auditing

JPA를 사용해 엔티티를 작성하다보면, 엔티티가 데이터베이스의 테이블에 일대일로 대응하게 되는데요. 코드를 짜다보면 엔티티의 필드에 중복되는 컬럼들이 생기게 되고, 이를 상속으로 개선하고 싶다는 생각이 자연스럽게 듭니다. 그럴 때 사용할 수 있는 기능이 바로 JPA Auditing입니다. 교재에서는 중복되는 컬럼인 생성일자와 수정일자를 BaseTimeEntity를 사용해서 중복을 개선했어요.

제가 생각하기에 주목할 것은 @MappedSuperClass인데요, 어노테이션은 객체의 필드와 달리 extends 키워드로 상속되지 않습니다. @MappedSuperClass를 사용하지 않고 부모 객체만 상속하면 그 부모 객체에 있는 필드는 자식에서 사용할 수 있지만 부모 객체 필드에 사용한 어노테이션은 사용할 수 없어요. @MappedSuperClass가 이 문제를 해결해줍니다. 그 외에 Jpa Auditing에 사용되는 애노테이션들을 더 알고 싶다면 이 링크를 참고해주세요.

[Spring] Spring Data JPA에서 Auditing 사용하는 법

 

[Spring] Spring Data JPA에서 Auditing 사용하는 법

JPA AuditingEntityListener 알아보기 위의 코드는 제가 작성했던 코드인데요. 누군가 저에게 아래와 같이 물었습니다. LocalDateTime.now() 코드는 왜 작성한거야? 위의 질문을 듣고 아.. BaseEntity를 통해서 생

devlog-wjdrbs96.tistory.com

✅ 어노테이션은 어떻게 동작하나요?

교재 전반에서 어노테이션을 활용해 코드를 더 깔끔하고 직관적으로 개선하고 있습니다. 어노테이션은 무엇이고, 어떻게 동작하는 걸까요?

1주차 추가 레퍼런스에서 살펴봤던 내용 중 스프링과 스프링 부트의 차이점이 있었습니다. 스프링이 처음 나왔을 당시, 스프링의 약점을 꼽히는 것은 버전 관리와 의존성 설정이었습니다. 너무 복잡하고 관리가 어려웠어요. 이를 개선하기 위해 나온 것이 스프링 부트입니다. 1장에서 간단하게 코드 몇줄로 의존성을 주입받고 버전 관리를 할 수 있었죠. Spring Boot가 설정 자동 구성을 간단하게 하기 위해 나온 것이 도입한 것이 바로 어노테이션입니다. 스프링 부트에서 사용되는 어노테이션의 종류와 설명은 이 링크를 참고하세요. (어노테이션은 스프링 기술이 아닙니다! 자바에서 오버라이딩 할 때 @Override를 사용하잖아요? 이것도 어노테이션입니다. 어노테이션에 대한 설명 이 링크를 참고하세요.)

원래 어노테이션은 단순한 주석으로 사용되기도 하지만, 추가 정보를 획득하여 기능을 실행하기도 합니다. 이것이 가능한 것은 Reflection이 있기 때문인데요, 리플렉션은 프로그램이 실행 중에 자신의 구조와 동작을 검사하고, 조사하고, 수정하는 것을 말해요. 그래서 어노테이션을 프로그램에게 추가적인 정보를 제공해주는 메타데이터라고도 합니다.

이후 교재 실습을 하면서 볼 수 있겠지만, 어노테이션을 사용자가 직접 정의해서 사용할 수도 있는데요. 추가적인 기능을 실행할 수 있게 하려면 Reflection 관련 코드를 작성해야 하는데 다소 복잡하고 성능이 떨어진다는 단점이 있어요. 그래서 Annotation Processing을 사용합니다.

한편으로 애노테이션 프로세서를 사용하는 롬복의 경우, 컴파일러 내부 클래스를 사용하고 기존 소스 코드를 조작하기 때문에 해킹이라고 주장하는 개발자들도 있습니다. 하지만 너무 편하잖아욧..! 이게 싫다면 코틀린을 사용해서 스프링 개발을 해보는 것도 방법이라고 생각합니다.

 


❄️ 멤버들 추가공부 자료

 

 

 

 

'Group Study (2022-2023) > Spring 입문' 카테고리의 다른 글

4주차 레퍼런스  (0) 2023.01.12
3주차 레퍼런스  (0) 2023.01.12
1주차 레퍼런스  (0) 2023.01.12
[spring 입문] 토이프로젝트 - TwoYeon  (1) 2022.12.20
[spring 입문] 토이 프로젝트 - BDNSook  (1) 2022.12.20