Development/Spring

[Spring] 스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술 정리, 인프런 김영한 스프링

hyelie 2022. 10. 4. 13:57

스프링 토이프로젝트를 하나 만들긴 했지만 내가 한 것은 기술을 어떻게 사용하느냐지, 어떻게 만들어졌는가는 생각하지 않았다. 모든 분야가 그렇듯 동작 원리에 대해 이해해야 더 좋은 코드를 작성할 수 있기 때문에 스프링 그 자체에 대해 공부를 조금 해 볼 예정이다. 인프런의 김영한님의 강의를 몇 개 들을 예정이다.

 

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%9E%85%EB%AC%B8-%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8/dashboard

 

이 글은 위 링크에 있는 강의를 정리한 글이다. 이후에 유료강의들은 비공개로 정리할 예정이고.

 

 

 


 

 

 

 

1. 프로젝트 환경설정

1) 프로젝트 생성

나는 VS Code Spring Initializr 이용하니 패스. start.spring.io 이용할 수 있음.

 

2) 라이브러리 살펴보기

gradle은 의존관계가 있는 라이브러리를 다운로드 해주는 기술 중 하나이다.

스프링부트에서 사용하는 라이브러리는 다음과 같다.

# spring boot library
spring-boot-starter-web
    spring-boot-starter-tomcat (웹서버, 톰캣)
    sprint-webmvc (스프링 웹 MVC)
spring-boot-starter-thymeleaf (타임리프 template engine)
spring-boot-starter (spring boot + spring core + logging)
    spring-boot
        spring-core
    spring-boot-starter-logging
        logback, slf4j

# spring test library
spring-boot-starter-test
    junit (테스트 프레임워크)
    mockito (목 라이브러리)
    assertj (테스크 좀 더 편하게 작성하게 해주는 라이브러리)
    spring-test (스프링 통합 테스트 지원)

 

3) View 환경설정

spring에서 어떻게 web과 상호작용하는지. 저번 포스팅에 작성한 것처럼

web -> spring 내장 tomcat 서버 -> spring controller -> view resolver -> web

으로 진행된다.(강의는 html로 진행하니까)

 

4) 빌드하고 실행하기

./gradlew build
java -jar {JAR파일이름}.jar

 

 

 


 

 

 

2. 스프링 웹 개발 기초

1) 정적 컨텐츠

spring boot에서 원래 제공해 주는 기능. web에서 어떤 url을 입력했을 떄, 그 url을 처리해 주는 controller가 없으면 resources/static에서 html 파일을 찾음.

 

2) MVC와 템플릿 엔진

controller를 만드는 경우. 앞에서 진행했던 게시판 예제와 동일.

parameter는 controller의 인자로 넣고, 받는 2가지 방법이 있음. 첫째는

 

 @RequestParam("name") String name

 

이 경우 http://{도메인}/url?name="" 이런 식으로 query문으로 받음.

 

@PathVariable("name") String name

 

이 경우 http://{도메인}/url/{이름} 이런 식으로 받음.

 

MVC를 이용하면, web -> tomcat 서버 -> controller -> viewResolver(model을 전달) -> html web에 띄움

이런 순서가 됨.

 

 

3) API

controller에 annotation으로 @ResponseBody를 이용하면 viewResolver를 사용하지 않고 HTTP Body에 직접 반환함. 만약 객체를 리턴하겠다, 즉 json을 리턴하기 위해서는 getter가 있어야 함. 객체와 해당 객체의 getter가 있으면 객체가 알아서 처리해 준다고 함.

 

API를 이용한다고 하면, web -> tomcat 서버 -> controller -> HttpMessageConverter(responsebody에 객체) -> web에 띄움 이런 순서가 됨.

 

 

 

요약하면, spring에서 web으로 리턴하는 방식은 크게 정적, MVC, API 3가지 방식이 있음. 정적은 개발할 때 사용하고, MVC는 프론트도 자기가 만들 때, API는 FE랑 연동할 때 주로 사용한다고 함.

 

 

 


 

 

 

3. 회원 관리 예제 - 백엔드 개발

1) 비즈니스 요구사항 정리

어떤 데이터를 사용할지(DB Schema), 어떤 기능이 필요한지(API)

 

2) 회원 도메인과 리포지토리 만들기

Optional이라는 기능을 이용하기 위해 해당 library import한다. optional은 value값을 wrapper로 감싸서 Null Pointer Exception 오류가 나지 않게 해 주는 기능이다.

import java.util.Optional;

 

또, 실무에서는 값들이 겹치면 안되고, 동시성 문제를 해결하기 위해 ConCurrentHashmap이라는 값을 사용한다고 한다.

 

3) 회원 리포지토리 테스트 케이스 작성

해당 강의에서 제일 신기했던 부분. 학교 과제같은 걸 보면 TC를 100% 패스해야 통과하는 그런 것처럼 내가 TC를 직접 작성할 수 있다. JUnit이라는 test framework를 Java에서 제공하는데 이걸 이용해준다. test class는 test 폴더 안에 작성한다.

 

package com.example.demo;

import org.assertj.core.api.Assertions;  // Assertion method를 이용하기 위함
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.AfterEach; // AfterEach method를 이용하기 위함
import org.junit.jupiter.api.BeforeEach;  // BeforeEach method를 이용하기 위함
import org.springframework.boot.test.context.SpringBootTest;


@SpringBootTest
class DemoApplicationTests {

	@AfterEach(){
		// 해당 클래스가 끝나면 멤버 초기화 해 줄 것
	}

	@BeforeEach(){
		// 해당 클래스 시작하기 전 멤버 초기화 해줄 것
	}

	@Test  // 이 각각 method가 test로 작동함.
	void contextLoads() {
		System.out.println("result = " + (1 == 0));
		Assertions.assertThat(1).isEqualTo(0);
        // print를 할 수도 있고 assertion를 이용할 수도 있다.
	}

	@Test
	void contextLoad2s() {
        Assertions.assertThat(1).isEqualTo(1);
		// given, 어떤 값들이 주어졌을 때

        // when, 어떻게 처리되어야 하고

        // then, 어떤 결과가 나와야 한다.
	}

}
​

 

일반적으로 Test Case는 테스트가 목적이기 때문에 BeforeEach, AfterEach method를 이용해 DB에 영향이 가지 않게 처리해 준다. 추후에 DB를 이용할 때는 @Transactional annotation을 이용해 DB에 반영되지 않게 할 수도 있다고 한다.

 

또, TC는 given-when-then 순서로, 1) 어떤 값이 주어졌을 때 2) 어떻게 처리되어야 하고 3) 어떤 결과가 나와야 한다, 순서로 처리하면 편하다고 한다.

 

VS Code에서는 아래 사진과 같이, Test class 전체, 또는 각각의 method를 실행시킬 수 있다. Test Class 전체에서 실행하면 하위의 모든 method가 실행된다.

 

 

 

4) 회원 서비스 개발

5) 회원 서비스 테스트

 

 

 


 

 

 

4. 스프링 빈과 의존관계

1) 컴포넌트 스캔과 자동 의존관계 설정

Spring에는 spring container라는 게 있다. 생성자에 @Autowired annotation이 있으면 연관된 객체를 spring container에서 찾아 넣어준다. 이렇게 각 object들의 의존관계를 넣어 주는 것을 DI, dependency injection이라 한다. 소스 상위에 import문을 적어주는 것보다 편리하기 때문에 이렇게 사용한다.

 

예를 들어 아래 코드를 보자.

@Controller
public class MyController {
    private final MyService myService;
    
    @Autowired
    public MyController(MyService myService) {
        this.myService = myService;
    }
}

 

이렇게 생성자 안에 @Autowired를 작성해 두면 spring container에 있는 MyService 객체를 찾아와 준다. 그러나 해당 object가 spring container에 등록되어 있지 않으면 오류가 발생된다. spring bean을 spring container에 올리는 방법은 1) component scan으로 자동으로 의존관계 설정, 2) java code로 직접 spring bean 등록 2가지 방법이 있다.

 

- component scan

@Component annotation이 있으면 spring bean으로 자동 등록된다. @Controller, @Service, @Repository 이런 annotation 내부에는 @Component annoation이 이미 있다.

 

*** 스프링 빈 spring bean

bean은 spring에서 관리하는 object들이다.

 

*** 싱글톤 Singleton

spring container에 bean을 등록할 때 singleton(하나만) 등록한다.

 

 

2) 자바 코드로 직접 스프링 빈 등록하기

@Bean을 이용해 직접 등록하는 방법이다.

 

 

DI에는 field 주입, setter 주입, constructor 주입 3가지 방식이 있는데 field 주입은 안 좋고, setter 주입은 setter가 public으로 노출되기 떄문에 보안상 문제가 있기 때문에 constructor 주입을 주로 사용한다고 한다.

 

실무에서는 정형화던 controller, service, repository는 component scan을, 상황에 따라 바뀌는 경우이면 spring bean으로 등록한다고 한다.

 

 

 


 

 

 

5. 회원 관리 예제 - 웹 MVC 개발

1) 회원 웹 기능 - 홈 화면 추가

2) 회원 웹 기능 - 등록

3) 회원 웹 기능 - 조회

 

지난 예제에서 다 한 것이므로 skip

 

 


 

 

 

6. 스프링 DB 접근 기술

1) H2 데이터베이스 설치

나는 MySQL을 사용하니 skip

 

2) 순수 Jdbc

3) 스프링 통합 테스트

 

package com.example.demo;

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.springframework.boot.test.context.SpringBootTest;

import org.springframework.transaction.annotation.Transactional;



@SpringBootTest
@Transactional
class DemoApplicationTests {



	@Test
	void contextLoads() {
...

 

앞에서 한 번 언급했던 내용. Test class 위에 @Transactional annotation을 추가하면 test 시작 전 transaction을 시작하고 test 완료 후에 rollback을 통해 DB에 data가 남지 않음. 이 경우 @BeforeEach, @AfterEach를 이용하는 것보다 편리하다.

 

 

 

4) 스프링 JdbcTemplate

5) JPA

JPA는 DB와 연결을 편리하게 해 주는 기능, 코드 반복을 줄여주고 SQL문을 직접 작성해 준다. JPA를 이용하기 위해서는 application.properties에 코드를 추가해야 한다.

 

JPA는 Entity라는 것을 이용하므로 @Entity annotation을 이용해 class와 DB schema를 mapping할 entity class를 만들어야 한다.

 

한편, *** JPA를 통한 모든 데이터 변경은 @Transactional annotation을 붙여서 실행해야 한다. 즉, 필요한 service method에 @Transactional annotation을 붙이는 것이다. 내가 이전에 작성한 게시판 만들기 에서도 service의 모든 method는 transaction annotation을 붙였다.

 

package com.example.testcompose.service;

import com.example.testcompose.dto.BoardDto;
import com.example.testcompose.domain.repository.BoardRepository;

import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;

import javax.transaction.Transactional;

@AllArgsConstructor
@Service
public class BoardService {
    private BoardRepository BoardRepository;

    @Transactional
    public Long savePost(BoardDto boardDto){
        return BoardRepository.save(boardDto.toEntity()).getId();
    }
}

 

6) 스프링 데이터 JPA

spring data JPA는 repository 구현 없이 interface로 개발해 주는 것이다.

Spring Data JPA는 JPA를 편리하게 이용하게 해 주는 기술이므로 JPA를 공부해야 한다...고 한다. 이름만으로 CRUD를 할 수 있게 된다. + paging까지 해 준다고 한다.

 

 

 


 

 

 

7. AOP

1) AOP가 필요한 상황

AOP는 Aspece Oriented Programming, 관점에 나누어 프로그래밍 하겠다는 것이다.

공통 관심 사항Cross-Cutting Concern과 핵심 관심 사항Core Concern 2가지로 나누어 따로 개발할 수 있게 한다는 것이다.

 

예를 들어 모든 logic의 시간 측정을 하고 싶은 경우에, 기존에는 controller, service, repository 등 모든 method에 시간 측정 코드를 전/후에 삽입해야 할 것이다. 그러나 이 코드들이 방대해지면? 답이 없다. 코드 삽입 할 때 귀찮음도 물론이거니와 로직이 섞여 유지보수도 어렵다. 그래서 AOP를 사용한다.

 

 

2) AOP 적용

필요한 method인 '핵심 관심 사항'과 추가적으로 넣고 싶은 '공통 관심 사항'을 분리해서 공통 관심 사항을 별도의 로직으로 만들 수 있다. 또한 작성한, 공통 관심 사항 로직을 내가 사용하고 싶은 '핵심 관심 사항' 코드들에만 적용시켜서 원하는 적용 대상을 고를 수 있다.