MVC / JPA - 실습

김민경·2022년 11월 7일
0

📌MVC 패턴 이란?

  • MVC 패턴은 디자인패턴 중 하나로 Model, View, Controller 의 약자이다.
  • 개발의 효율성, 유지보수성, 운용성이 높아지고 프로그램의 최적화에 도움이 된다.
  • 비즈니스 로직과 UI로직을 분리하여 유지보수를 독립적으로 수행할 수 있다.
  • Model과 View가 다른 컴포넌트들에 종속되지 않아 애플리케이션의 확장성, 유연성에 유리하다.
  • 중복 코딩의 문제점을 제거할 수 있다.
  1. User 접속 → Controller 조작(필요한 데이터를 User에게 요청받음)
  2. Model을 통해 요청받은 data를 가져와 View에게 전달
  3. View를 제어하여 최종 페이지를 생성하여 User에게 보여줌

Model(모델)

  • 데이터를 가진 객체
    • 사용자가 편집하기 원하는 모든 data를 가지고 있어야함
    • view 나 controller에 대한 어떠한 정보도 알지 말아야함
    • 변경이 일어날 경우 변경 통지에 대한 처리방법을 구현해야함

View(뷰)

  • 사용자가 요청한 데이터를 Model로 부터 받아와 HTML/CSS/Javascript 등의 기술들을 통하여 결과물을 만들어 보여줌
    • model이 가지고 있는 data를 따로 저장하면 안됨
    • model이나 controller와 같이 다른 구성 요소를 몰라야함
    • 변경이 일어날 경우 변경 통지에 대한 처리방법을 구현해야함

Controller(컨트롤러)

  • User가 접근한 URL에 따라 User가 요청하는 사항을 파악한 후 그 요청에 맞는 data를 Model에 의뢰하고, data를 View에 반영하여 User에게 알려줌(Model과 View를 연결시켜주는 역할)
    • Model 이나 View에 대해 알고 있어야함
    • Model 이나 View의 변경을 모니터링 해야함

📌 CRUD란?

  • 대부분의 컴퓨터 소프트웨어가 가지는 기본적인 데이터 처리 기능
    • Create(생성)
    • Read(읽기)
    • Update(갱신)
    • Delete(삭제)

💻 실습) SpringBoot MVC 이용하여 CRUD 기능 만들기

💚 project의 기본 정보와 Dependency 설정

  • Lombok : 코드 간결화, 로깅 기능
  • Sptring Web : MVC 프레임웍 사용 시 필요
  • Mustache : View 페이지를 머스테치 템플릿으로 작성
  • MySQL Driver : DB는 MySQL 사용

💚 git commit 하기 ⇒ docker 사용 시 git clone 하여 들고와야함

  • 터미널 사용
  1. git init
  2. git remote add origin [ git 리포지토리 주소 ]
  3. git add → git commit

💚 Controller 생성

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

@Controller
public class MustacheController {

    @GetMapping("/hi")
    public String mustacheCon(Model model){
        model.addAttribute("username","minkyoung");
        return "greetings";
    }
}
  • @Controller 어노테이션 ⇒ 컨트롤러로 지정하기
  • Model 객체 사용
    • Model 객체를 파라미터로 받아서 data를 View로 넘길 수 있다.
    • Model ⇒ 데이터를 담는 그릇 역할, map의 구조로 저장됨(key, value로 구성)
    • model.addAttribute("key", value); 으로 데이터를 담는다.
    • return "View파일명";

<div>
    <h1>{{username}} Hello World!</h1>
</div>
  • Controller에서 설정한 key 값인 username에 value 값을 넣는다.

💚 응용 GetMapping id값 받아서 넘기기

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

@Controller
public class MustacheController {

    @GetMapping("/hi")
    public String mustacheCon(Model model){
        model.addAttribute("username","minkyoung");
        return "greetings";
    }

    @GetMapping("/hi/{id}")
    public String mustacheCon1(@PathVariable String id, Model model){
        model.addAttribute("username","minkyoung");
        model.addAttribute("id",id);
        return "greetings";
    }

}
  • @PathVariable 어노테이션 사용하여 url의 {id} 값을 받아 올 수 있음
  • 나머지는 동일하게 greetings.mustache 로 값을 넘겨 View 페이지로 확인 가능

🤢 에러

<!--greetings.mustache-->
<div>
	<h1>{{id}} {{username}} Hello World! </h1>
</div>
  • 위의 HTML 코드는 localhost:8080/hi/{id} 로 두 값을 받을 때만 사용할 수 있음
  • 한개의 값만 받는 localhost:8080/hi 로 URL 입력 시 에러 발생
  • 아래의 코드로 해결가능
<!--greetings.mustache-->
<div>
    {{# id}}
        <h1>{{id}} {{username}} Hello World! </h1>
    {{/ id}}
    {{^ id}}
        <h1>{{username}} Hello World! </h1>
    {{/ id}}
</div>

💚 한글깨짐 에러 해결

  • application.yml 파일에 아래 코드 입력
<!--application.yml-->
server:
  port: 8080
  servlet:
    encoding:
      force-response: true

💚 BootStrap 적용하기

👉 https://getbootstrap.com/

  • 메인 페이지에서 조금 내려오면 quick start 버튼 클릭
  • 2번 코드 복붙
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Bootstrap demo</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-Zenh87qX5JnK2Jl0vWa8Ck2rdkQ2Bzep5IDxbcnCeuOxjzrPF/et3URy9Bv1WTRi" crossorigin="anonymous">
  </head>
  <body>
    <h1>Hello, world!</h1>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-OERcA2EqjJCMA+/3y+gxIOqMEjwtxJY7qPCqsdltbNJuaOe923+mo//f6V8Qbsw3" crossorigin="anonymous"></script>
  </body>
</html>
  • 적용 코드 보기
    <!doctype html>
    <html lang="en">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Bootstrap demo</title>
        <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-Zenh87qX5JnK2Jl0vWa8Ck2rdkQ2Bzep5IDxbcnCeuOxjzrPF/et3URy9Bv1WTRi" crossorigin="anonymous">
    </head>
    <body>
    <nav class="navbar navbar-expand-lg bg-light">
        <div class="container-fluid">
            <a class="navbar-brand" href="#">Navbar</a>
            <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
                <span class="navbar-toggler-icon"></span>
            </button>
            <div class="collapse navbar-collapse" id="navbarSupportedContent">
                <ul class="navbar-nav me-auto mb-2 mb-lg-0">
                    <li class="nav-item">
                        <a class="nav-link active" aria-current="page" href="#">Home</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="#">Link</a>
                    </li>
                    <li class="nav-item dropdown">
                        <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
                            Dropdown
                        </a>
                        <ul class="dropdown-menu">
                            <li><a class="dropdown-item" href="#">Action</a></li>
                            <li><a class="dropdown-item" href="#">Another action</a></li>
                            <li><hr class="dropdown-divider"></li>
                            <li><a class="dropdown-item" href="#">Something else here</a></li>
                        </ul>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link disabled">Disabled</a>
                    </li>
                </ul>
                <form class="d-flex" role="search">
                    <input class="form-control me-2" type="search" placeholder="Search" aria-label="Search">
                    <button class="btn btn-outline-success" type="submit">Search</button>
                </form>
            </div>
        </div>
    </nav>
    <div>
        {{# id}}
            <h1>{{id}} {{username}} Hello World! </h1>
        {{/ id}}
        {{^ id}}
            <h1>{{username}} Hello World! </h1>
        {{/ id}}
    </div>
    <button type="button" class="btn btn-primary">Primary</button>
    <div class="container">
        <footer class="d-flex flex-wrap justify-content-between align-items-center py-3 my-4 border-top">
            <div class="col-md-4 d-flex align-items-center">
                <a href="/" class="mb-3 me-2 mb-md-0 text-muted text-decoration-none lh-1">
                    <svg class="bi" width="30" height="24"><use xlink:href="#bootstrap"></use></svg>
                </a>
                <span class="mb-3 mb-md-0 text-muted">© 2022 Company, Inc</span>
            </div>
    
            <ul class="nav col-md-4 justify-content-end list-unstyled d-flex">
                <li class="ms-3"><a class="text-muted" href="#"><svg class="bi" width="24" height="24"><use xlink:href="#twitter"></use></svg></a></li>
                <li class="ms-3"><a class="text-muted" href="#"><svg class="bi" width="24" height="24"><use xlink:href="#instagram"></use></svg></a></li>
                <li class="ms-3"><a class="text-muted" href="#"><svg class="bi" width="24" height="24"><use xlink:href="#facebook"></use></svg></a></li>
            </ul>
        </footer>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-OERcA2EqjJCMA+/3y+gxIOqMEjwtxJY7qPCqsdltbNJuaOe923+mo//f6V8Qbsw3" crossorigin="anonymous"></script>
    </body>
    </html>

💚 template 에 layouts 패키지 생성하여 NavBar, Footers 고정하기

  • 모든 페이지에서 NavBar와 Footers는 위, 아래에 고정되어있어야함
  • header.mustache, footer.mustache로 분리하기
<!--greetings.mustache-->

{{>layouts/header}}
<div>
    {{# id}}
        <h1>{{id}} {{username}} Hello World! </h1>
    {{/ id}}
    {{^ id}}
        <h1>{{username}} Hello World! </h1>
    {{/ id}}
</div>
{{>layouts/footer}}
  • {{>layouts/header}}, {{>layouts/footer}} ⇒ 해당 구간에 있던 코드는 header, footer.mustache 파일로 분리하고 위의 코드를 입력해주기

💚 ArticleController 생성

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/articles")
@Slf4j
public class ArticleController {
	// 새 글 작성 page
    @GetMapping(value = "/new")
    public String newArticleForm() {
        return "articles/new";
    }
}
  • @RequestMapping ⇒ 공통으로 사용할 url 설정 (반복해서 적어줄 필요없음)
  • @Slf4j ⇒ 로깅설정

💚 form 태그 사용

  • form 태그 ⇒ 사용자의 의견이나 정보를 알기 위해 입력할 큰 틀을 만드는데 사용
    - action : 폼을 전송할 서버 쪽 스크립트 파일 지정
    - name : 폼을 식별하기 위한 이름 지정
    - accept-charset : 폼 전송에 사용할 문자 인코딩 지정
    - target : action에서 지정한 스크립트 파일을 현재 창이 아닌 다른 위치에 열도록 지정
    - method : 폼을 서버에 전송할 http 메서드 지정(get 또는 post)
{{>layouts/header}}
<form action="/articles/posts" method="post">
    <input type="text" name="title">
    <input type="text" name="content">
    <button type="submit" class="btn btn-primary">Submit</button>
    <a href="/articles">back</a>
</form>
{{>layouts/footer}}

💚 Dto 생성

  • ArticleDto를 생성하여 User가 입력한 정보를 담아 올 수 있다.
  • @Getter, @ToString 롬복기능을 사용
  • toEntity() 메서드 사용하여 title, content 정보가 담긴 Article 타입의 객체 생성
import com.mustache.bbs1.domain.entity.Article;
import lombok.Getter;
import lombok.ToString;

@Getter
@ToString
public class ArticleDto {
    private Long id;
    private String title;
    private String content;

    public ArticleDto(Long id, String title, String content) {
        this.id = id;
        this.title = title;
        this.content = content;
    }

    public Article toEntity() {
        return new Article(title, content);
    }

}

💚 @Entity 생성

  • DB의 테이블과 1:1로 매칭되는 객체 단위
  • Entity 객체의 인스턴스 하나가 테이블에서 하나의 레코드 값을 의미
  • Primary Key인 Id를 @Id로 선언해줘야함
  • @Entity 선언 시 dependency 추가
    • build.gradle 에서 확인
  • @GeneratedValue
    • id 값을 직접생성하지 않고 자동으로 생성해줌
    • @GeneratedValue(strategy = GenerationType.IDENTITY)
    • 기본키 생성을 데이터베이스에게 위임하는 방식으로 id 값을 따로 할당하지 않아도 데이터베이스가 자동으로 AUTO_INCREMENT를 하여 기본키를 생성
      👉 참고 사이트 : https://velog.io/@gudnr1451/GeneratedValue-%EC%A0%95%EB%A6%AC

import lombok.Getter;
import lombok.NoArgsConstructor;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
@NoArgsConstructor
@Getter
public class Article {

    @Id
    @GeneratedValue
    private Long id;

    private String title;
    private String contents;

    public Article(String title, String contents) {
        this.title = title;
        this.contents = contents;
    }

}

💚 Repository 인터페이스 생성

  • Entity에 의해 생성된 DB에 접근하는 메서드들을 사용하기 위한 인터페이스
  • @Entity 어노테이션을 통해 데이터베이스 구조를 만들고 이곳에 CRUD를 설정해주는 것
    - extends JpaRepository<Entity클래스명, Entity클래스의 PK의 자료형>
    - 위의 상속을 사용하여 JPA로 Repository 생성
package com.mustache.bbs1.repository;

import com.mustache.bbs1.domain.entity.Article;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface ArticleRepository extends JpaRepository<Article, Long> {
    
}

💚 Controller 완성

  • ArticleRepository 인터페이스를 사용하기 위해 final로 선언하고 생성자를 만들어줌
  • JPA 의 save 함수를 사용
import com.mustache.bbs1.domain.dto.ArticleDto;
import com.mustache.bbs1.domain.entity.Article;
import com.mustache.bbs1.repository.ArticleRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/articles")
@Slf4j
public class ArticleController {

    private final ArticleRepository articleRepository;

    public ArticleController(ArticleRepository articleRepository) {
        this.articleRepository = articleRepository;
    }

    @GetMapping(value = "/new")
    public String newArticleForm() {
        return "articles/new";
    }

    @PostMapping(value = "/posts")
    public String createArticle(ArticleDto form) {
        log.info(form.toString());
        Article article =form.toEntity();
        articleRepository.save(article);
        return "";
    }
}

💚 환경변수, jpa 설정(.yml)

<!-- application.yml -->

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:8080/likelion-db
    username: root
    password: 1q2w3e4r
  jpa:
    show-sql: true
    database-platform: org.hibernate.dialect.MySQL8Dialect
    database : mysql
    hibernate.ddl-auto : update
  • spring > jpa
    • show-sql: true ⇒ jpa가 자동으로 만들어주는 query문을 console에서 볼 것인지 여부
    • database-platform ⇒ 사용할 DB의 Dialect설정
    • database : mysql ⇒ 사용할 DB 종류
    • hibernate.ddl-auto : update ⇒ 가장 중요한 옵션
      • DDL-auto 종류(create, update 사용 시 주의!)

0개의 댓글