Spring boot

(스프링 부트 입문)스프링 블로그 프로젝트 학습을 위한 기본 뼈대 코드 만들기

mynote6676 2025. 6. 20. 23:14

파일 작성

더보기

 

기본 시안

기본 페이지

 회원 가입

글쓰기 화면입니다.

 

회원수정

 

로그인 

제목자리

 

기본 머스태치 파일 시안 확인

더보기
<!DOCTYPE html>
<html lang="en">

<head>
    <title>Blog</title>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"/>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
</head>

<body>
<nav class="navbar navbar-expand-sm bg-dark navbar-dark">
    <div class="container-fluid">
        <a class="navbar-brand" href="/">Tencoding</a>
        <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#collapsibleNavbar">
            <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="collapsibleNavbar">
            <ul class="navbar-nav">
                <li class="nav-item">
                    <a class="nav-link" href="/join-form">회원가입</a>
                </li>
                <li class="nav-item">
                    <a class="nav-link" href="/login-form">로그인</a>
                </li>
                <li class="nav-item">
                    <a class="nav-link" href="/board/save-form">글쓰기</a>
                </li>
                <li class="nav-item">
                    <a class="nav-link" href="/user/update-form">회원정보보기</a>
                </li>
                <li class="nav-item">
                    <a class="nav-link" href="/logout">로그아웃</a>
                </li>
            </ul>
        </div>
    </div>
</nav>

 

layout/footer.mustache

<div class="bg-light p-5 text-center">
    <h4>Created by Tencoding</h4>
    <h5>☎ 010-1234-1234</h5>
    <button class="btn btn-outline-primary">고객센터</button>
    <button class="btn btn-outline-primary">오시는길</button>
</div>
</body>

</html>

 

index.mustache

{{> layout/header}}

<div class="container p-5">

    <div class="card mb-3">
        <div class="card-body">
            <h4 class="card-title mb-3">제목1</h4>
            <a href="/board/1" class="btn btn-primary">상세보기</a>
        </div>
    </div>

    <ul class="pagination d-flex justify-content-center">
        <li class="page-item disabled"><a class="page-link" href="#">Previous</a></li>
        <li class="page-item"><a class="page-link" href="#">Next</a></li>
    </ul>

</div>

{{> layout/footer}}

 

user/join-form.mustache

{{> layout/header}}

<div class="container p-5">

    <!-- 요청을 하면 localhost:8080/join POST로 요청됨
    username=사용자입력값&password=사용자값&email=사용자입력값 -->

    <div class="card">
        <div class="card-header"><b>회원가입을 해주세요</b></div>
        <div class="card-body">
            <form action="/join" method="post" enctype="application/x-www-form-urlencoded">
                <div class="mb-3">
                    <input type="text" class="form-control" placeholder="Enter username" name="username">
                </div>
                <div class="mb-3">
                    <input type="password" class="form-control" placeholder="Enter password" name="password">
                </div>
                <div class="mb-3">
                    <input type="email" class="form-control" placeholder="Enter email" name="email">
                </div>
                <button type="submit" class="btn btn-primary form-control">회원가입</button>
            </form>
        </div>
    </div>
</div>

{{> layout/footer}}

 

user/login-form.mustache

{{> layout/header}}

<div class="container p-5">

    <!-- 요청을 하면 localhost:8080/login POST로 요청됨
    username=사용자입력값&password=사용자값 -->
    <div class="card">
        <div class="card-header"><b>로그인을 해주세요</b></div>
        <div class="card-body">
            <form action="/login" method="post" enctype="application/x-www-form-urlencoded">
                <div class="mb-3">
                    <input type="text" class="form-control" placeholder="Enter username" name="username">
                </div>
                <div class="mb-3">
                    <input type="password" class="form-control" placeholder="Enter password" name="password">
                </div>
                <button type="submit" class="btn btn-primary form-control">로그인</button>
            </form>
        </div>
    </div>
</div>

{{> layout/footer}}

 

  • user/update-form.mustache
  •  
{{> layout/header}}

<div class="container p-5">

    <!-- 요청을 하면 localhost:8080/join POST로 요청됨
    username=사용자입력값&password=사용자값&email=사용자입력값 -->

    <div class="card">
        <div class="card-header"><b>회원수정을 해주세요</b></div>
        <div class="card-body">
            <form action="/user/update" method="post" enctype="application/x-www-form-urlencoded">
                <div class="mb-3">
                    <input type="text" class="form-control" placeholder="Enter username" name="username" disabled>
                </div>
                <div class="mb-3">
                    <input type="password" class="form-control" placeholder="Enter password" name="password">
                </div>
                <div class="mb-3">
                    <input type="email" class="form-control" placeholder="Enter email" name="email" disabled>
                </div>
                <button type="submit" class="btn btn-primary form-control">회원가입수정</button>
            </form>
        </div>
    </div>
</div>

{{> layout/footer}}

 

board/save-form.mustache

{{> layout/header}}

<div class="container p-5">
    <div class="card">
        <div class="card-header"><b>글쓰기 화면입니다</b></div>
        <div class="card-body">
            <!--
            action="/board/save": 폼 제출시 /board/save 경로로 요청
            method="post": HTTP POST 방식으로 데이터 전송
            GET: URL에 데이터 노출, POST: 요청 본문에 데이터 숨김
            -->
            <form action="/board/save" method="post">
                <!--
                name 속성: 서버에서 받을 때 사용하는 변수명
                name="username" → 컨트롤러에서 String username으로 받음
                -->
                <div class="mb-3">
                    <input type="text" class="form-control" placeholder="Enter username" name="username">
                </div>
                <div class="mb-3">
                    <input type="text" class="form-control" placeholder="Enter title" name="title">
                </div>
                <div class="mb-3">
                    <!-- textarea: 여러 줄 텍스트 입력 가능 -->
                    <textarea class="form-control" rows="5" name="content"></textarea>
                </div>
                <!-- 버튼 클릭시 폼이 제출됨 -->
                <button class="btn btn-primary form-control">글쓰기완료</button>
            </form>
        </div>
    </div>
</div>

{{> layout/footer}}

 

board/detail.mustache

{{> layout/header}}

<div class="container p-5">

    <!-- 수정삭제버튼 -->
    <div class="d-flex justify-content-end">
        <button class="btn btn-warning me-1">수정</button>
        <button class="btn btn-danger">삭제</button>
    </div>
    <div class="d-flex justify-content-end">
        <b>작성자</b> : ssar
    </div>

    <!-- 게시글내용 -->
    <div>
        <h2><b>제목자리</b></h2>
        <hr />
        <div class="m-4 p-2">
            내용입니다
        </div>
    </div>

    <!-- 댓글 -->
    <div class="card mt-3">
        <!-- 댓글등록 -->
        <div class="card-body">
            <form action="/reply/save" method="post">
                <textarea class="form-control" rows="2" name="comment"></textarea>
                <div class="d-flex justify-content-end">
                    <button type="submit" class="btn btn-outline-primary mt-1">댓글등록</button>
                </div>
            </form>
        </div>

        <!-- 댓글목록 -->
        <div class="card-footer">
            <b>댓글리스트</b>
        </div>
        <div class="list-group">
            <!-- 댓글아이템 -->
            <div class="list-group-item d-flex justify-content-between align-items-center">
                <div class="d-flex">
                    <div class="px-1 me-1 bg-primary text-white rounded">cos</div>
                    <div>댓글 내용입니다</div>
                </div>
                <form action="/reply/1/delete" method="post">
                    <button class="btn">🗑</button>
                </form>
            </div>
            <!-- 댓글아이템 -->
            <div class="list-group-item d-flex justify-content-between align-items-center">
                <div class="d-flex">
                    <div class="px-1 me-1 bg-primary text-white rounded">ssar</div>
                    <div>댓글 내용입니다</div>
                </div>
                <form action="/reply/1/delete" method="post">
                    <button class="btn">🗑</button>
                </form>
            </div>
        </div>
    </div>
</div>

{{> layout/footer}}

 

개발 환경 설정 확인

더보기

application-dev.yml

server:
  servlet:
    encoding:
      charset: utf-8
      force: true
  port: 8080

# 로그 레벨의 개념
# ERROR > WARN > INFO > DEBUG > TRACE
logging:
  level:
    root: INFO  # 모든 라이브러리는 INFO 이상만 출력
    com.tenco: DEBUG # 내 프로젝트는 DEBUG 이상 모두 출력

# spring 환경 설정 
spring:
  mustache:
    # 템플릿 파일의 확장자를 .mustache로 지정하여 Mustache
    # 템플릿 파일을 인식하도록 설정
    suffix: .mustache
    # 템플릿 파일이 위치한
    # 기본 경로를 src/main/resources/templates/로 설정
    prefix: classpath:/templates/
    # 개발 중 템플릿 캐시를 비활성화하여 코드
    # 수정 후 바로 반영되도록 함 (프로덕션에서는 true로 설정 권장)
    cache: false
    # 템플릿 파일을 UTF-8 인코딩으로 처리하여 한글 등 다양한 문자 지원
    charset: UTF-8
    servlet:
      # 세션과 요청 속성을 템플릿에서 사용 가능하게 함
      expose-session-attributes: true
      expose-request-attributes: true
  # 데이터 베이스 연결 설정 (H2 인메모리 데이터베이스)
  datasource:
    #driver-class-name: com.mysql.cj.jdbc.Driver
    #url: jdbc:mysql://localhost:3306/blog?useSSl=false&serverTimezone=Asia/Seoul
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/blog?useSSl=false&serverTimezone=Asia/Seoul
    username: root
    password: asd1234
  # H2 데이터베이스 웹 콘솔 활성화 (개발용)
  # http://localhost:8080/h2-console
  h2:
    console:
      enabled: true

  sql:
    init:
      # 2.5 이상 버전부터 명시를 해야 insert 처리 됨
      mode: always
      data-locations:
        - classpath:db/data.sql

  # JPA 설정
  jpa:
    hibernate:
      # create: 애플리케이션 시작시 테이블을 새로 생성
      # 기존 데이터는 모두 삭제됨 (개발용)
      ddl-auto: create
    # SQL 쿼리를 콘솔에 출력 (개발용 디버깅)
    show-sql: true
    properties:
      hibernate:
        # SQL 쿼리를 보기 좋게 포맷팅
        format_sql: true
    # data.sql 파일을 Hibernate 초기화 이후에 실행
    defer-datasource-initialization: true
    # 기본값 : 엔티티 - 카멜케이스 -> db 생성될 스네이크 케이스로 자동으로 설정 됨

 

Board 엔티티 만들어 보기

model/Board.java

package com.tenco.blog.model;


import jakarta.persistence.*;
import lombok.Data;

import java.sql.Timestamp;

@Data
// @Table : 실제 데이터베이스 테이블 명을 지정할 때 사용
@Table(name = "board_tb")
// @Entity : JPA가 이 클래스를 테이터베이스 테이블과 매핑하는 객체(엔티티)로 인식
// 즉, @Entity 어노테이션이 있어야 JPA가 이 객체를 관리 한다.
@Entity
public class Board {

    // @Id 이 필드가 기본키(Primary key) 임을 나타냄
    @Id
    // DENTITY 전략 : 데이터베이스의 기본 전략을 사용한다. -> Auto_Increment
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    // 별도 어노테이션이 없으면 필드명이 컬럼명이 됨
    private String title;
    private String content;
    private String username;
    private Timestamp createdAt; // created_at (스네이크 케이스로 자동 변환) 
}

 

db/data.sql // 더미 파일

insert into board_tb(title, content, username, created_at) values('제목1','내용1','ssar',now());
insert into board_tb(title, content, username, created_at) values('제목2','내용2','ssar',now());
insert into board_tb(title, content, username, created_at) values('제목3','내용3','cos',now());
insert into board_tb(title, content, username, created_at) values('제목4','내용4','love',now());

화면 연결 설정

더보기

BoardController 코드 추가

package com.tenco.blog.controller;

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

@Controller // IoC 대상 - 싱글톤 패턴으로 관리 됨
public class BoardController {

    @GetMapping({"/", "/index"})
    public String index() {
        //    prefix: /templates/
        //    return : index
        //    suffix: .mustache
        //    # 기본 경로를 src/main/resources/templates/index.mustache
        return "index";
    }

    @GetMapping("/board/save-form")
    public String saveFrom() {
        // /templates//board
        // /templates/board/
        return "board/save-form";
    }

    //

    /**
     * 상세보기 화면 요청
     * board/1
     */
    @GetMapping("/board/{id}")
    public String detail(@PathVariable(name = "id") Integer id) {
        // URL 에서 받은 id 값을 사용해서 특정 게시글 상세보기 조회
        // 실제로는 이 id값으로 데이터베이스에 있는 게시글 조회 하고
        // 머스태치 파일로 데이터를 내려 주어야 함 (Model)
        return "board/detail";
    }

}

 

Usercontroller 코드 추가

package com.tenco.blog.controller;

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

@Controller
public class UserController {

    // 요청 되어 오는 주소 -> /join-form
    @GetMapping("/join-form")
    public String joinForm() {
        return "user/join-form";
    }

    @GetMapping("/login-form")
    public String loginForm() {
        // 반환값이 뷰(파일) 이름이 됨 (뷰 리졸버가 실제 파일 경로를 찾아 감)
        return "user/login-form";
    }

    // 주소 설계 : http://localhost:8080/user/update-form
    @GetMapping("/user/update-form")
    public String updateForm() {
        return "user/update-form";
    }


    @GetMapping("/logout")
    public String logout() {
        // "redirect: " 스프링 에서 접두사를 사용하면 다른 URL 로 리다이렉트 됨
        // 즉 리다렉트 한다는 것은 뷰를 렌더링 하지 않고 브라우저가 재 요청을
        // 다시 하게끔 유도 한다.
        return "redirect:/";
    }

}

리다이렉트 추가 정리

1. 요청-응답, 재요청-응답 흐름

2. 클라이언트가 서버에 요청을 보냄(예:/ logout).

3.서버는 리다이렉트 응답(HTTP 302 상태 코드 + 새로운 URL)을 반환.

4.클라이언트는 새로운 URL(예:/)로 재요청

5. 서버는 재요청된 URL에 대한 응답(예: 홈페이지 HTML)을 반환.

728x90