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

조갱·2023년 10월 1일
0

스프링 강의

목록 보기
1/16
  • 김영한님의 강의를 듣고 이해한 내용을 주관적으로 복습해봅니다.
  • 김영한님의 추천 로드맵을 100% 따라가기 보다는, 제가 실무에서 필요한 지식을 로드맵에서 걸러서 들었습니다.

프로젝트 환경설정

고냥 프로젝트를 생성하고 gradle 설정을 한다.
이미 알고있는 내용이라 훑으며 진행했다.

다만, 한가지 의문이 들었던 부분이 있었는데, 강의 자료 중

최근 IntelliJ 버전은 Gradle을 통해서 실행 하는 것이 기본 설정이다.
이렇게 하면 실행속도가 느리다.
다음과 같이 변경하면 자바로 바로 실행해서 실행속도가 더 빠르다.

라는 내용이 있어서 왜 Gradle보다 IntelliJ 로 빌드하는게 빠를까?에 대한 의문이 들어서 알아봤다.

IntelliJ는 증분 빌드 (Increment Build) 라는것을 사용하는데,
간략하게 설명하면 변경된 내용만을 빌드하기 때문이다.
반면에, Gradle은 프로젝트 전체를 빌드하기 때문에 시간이 오래걸린다.

여기서 생기는 차이점이 있는데

  • IntelliJ
    • 빌드 산출물이 out 폴더에 만들어진다.
    • 단순히 소스코드만을 빌드한다.
      -> 빌드 설정(build.gradle(.kts))이 변경될 경우 반드시 gradle rebuild 필요.
    • 삭제된 파일을 out 폴더에서 삭제하지 않는다.
      -> 삭제된 파일은 단순히 build 대상에서 제거되기 때문에 관리되지 않는다.
  • Gradle
    • 빌드 산출물이 build 폴더에 만들어진다.
    • 빌드 도구이기 때문에, 소스코드 빌드 이외에 다른 기능까지 자동화가 가능하다.
      -> 테스트 (./gradlew test)
      -> CI/CD
      -> custom task
      단, 위의 기능을 사용하기 위해서는 groovy script를 별도로 사용해야한다.
    • 삭제된 파일들도 관리된다.

스프링 웹 개발 기초

Welcome Page 만들기

resources/static/index.html 파일을 통해 Welcome Page를 만들 수 있다.
-> 공식 문서 참조

Thymeleaf

  • 타임리프는 Server-Side 템플릿 엔진이다.
  • 자연스러운 마크업 덕분에 퍼블리셔와 개발자간의 의사소통이 원활하다.
  • Spring Framework와 통합하기 쉽다.
    Spring MVC와 함께 사용하면 컨트롤러에서 모델 데이터를 뷰에 전달하여 렌더링할 수 있다.
  • 템플릿 상속을 지원하여, 웹 페이지의 일부를 재사용할 수 있다.

정적 페이지 반환하기

Controller 에서 문자열을 반환하면, viewResolver가 model을 매핑하여 정적 페이지를 반환한다.
매핑되는 템플릿 : resources/templates/{return된 문자열}.html

@Controller
public class HelloController {
  @GetMapping("hello")
  public String hello(Model model) {
    model.addAttribute("data", "hello!!");
    return "hello";
  }
}

resources/templates/hello.html

<!DOCTYPE HTML>
  <html xmlns:th="http://www.thymeleaf.org">
  <head>
      <title>Hello</title>
      <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  </head>
<body>
<p th:text="'안녕하세요. ' + ${data}" >안녕하세요. 손님</p>
  </body>
  </html>

API 로 사용하기

Controller 에 @ResponseBody 어노테이션을 붙여서 사용 가능하다.
@ResponseBody 어노테이션을 사용하면, viewResolver가 사용되지 않는다.
즉, 리턴되는 문자열이 template와 매핑되지 않고, 문자열 자체가 반환된다.

@Controller
@ResponseBody
public class HelloController {
  @GetMapping("hello")
  public String helloString(@RequestParam("name") String name) {
  	return "hello " + name;
  }
}

함께 알아두면 좋은 지식들 :

스프링 빈과 의존관계

스프링 빈을 사용하기 위해서는 여러 방법이 있지만,
아래 2가지 방법에 대해 소개한다.

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

스프링의 시작점 (main) 함수에는 @SpringBootApplication 어노테이션이 붙어있다. 내부를 살펴보면 @ComponentScan(...) 이 선언되있는 것을 확인할 수 있다.
BasePackage 가 별도로 설정되어있지 않다면, 현재 위치와 하위 위치를 탐색한다.

이 때, 각 클래스에 @Component어노테이션이 붙어있다면, 스프링이 자동으로 빈으로 등록한다.
일반적으로 @Controller, @Repository, @Service, @Configuration어노테이션도 빈으로 등록되는데, 이 또한 내부를 살펴보면 @Component 어노테이션이 적용되어있는 것을 확인할 수 있다.

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

@Configuration 어노테이션을 적용한 클래스에서,
메소드에 @Bean 어노테이션을 붙이면 빈으로 관리할 수 있다.

package myPackage;

@Configuration
public class MyConfiguration {
    @Bean
    public MyService myService() {
        return new MyService();
    }
    
    @Bean
    public MyRepository myRepository() {
        return new MyRepository();
    }
}

위 예제 코드에서는
MyService 클래스에 @Service, MyRepository 클래스에 @Repository 어노테이션이 없어도 된다.
명시적으로 MyService 및 MyRepository 클래스를 bean으로 등록했기 때문이다.

등록된 빈 사용하기

등록해둔 빈을 코드에서 변수에 할당하는 것을 '빈을 주입한다'고 표현한다.
빈을 주입하는 방법에는 3가지가 있다.

미리 스포를 하자면, 생성자 주입을 사용하도록 권장된다.
아래는 각 주입 방법에 대한 설명과 왜 생성자 주입을 사용해야 하는거?에 대한 간략한 소개를 해본다.

필드 주입

필드에 @Autowired 어노테이션을 통해 직접적으로 주입한다.

필드 주입을 사용하게되면 IntelliJ 의 경우에는 '필드주입 쓰지 말고 생성자 주입 쓰세요' 하고 경고를 표시해준다.

public class MyService {

	@Autowired
	private MyRepository repository;
    ...
}

Setter 주입

setMethod 에 @Autowired 어노테이션을 통해 MyRepository빈을 주입받고,
주입받은 빈을 MyRepository에 할당한다.

주입받는 객체가 변경될 수 있는 경우에 사용하지만, 실무에서는 그럴 일이 99.99% 확률로 없다.

public class MyService {

    private MyRepository repository;

    @Autowired
    public void setMyRepository(MyRepository repository) {
        this.repository = repository;
    }
}

생성자 주입

가장 권장되는 방법이다.
클래스가 생성되는 시점에 주입된다. 즉, 1회만 주입됨이 보장된다.

생성자가 주 생성자 1개만 있는 경우 @Autowired 어노테이션을 생략할 수도 있다.

public class MyService {

    private final MyRepository repository;

	// 주 생성자 1개만 있는 경우 @Autowired 생략 가능
    public MyService(MyRepository repository) {
        this.repository = repository;
    }

}

자바 언어의 경우 Lombok 에서 제공하는 @RequiredArgsConstructor 를 클래스 레벨에 붙여주면, Lombok이 생성자를 자동으로 생성해주면서 생성자 마저 안써도 주입이 된다고 한다.

하지만 코틀린은 기본적으로 제공해준 기능이라 코프링부트 개발자인 나는 '그러려니~' 하고 넘겼다 ㅋㅋ.

생성자 주입을 써야하는 이유

  • 의존성 명시적 표현
    생성자 파라미터로 필요한 의존성을 전달하기 때문에 코드에서 어떤 의존성이 필요한지 쉽게 파악할 수 있다. -> 가독성과 이해도가 향상

  • 불변성 보장
    생성자 주입을 사용하면 필드가 final로 선언되기 때문에, 한 번 주입된 의존성은 런타임 중에 변경할 수 없다.

  • 의존성 순환 참조 방지

  • 단위 테스트 용이성
    의존성을 명시적으로 전달하므로, 테스트 중에 가짜(mock) 객체 또는 테스트 더미(dummy) 객체를 주입하여 의존성을 테스트할 수 있다.

  • Spring 프레임워크와 호환성
    Spring Framework에서 기본적으로 지원되는 주입 방식을 사용하여, 의존성 주입을 더욱 쉽고 효과적으로 관리할 수 있다.

스프링 DB 접근 기술

JdbcTemplate

  • Spring Framework에서 제공하는 JDBC를 보다 쉽게 다룰 수 있도록 도와준다.
  • JDBC 와 동일한 설정을 해야한다.
  • 쿼리는 직접 작성해야한다.

JPA

  • 자바 플랫폼의 ORM (Object-Relational Mapping) 스펙이다.
  • JPA를 사용하면 객체 지향 언어인 자바와 RDBMS간의 데이터 매핑을 쉽게할 수 있다.
  • SQL 중심 데이터 설계에서, 객체 중심의 설계로 패러다임 전환이 가능하다.

QueryDSL

  • 쿼리를 문자열로 작성하는 대신, Java 코드로 쿼리를 작성할 수 있다.
  • 컴파일 타임 타입 안전성을 보장하면서 동적 쿼리 생성을 지원한다.
  • 자바 코드로 작성되기 때문에 재사용성이 뛰어나다.

Spring Data JPA

  • Spring Data 프로젝트의 일부로, Spring 프레임워크와 JPA를 결합하여 데이터 액세스 계층을 보다 쉽게 구현할 수 있도록 하는 라이브러리이다.
  • JPA와 달리, Hibernate 구현체를 통해 쿼리를 작성하지 않아도 된다.
  • 메소드 이름을 통한 쿼리 메소드 정의와 동적 쿼리 생성을 지원한다.
  • (개인적으로) 복잡한 쿼리는 작성하기 어렵다.

AOP

  • Aspect Oriented Programming의 약자로, 관점 지향 프로그래밍을 의미한다.
  • 공통적으로 필요한 기능 (ex: 로깅, 로직 수행 시간 측정)을 매번 코드에 작성하지 않고 적용할 수 있다.
  • 비즈니스 로직을 깔끔하게 유지할 수 있다.
  • 클래스에 @Aspect어노테이션을 적용하여 해당 클래스를 Aspect로 정의하고, 어떤 Join Point에서 어떤 Advice를 실행할지 정의할 수 있다. (정의된 Aspect는 스프링 빈으로 관리되어야 하기 때문에, @Component어노테이션을 함께 붙여야 한다.)
  • 스프링에서 proxy 를 적용한다. (Proxy 패턴)
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LoggingAspect {

    @Pointcut("execution(* com.example.service.*.*(..))")
    private void pointcut() {}
    
    @Before("pointcut()")
    //@Before("execution(* com.example.service.*.*(..))") 로 줄여서 사용 가능
    public void beforeServiceMethodExecution() {
        // 메소드 호출 전에 로깅을 수행하는 어드바이스
        System.out.println("Before executing a service method.");
    }
}

Reference
김영한님 스프링 강의
https://haenny.tistory.com/394
https://programforlife.tistory.com/111

profile
A fast learner.

0개의 댓글