윈도우 환경에서 빌드하고 실행하기

빌드하기에 앞서 java --version확인을 하시고, 인텔리제이 실행은 종료하여야합니다.

  • 명령 프롬프트(cmd)로 이동합니다.
  • 명령 프롬프트에서 gradlew build로 빌드합니다.

  • 빌드하면 build 디렉토리가 생성됩니다.
  • cd build/libs로 이동하여 .jar 파일을 확인합니다.

  • .jar 파일을 실행하면, 화면이 정상적으로 뜨는 것을 확인할 수 있습니다.

  • 빌드를 다시 하고 싶은 경우는, gradlew clean을 하면 build 폴더를 지우고 다시 빌드할 수 있습니다.


이 글은 김영한 님의 스프링 입문 강의를 복습하기 위해 작성된 글 입니다.

참고: 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

맨처음 프로젝트 실행시 에러페이지가 뜨는 것을 확인했었습니다.

이 에러 페이지를 해결하고 동작하는 화면을 확인해보겠습니다.

Welcome Page 만들기

  • resources/static/index.html 해당경로에 index.html 파일 만들기
    • static/indext.html을 올려두면 스프링 부트 기능으로 Welcome page로 설정된다
<!DOCTYPE HTML> 
<html> 
<head> 
<title>Hello</title> 
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> 
</head> 
<body> Hello <a href="/hello">hello</a> </body> 
</html>

thymeleaf 템플릿 엔진

  • 템플릿 엔진은 정적페이지(파일)을 원하는대로 수정할 수 있도록 해준다.
  • thymeleaf 사이드: https://www.thymeleaf.org/

Controller, templates 작업 후 연결하기

  • Controller
@Controller 
public class HelloController { 
    @GetMapping("hello") public String hello(Model model) { 
        model.addAttribute("data", "hello!!"); 
        return "hello"; 
    } 
}
  • view  
    • 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>

thymeleaf 템플릿엔진 동작 확인하기

동작 이해하기

  • 컨트롤러에서 리턴 값으로 문자를 반환하면 viewResolver 가 화면을 찾아서 처리한다.
    • 스프링 부트 템플릿엔진 기본 viewName 매핑
    • resources:templates/+{viewName}+.html

이 글은 김영한 님의 스프링 입문 강의를 복습하기 위해 작성된 글 입니다.

참고: 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

Gradle은 의존관계가 있는 라이브러리를 함께 다운로드 합니다.

따라서, 처음 프로젝트에 추가한 spring-boot-starter-web, spring-boot-starter-thymeleaf 이외에도 External Linraries에서는 의존관계가 있는 라이브러리들이 받아져 있는 것을 확인 할 수 있습니다.

라이브러리를 파악해보면 아래와 같습니다.

스프링 부트 라이브러리

  • spring-boot-starter-web
    • spring-boot-starter-tomcat: 톰캣 (웹서버)
    • spring-webmvc: 스프링 웹 MVC
  • spring-boot-starter-thymeleaf: 타임리프 템플릿 엔진(View)
  • spring-boot-starter(공통): 스프링 부트 + 스프링 코어 + 로깅
    • spring-boot
      • spring-core
    • spring-boot-starter-logging
      • logback, slf4j

테스트 라이브러리

  • spring-boot-starter-test
    • junit: 테스트 프레임워크
    • mockito: 목 라이브러리
    • assertj: 테스트 코드를 좀 더 편하게 작성하게 도와주는 라이브러리
    • spring-test: 스프링 통합 테스트 지원

이 글은 김영한 님의 스프링 입문 강의를 복습하기 위해 작성된 글 입니다.

참고: 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

스프링부트로 프로젝트를 하면서 공부를 하려고합니다. 김영한님의 스프링 입문 강의로 시작합니다.

 

1.  IDE 준비하기

기존에 IntelliJ를 사용중이기에 따로 설치하지 않고 바로 이용합니다. 개인 상황에 맞추어 IntelliJ나 Eclipse를 설치합니다. 요즘 추세로는 IntelliJ가 편리하여 주로 사용된다고 합니다.

 

2. 자바 11 설치하기

프로젝트에서 Java 11을 사용하기 위해서 JDK 11 버전을 다운받습니다.

저는 Window 환경이라 아래의 블로그를 참고하여 설치를 하였습니다. 개인 컴퓨터 환경에 따라 설치를 진행하시면 됩니다.

- (참고) https://crazykim2.tistory.com/478

 

[JAVA] Window10의 JAVA SE 11 설치하기

안녕하세요 포스팅이 늦은 것 같지만 이번에 윈도우를 포맷하면서 자바를 다시 설치하게 되었습니다 자바 개발을 처음하거나 자바를 설치한지 오래되어서 기억이 안 나는 분들을 위해 자바 설

crazykim2.tistory.com

 

3. Springboot 프로젝트 만들기

- Spring Initializr에서 프로젝트를 만들어줍니다. 

- https://start.spring.io/

- 위, 사이트는 스프링부트를 기반으로 스프링 프로젝트를 만들어주는 사이트입니다.

- Maven / Gradle :  필요한 라이브러리를 관리하고, 빌드 라이프 사이클을 관리해주는 도구

- 요즘은 Gradle 을 주로 사용한다고 합니다.

- 언어와 버전은 위와 같이 설정하였습니다. 

라이브러리를 추가합니다.

- Spring Web: 웹 프로젝트를 진행하기 때문에 추가

- Thymeleaf: html을 만들어주는 템플릿 엔진

- GENERATE 하여 다운로드 받고 인텔리제이에서  프로젝트를 실행하여줍니다.

 

4. 프로젝트 실행

프로젝트를 열면 라이브러리들을 받느라 시간이 소요됩니다. 잠시 기다리면서 JDK와 Bjild and run 설정을 확인해줍니다.

- Build and run을 위처럼 IntelliJ IDEA로 설정해 줍니다.

- Project Settings 에서  SDK  버전을 확인해 줍니다. 저는 18로 설정되어있어 11로 변경해주었습니다.

- 메인함수를 실행하고 localhost:8080에 아래와 같은 화면이 뜨면 성공입니다.

 


이 글은 김영한 님의 스프링 입문 강의를 복습하기 위해 작성된 글 입니다.

참고: 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

JPA 소개

현대 웹 애플리케이션에서 Oracle, MySQL, MSSQL 등의 RDB를 쓰지 않은 경우가 드물다. 따라서, 객체를 관계형 데이터베이스에서 관리하는 것이 중요하다.
하지만, 기존 iBatis, MyBatis 등의 SQL Mapper로 쿼리를 매핑하는 방식은 아래와 같은 문제점이 있었다.
관계형 데이터베이스는 SQL만 인식할 수 있기 때문에, 각 테이블마다 CRUD SQL을 매번 생성하다보면 SQL의 비중이 높아진다. 그에 따라 유지보수의 어려움이 있다.
또한, 관계형 데이터베이스와 객체지향 프로그래밍 언어의 패러다임이 서로 다름으로 인한 여러 문제가 발생한다. 상속, 1:N 등 다양한 객체 모델링을 데이터베이스로는 구현할 수 없다.
이러한 문제점을 해결하기 위해 JPA 가 등장하였다.

Spring Data JPA

JPA는 인터페이스로서 자바 표준명세서이다.
따라서, 인터페이스인 JPA를 사용하기 위해서는 구현체가 필요하다. 대표적으로 HIbernate, Eclipse Link 등이 있다.
하지만, Spring에서 JPA를 사용할 때 구현체들을 직접 다루지 않는다.
구현체들을 더 쉽게 사용하고자 추상화시킨 Spring Data JPA라는 모듈을 이용하여 JPA를 다룬다.

JPA <= Hibernate <= Spring Data JPA

 

Spring Data JPA 등장 이유

1. 구현체 교체의 용이성
Hibernate 외에 다른 구현체로 쉬게 교체하기 위하여 Spring Data JPA를 사용한다. 기술의 변화함에 따라 구현체를 교체해야할 때, Spring Data JPA 내부에서 구현체 매핑을 지원해 줌으로 쉽게 교체가 가능하다.
2. 저장소 교체의 용이성
Spring data JPA에서 DB의 의존성만 교체하면 쉽게 DB 교체가 가능하다. Spring Data 하위의 프로젝트들은 기본적인 CRUD 인테페이스(save(), findAll, finOne() 등)가 같이 때문에, 저장소가 교체되어도 기본적인 기능은 변경할 것이 없어 교체가 쉽다.

프로젝트에 Spring Data Jpa 적용하기

build.gradle에 spring-boot-starter-data-jpa, h2 의존성 등록

dependencies {
    implementation('org.springframework.boot:spring-boot-starter-web')
    implementation('org.projectlombok:lombok')
    implementation('org.springframework.boot:spring-boot-starter-web') // 추가
    implementation('org.h2database:h2') // 추가
    annotationProcessor('org.projectlombok:lombok') 
    testImplementation('org.springframework.boot:spring-boot-starter-test')
}

spring-boot-starter-data-jpa

- 스프링 부트용 Spring Data Jpa 추상화 라이브러리
- 스프링 부트 버전에 맞춰 자동으로 JPA 관련 라이브러리 버전을 관리

h2

- 인메모리 관계형 데이터베이스로 별도의 설치없이 프로젝트 의존성만으로 관리 가능
- 메모리에서 실행되기 때문에 애플리케이션을 재시작할 때마다 초기화된다는 점을 이용하여 테스트 용도로 많이 사용
- JPA의 테스트, 로컬 환경에서의 구동에 사용

DB 테이블과 매칭될 Entity 클래스 작성

기존에 MyBatis같은 쿼리 매퍼의 경우 dao 클래스에서 오로지 xml 쿼리의 결과만 담던일을 Entity 클래스에서 모두 해결

import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Getter
@NoArgsConstructor
@Entity
public class Posts {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(length = 500, nullable = false)
    private String title;

    @Column(columnDefinition = "TEXT", nullable = false)
    private String content;

    private String author;

    @Builder
    public Posts(String title, String Contnet, String author) {
        this.title = title;
        this.content = content;
        this.author = author;
    }

}

@Entity

- 테이블과 링크될 클래스임을 나타낸다.
- 기본값으로 클래스와 카멜케이스 이름을 언더스코어 네이밍으로 테이블 이름을 매칭한다.
ex) SalesManager.java -> sales_manager table

@id

- 해당 테이블의 PK 필드를 나타낸다.

@GeneratedValue

- PK의 생성 규칙을 나타낸다.
- 스프링 부트 2.0에서는 GenerationType.IDENTITY 옵션을 추가해야만 auto_increment가 된다.
!! 웬만하면 Entity의 PK는 Long 타입의 Auto_increment를 추천!!

@Column

- 테이블의 칼럼을 나타내며 굳이 선언하지 않더라도 해당 클래스의 필드는 모두 칼럼이 된다.
- 기본값 외에 추가로 변경이 필요한 옵션이 있으면 사용한다.
ex) 문자열의 경우 VARCHAR(255)가 기본값인데, 사이즈를 500으로 늘리고 싶거나, 타입을 TEXT로 변경하고 싶은 경우 등에 사용

@NoArgsConstructor

- 기본 생성자 자동 추가
- public Post(){}와 같은 효과

@Getter

- 클래스 내 모든 필드의 Getter 메소드 자동 생성

@Builder

- 해당 클래스의 빌더 패턴 클래스를 생성
- 생성자 상단에 선언 시 생성자에 포함된 필드만 빌더에 포함

DB Layer 접근자 Repository 생성

ibatis나 MyBatis 등에서 Dao라고 불리는 DB Layer 접근자를 JPA에선 Repository라고 부르며 인터페이스로 생성한다.

import org.springframework.data.jpa.repository.JpaRepository;

public interface PostsRepository extends JpaRepository<Posts, Long> {
    
}

!!Entity 클래스와 기본 Entity Repository는 함께 위치해야 한다.!!
단순히 페이스를 생성 후, JpaRepository<Entity 클래스, PK 타입> 를 상속하면 기본적인 CRUD 메소드가 자동으로 생선된다.

위의 Entity 클래스와 기본 Repository는 도메인별로 함께 움직여야 하므로 동일한 도메인 패키지에서 함께 관리한다.

Spring Data JPA 테스트 코드 작성

import com.choee.service.springboot.domain.posts.Posts;
import com.choee.service.springboot.domain.posts.PostsRepository;
import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;

@RunWith(SpringRunner.class)
@SpringBootTest
public class PostsRepositoryTest {

    @Autowired
    PostsRepository postsRepository;

    @After
    public void cleanup() {
        postsRepository.deleteAll();
    }

    @Test
    public void 게시글저장_불러오기() {
        //given
        String title = "테스트 게시글";
        String content = "테스트 본문";

        // 빌더를 활용한 빌드 패턴
        postsRepository.save(Posts.builder()
                .title(title)
                .content(content)
                .author("rhksh99@gmail.com")
                .build());

        //when
        List<Posts> postsList = postsRepository.findAll();

        //then
        Posts posts = postsList.get(0);
        assertThat(posts.getTitle()).isEqualTo(title);
        assertThat(posts.getAuthor()).isEqualTo(content);
    }
}

@After

- Junit에서 단위 테스트가 끝날 때마다 수행되는 메소드를 지정
- 보통은 배포 전 전체 테스트를 수핼할 때 테스트간 데이터 침범을 막기위해 사용된다.
- 여러 테스트가 동시에 수행되면 테스트용 데이터베이스인 H2에 데이터가 그대로 남아 있어 다음 테스트실행시 테스트가 실패할 수 있다. 따라서 위 코드에서는 cleanup()을 수행하도록 작성되었다.

@postRepository.save

- 테이블 posts에 insert/update 쿼리를 실행
- id 값이 있다면 update가, 있다면 insert 쿼리가 실행된다.

@postRepository.findAll

- 테이블 posts에 있는 모든 데이터를 조회해오는 메소드이다.

@SpringBootTest

- 별다른 설정 없이 H2 데이터베이스를 자동으로 실행해 준다.

 

실행된 쿼리 확인하는 법

스프링 부트에서는 application.properties, application.yml 등의 파일로 한 줄의 코드로 설정할 수 있도록 지원하고 이를 권장한다.
1. src/main/resources 하위에 application.properties 파일 생성 및 옵션 추가

spring.jpa.show-sql=true

2. 콘솔에서 쿼리 로그 확인

롬복이란?

  • 자바 개발자들의 필!수! 라이브러리 롬복, 롬복이란 무엇인지와 어떻게 사용하는지 알아보자.

롬복은 자바 개발할 때 자주 사용하는 코드 Getter, Setter, 기본생성자, toString 등을 어노테이션으로 자동 생성해 줍니다.

인텔리제이에서 프로젝트에 롬복 추가

  1. Build.gradle에 코드 추가
  2. compile('org.projectlombok:lombok')
  3. Gradle 새로고침하여 라이브러리(의존성) 추가
  4. Lombok 플러그인 다운로드 및 인텔리제이 재시작
  5. 롬복에 대한 설정 팝업 클릭 or [Preferences > Build,... > Compiler > Annotaion Processor]에서 Enable annotaion processing 체크

롬록으로 리팩토링하기

Hello Controller 코드 롬복으로 전환하기

  1. web 패키지에 dto 패키지 추가
  • 모든 응답 Dto를 관리
  1. Hello ResponseDto 생성
package com.choee.service.springboot.web.dto; 
import lombok.Getter; 
import lombok.RequiredArgsConstructor; 
@Getter 
@RequiredArgsConstructor 
public class HelloResponseDto { 
	private final String name; 	
    private final int amount; 
}

@Getter

  • 선언된 모든 필드의 get 메소드를 생성해 준다.

@RequiredArgsConstructor

  • 선언된 모든 final 필드가 포함된 생성자를 생성해 준다.
  • final이 없는 필드는 생성자에 포홤되지 않는다.

Dto에 적용된 롬복이 잘 작동하는지 테스트 코드 작성

package com.choee.service.springboot.web.dto;

import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;

public class HelloResponseDtoTest {

    @Test
    public void 롬복_기능_테스트() {
        // given
        String name = "test";
        int amount = 1000;

        // when
        HelloResponseDto dto = new HelloResponseDto(name, amount);

        // then
        assertThat(dto.getName()).isEqualTo(name);
        assertThat(dto.getAmount()).isEqualTo(amount);
    }
}

assertThat

  • assertj라는 테스트 검증 라이브러리의 검증 메소드
  • 검증하고 싶은 대상을 메소드 인자로 받는다.
  • 메소드 체이닝이 지원되어 isEqualTo와 같이 메소드를 이어서 사용 가능

isEqualTo

  • assertj의 동등 비교 메소드
  • assertThat에 있는 값과 isEqualTo의 값을 비교해서 같을 때만 성공

Junnit과 비교하여 assertj의 장점

  • CoreMatchesrs와 달리 추가적으로 라이브러리가 필요하지 않다.
  • -> Junit의 asserThat을 쓰게 되면 is()와 같이 CoreMatchers 라이브러리가 필요하다.
  • 자동완성이 좀 더 확실하게 지원된다.
  • -> IDE에서 CoreMatcher와 같은 Matcher 라이브러리의 자동완성 지원이 약하다.

!트러블슈팅!

  • 문제: 그레이들 5에서 롬복을 비롯해서 Querydsl 등의 플러그인 설정 방법 변경
  • 해결: build.gradle 의존성에 annotationProcessor('org.projectlombok:lombok') 추가
  • 비고: gradle 5 이상부터 어노테이션을 구별해서 추가해야 한다.

HelloReponseDto를 사용하여 HelloController에 코드 추가

package com.choee.service.springboot.web;

import com.choee.service.springboot.web.dto.HelloResponseDto;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

    @GetMapping("/hello")
    public String hello() {
        return "hello";
    }

    // 추가된 부분
    @GetMapping("/hello/dto")
    public HelloResponseDto helloDto(@RequestParam("name") String name,
                                     @RequestParam("amount") int amount) {
        return new HelloResponseDto(name, amount);
    }
}
package com.choee.service.springboot.web;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;

import static org.hamcrest.Matchers.is;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@RunWith(SpringRunner.class)
@WebMvcTest(controllers = HelloController.class)
public class HelloControllerTest {

    @Autowired
    private MockMvc mvc;

    @Test
    public void hello가_리턴된다() throws Exception {
        String hello = "hello";

        mvc.perform(get("/hello"))
                .andExpect(status().isOk())
                .andExpect(content().string(hello));
    }

    // 추가된 API 테스트 코드
    @Test
    public void helloDto가_리턴된다() throws Exception {
        String name = "hello";
        int amount = 1000;

        mvc.perform(
                get("/hello/dto")
                        .param("name",name)
                        .param("amount",String.valueOf(amount)))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.name",is(name)))
                .andExpect(jsonPath("$.amount",is(amount)));
    }
}

param

  • API 테스트할 때 사용될 요청 파라미터를 설정한다.
  • 단, 값은 String만 허용된다.(숫자, 날짜 등의 데이터도 등록할 때 문자열로 변경해야한다.)

jsonPath

  • JSON 응답값을 필드별로 검증할 수 있는 메소드
  • $를 기준으로 필드명을 명시

+ Recent posts