Project/토이 프로젝트

[Cinemagram] 프로젝트 생성, Thymeleaf 적용, 스프링부트 Controller 동작방식 - (1)

Lea Hwang 2022. 11. 10. 12:04

지금까지 조각조각 공부했던 개념들을 어떻게 하면 퍼즐처럼 잘 모을 수 있을까 고민을 하다 토이 프로젝트를 시작하게 되었습니다. 평소 영화를 좋아해서 영화 관련 포스팅을 모아 볼 수 있는 웹을 만들자!라는 마음에 프로젝트명을 Cinemagram으로 정했습니다. 

 

기능 구현 부분은 스스로 구현하다 막히는 부분은 여러 블로그나 영상을 참고해서 만들었으며 디자인 요소는 다른 분께서 만들어 놓으신 부분을 가져왔습니다. (저도 디자인에 소질이 있고 싶습니다...)

기존 jsp코드를 thymeleaf로 바꾸면서 이론으로만 공부했던 문법을 실제로 적용해 볼 수 있는 시간이었습니다.

생각보다 이 부분에서 시간이 제일 많이 소요되어서 힘들었지만 전 괜찮습니다...

 

 

 

포스팅에는 코드 뿐만 아니라 시행착오를 기록할 예정이며 단순 클론 코딩이 아닌 관련 개념까지 정리하는 포스팅이 될 것 같습니다. 

 

이번 프로젝트를 통해 여태까지 공부한 정도를 스스로 알아보고 부족한 부분을 채우는 것을 목표로 합니다.

 

 

 

 

 


 

 

목차

프로젝트 생성 및 기본 세팅

thymeleaf 적용

스프링 부트 Controller 기본 동작 방식

 

 

 

프로젝트 생성

IDE : IntelliJ

Build Tool : Gradle 

language: JAVA 8

Framework : SpringBoot, JPA

Database : RDBMS(MySQL)
API test : Postman

 

 

기본 세팅

build.gradle → dependencies

plugins {
	id 'org.springframework.boot' version '2.7.5'
	id 'io.spring.dependency-management' version '1.0.15.RELEASE'
	id 'java'
}

group = 'com'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'
	compileOnly 'org.projectlombok:lombok'
	annotationProcessor 'org.projectlombok:lombok'
	// DB
	implementation 'mysql:mysql-connector-java'

	testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

tasks.named('test') {
	useJUnitPlatform()
}

 

 

프로젝트용 Datababse를 새롭게 생성합니다. (관련 포스팅)

 

 

application.yml 기본 설정 (위에서 생성한 Database 연결)

# dev-profile
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/[DB명]?serverTimezone=Asia/Seoul&characterEncoding=UTF-8
    username: [username]
    password: [password]
  jpa:
    show-sql: true
    hibernate:
      ddl-auto: update
      naming:
        implicit-strategy: org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl
        physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
    properties:
      hibernate:
        format_sql : true
    open-in-view: true

logging:
  level:
    ROOT: INFO
    example.springsecurity: DEBUG
  path: ./
#    org:
#      hibernate:
#        type:
#          descriptor:
#            sql: trace

 

 

이렇게 까지 설정하고 Application.java를 구동했을 때 에러가 나거나 예상한 것보다 시간이 걸리는 경우 저의 경우엔 이 부분들을 수정했을 때 해결되었습니다.

 

1. Settings → Gradle → Build and run using & Run tests using을 IntelliJ IDEA로 수정 → Apply

 

2. Project Structure → Project, Modules, SDKs에 나의 Java 버전으로 변경

 

다행히 구동이 잘 됨을 확인할 수 있습니다.

 

 

💡 앞으로의 계획
저는 사실 이 프로젝트를 한 번 쭉 만들어본 상태이고 그 당시 '처음부터 이렇게 했으면 좋았을 텐데'라고 생각했던 부분을 포스팅에서 실현할 계획입니다.

그건 기능 덩어리마다 GitHub에 push 하는 것인데요.

앞으로 push후 commit메시지를 따로 표기해서 혹시 코드를 따라 하시는 분들께 조금이나마 도움이 되었으면 하는 마음입니다.

제 GitHub 주소 : https://github.com/youjungHwang/cinemagram


* IntelliJ에서 공유, 커밋, 푸시하는 방법
1. share
Ctrl + Shift + a →  share project github → 로그인, 패스워드 입력 → 리포지토리 이름 기입 → .idea 폴더는 체크 해제 → add 

리포지토리가 잘 생성되었는지 Github 페이지 확인

2. commit (Ctrl + K)
커밋 메시지 적고 commit

3. push (Ctrl + Shift + K)

해당 리포지토리에 push가 되었는지 Github 페이지 확인

 

 

 

디자인 코드

기본 세팅은 끝났으니 여기에 디자인 코드를 붙여보겠습니다. (html, css)

해당 부분까지 해서 Initial commit으로 push 했습니다.

 

 

 

thymeleaf 적용

참고하는 코드 부분이 jsp로 되어있던 것을 thymeleaf 로 변경하였습니다.

(왜 제가 thymeleaf를 한다고 했을까요, 커뮤니티에서 다들 포기 했다고 하던데, 사람들이 안 하는 데는 다 이유가 있나봅니다. 제 고집덕에 프로젝트 완성기간이 2달은 미뤄졌지만 인내심과 지구력을 기를 수 있었습니다.)

  • .jsp → .html 변경
    • 상단에 thymeleaf 사용 선언, 중간중간 데이터 넘어오는 곳에 thymeleaf 문법 적용
      • 혹시 thymeleaf가 잘 기억이 안 나신다면 먼저 기본적인 기능을 정리해놓은 포스팅을 참고하시길 바랍니다.
  • thymeleaf 템플릿 조각 적용
    • 추후 수정을 편리하게 하기 위해 header, footer를 분리함
  • dependencies 추가
    • implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'

 

여기까지 thymeleaf적용 추가로 push 했습니다.

 

 

 

 

기능 구현에 바로 들어가기 전 Controller에 대한 동작 방식과 주요 개념에 대해 짚고 넘어가 보겠습니다.

스프링 부트 Controller

 

[ 사용자 요청이 들어오면 스프링 부트는 어떤 로직으로 처리하는 것일까? ]

부제: Controller, FrontController, Dispatcher 차이

 

사용자가 로그인을 하고 상품을 주문한다고 가정해보자면 여기서 사용자는 서버에 여러 요청을 하게 됩니다.

1. 회원가입 요청

2. 로그인 요청

3. 상품 주문 요청

4. 상품 취소 요청 등등 

 

 

(1) 사용자가 요청을 할 때마다 Java파일이 호출된다.

로그인 요청을 하면 → 로그인 관련 자바 파일이 호출(Login.java)

회원가입 요청을 하면 → 회원가입 관련 자바 파일이 호출(Join.java)

상품 주문 요청을 하면 상품 주문 관련 자바 파일이 호출(Order.java)

상품 취소 요청을 하면  상품 취소 관련 자바 파일이 호출(Cancel.java)

....

 

이 방법의 문제점은 요청마다 각각 다른 Java 파일이 호출된다는 것입니다.

 

 

그럼 한 Java파일에서 전부 처리해버리면 어떨까요?

 

(2) 이러한 Controller를 FrontController라고 합니다.

하지만 이 방법은 예상 가능하게도 너무 많은 요청이 FrontController.java에 몰리게 됩니다.

 

 

(3) FrontController.java의 장점을 살리되 도메인(범주를 지정) 별로 분리를 하는 것이 좋을 것 같습니다.

User테이블

  로그인 요청과 회원가입 요청은 UserController.java에서 처리

Product테이블

  상품 주문 취소는 ProductController.java에서 처리

 

이 방법이 제일 좋아 보일 수 있지만 요청이 왔을 때 어느 Controller로 가야 할지 누가 판단해서 진행할까요?

이때 사용자의 요청들을 적절한 Controller로 배분해주는 기능을 하는 게 Dispatcher입니다.

  *  Dispatcher는 ServletDispatcher 또는 RequestDispatcher라고도 합니다. 

 

 

정리
사용자의 요청이 오면 앞단의 FrontController인 ServletDispatcher가 요청에 맞는 컨트롤러에게 요청을 전달합니다.
  여기서 컨트롤러는 도메인 별로 나뉘어 있습니다. (UserController, ImageController, ProductController.....)

관련 포스팅 : https://lealea.tistory.com/136

 

 

[ 요청과 응답 ]

부제 : HTTP 5가지 요청 방식과 Controller단의 2가지 응답 방식

 

클라이언트가 웹 서버에 *요청을 하고

웹 서버가 DB에 SELECT, INSERT, UPDATE, DELETE 요청을 해서 응답을 하는 구조입니다.

클라이언트 ↔ 웹 서버 ↔  DB

 

 *요청 방식

(1) GET : 데이터 요청

(2) POST : 데이터 전송 (예) 회원 등록)

(3) PUT :  전체 데이터 업데이트

(4) PATCH : 부분 데이터 업데이트

(5) DELETE : 데이터 삭제

 

 

응답 방식

웹서버가 클라이언트한테 응답할 때는 .html형식인 File로 응답을 해주거나 문자열과 같은 데이터로 응답해 줄 수 있습니다. (둘 중 하나만 사용 가능합니다.)

File을 응답하는 컨트롤러 : @Controller
Data를 응답하는 컨트롤러 : @RestController

 

 

[ 구체적인 데이터 요청 ]

부제: Controller단에서 uri를 통해 전달된 값을 받아오는 2가지 방식

  • @RequestParam
  • @PathVariable

 

이와 관련된 내용은 따로 포스팅하였습니다. 간단히 살펴보고 오시면 좋을 것 같습니다. 

 

 

[ HTTP Body에 데이터 실어 전송하기 ]

HTTP Body를 설명하기 전에 먼저 HTTP Header의 Content-Type에 대해 간단하게 알고 있어야 합니다.

쉽게 말하면 요청한 데이터의 '종류'를 명시해서 데이터를 받는 쪽에서 본문이 어떤 형식으로 되어있는지 알 수 있도록 해주는 장치입니다.

조금 더 자세한 내용은 HTTP 헤더 관련한 포스팅을 가볍게 읽고 오시면 될 것 같습니다.

 

주의할 점은 POST, PUT, PATCH요청 시 반드시 Content-Type을 명시해 주어야 합니다. 

 

 

데이터가 들어오면 기본적으로 x-www-form-urlencoded타입으로 인식하고 파싱 해주며 대표적으로 3가지 타입이 존재합니다.

  •  x-www-form-urlencoded 
    • default입니다.
    •  형태 : key=value 
  • text/plain
    • 형태: 문자(열)
  • application/json
    • 형태: {"id":"1", "username": "mango"}

 

 

[ HTTP 요청 시 Json으로도 응답받을 수 있고 Filie로도 응답받을 수 있습니다. ]

1. JavaObject를 Json으로 변경해서 응답받기

데이터를 리턴할 경우 어노테이션은 @RestController

 

도메인 생성 (User)

@Data
public class User {
    private String username;
}

 

Controller

@RestController
public class ObjectToJsonController {

    @GetMapping("/object/json")
    public User objectToJson(){
        User user = new User();
        user.setUsername("Lea");

        return user;
    }
}

 

Controller에서 user 오브젝트로 리턴했는데 응답받은 결과를 보니 Json 형태입니다.

이게 가능한 이유는 MessageConverter 클래스가 자동으로 JavaObject를 Json으로 변경해서 통신을 통해 응답을 해주기 때문입니다. (단, @RestController일 때만 MessageConverter 가 작동합니다.)

 

 

2. 파일 응답하기

파일을 리턴할 경우 어노테이션은 @Controller

  • 정적 파일 응답하기 (기본 경로는 resources/static)
    • HTTP 요청 시 브라우저가 파일을 읽어서 랜더링함
  • 템플릿 엔진 응답하기(resource/templates)
템플릿 엔진이란?
쉽게 말해 html 파일에 java 코드를 쓸 수 있는 파일입니다. (예 jsp, mustache, thymeleaf)
java코드를 적을 수 있다는 의미는 DB를 연결해서 .html파일에 값을 넣어 동적인 파일을 만들 수 있다는 것입니다.

템플릿 엔진을 사용할 경우 Web Server(아파치)에서 바로 브라우저로 응답 시 브라우저는 자바 코드 부분을 이해하지 못합니다.

따라서 Web Server는 WAS(톰캣)에게 파일을 전달하면 → 자바 코드를 해석해서 . html파일로 만들고 → 
파일을 클라이언트로 응답해줍니다. 


혹시 Web Server와 WAS의 개념이 헷갈리신다면 이 포스팅을 참고해주세요. 

 

그럼 .html파일에 어떻게 데이터가 동적으로 찍히게 되는 걸까요?

이걸 다르게 말하면 '어떻게 Controller에서 View에 데이터를 넘길 수 있나요?'라고 질문할 수 있을 것 같습니다.

 

이는 함수의 파라미터에 Model을 선언하고 model.addAttribute 함수로 값을 전달하면 됩니다. 엄청 간단하죠? ㅎㅎ

  • model.addAttribute(키, 밸류); 
    • 해쉬 맵 자료 구조로 밸류 자리는 오브젝트 타입이므로 모든 타입이 다 들어갈 수 있습니다.

코드를 통해 살펴보겠습니다.

HelloController 

package jpabook.jpashop;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class HelloController {

    @GetMapping("hello") // hello url로 오면 HelloController 호출
    public String hello(Model model) { // model에 data를 실어서 controller를 통해 view로 넘김
        model.addAttribute("data", "hello!!"); // 키 값
        return "hello"; // return은 화면 이름, resources > templates > hello.html로 이동 - ${data}
    }
}

 

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 적용, 기본적인 스프링 부트 Controller 동작 방식까지 알아보았습니다. 

다음 포스팅에서는 회원가입과 로그인 부분을 기술할 예정입니다.