์นดํ…Œ๊ณ ๋ฆฌ ์—†์Œ

STEP 02 - ๋กœ๊ทธ์ธ & ๋กœ๊ทธ์•„์›ƒ ๊ตฌํ˜„ํ•˜๊ธฐ

๋ฏธ๋กœ910 2024. 10. 8. 10:46
๐Ÿ’ก
Spring Boot์™€ JPA๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋กœ๊ทธ์ธ ๋ฐ ๋กœ๊ทธ์•„์›ƒ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

1. ๋กœ๊ทธ์ธ ์ฟผ๋ฆฌ ๋งŒ๋“ค๊ธฐ

๋จผ์ €, UserRepository๋ฅผ ํ†ตํ•ด ์‚ฌ์šฉ์ž ์ด๋ฆ„๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ๋กœ ์‚ฌ์šฉ์ž๋ฅผ ์กฐํšŒํ•˜๋Š” ๋ฉ”์„œ๋“œ๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค. ์ด ๋ฉ”์„œ๋“œ๋Š” JPQL์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ์‚ฌ์šฉ์ž๋ฅผ ๊ฒ€์ƒ‰ํ•ฉ๋‹ˆ๋‹ค.

package com.tenco.blog_v1.user;

import jakarta.persistence.EntityManager;
import jakarta.persistence.TypedQuery;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;

@RequiredArgsConstructor
@Repository // IoC
public class UserRepository {

    private final EntityManager em;

    /**
     * ์‚ฌ์šฉ์ž ์ด๋ฆ„๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ๋กœ ์‚ฌ์šฉ์ž ์กฐํšŒ
     * @param username
     * @param password
     * @return ์กฐํšŒ๋œ User ์—”ํ‹ฐํ‹ฐ, null
     */
    public User findByUsernameAndPassword(String username, String password) {
        TypedQuery<User> jpql =
                em.createQuery("SELECT u FROM User u WHERE u.username = :username AND u.password = :password", User.class);
        jpql.setParameter("username", username);
        jpql.setParameter("password", password);
        return jpql.getSingleResult();
    }

}

 

LoginDTO ๋งŒ๋“ค๊ธฐ

๋กœ๊ทธ์ธ ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด Data Transfer Object (DTO)๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค. ์ด๋Š” ํด๋ผ์ด์–ธํŠธ๋กœ๋ถ€ํ„ฐ ์ „๋‹ฌ๋ฐ›์€ ๋ฐ์ดํ„ฐ๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ๊ด€๋ฆฌํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

package com.tenco.blog_v1.user;

import lombok.Data;

@Data
public class UserDTO {

    // ์ •์  ๋‚ด๋ถ€ ํด๋ž˜๋กœ ๋ชจ์šฐ์ž
    @Data
    public static class LoginDTO {
        private String username;
        private String password;
    }

    // ์ •์  ๋‚ด๋ถ€ ํด๋ž˜๋กœ ๋ชจ์šฐ์ž
    @Data
    public static class JoinDTO {
        private String username;
        private String password;
        private String email;
    }

}

 

UserController ๊ตฌํ˜„

UserController๋ฅผ ๊ตฌํ˜„ํ•˜์—ฌ ๋กœ๊ทธ์ธ ๋ฐ ๋กœ๊ทธ์•„์›ƒ ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ์ด ์ปจํŠธ๋กค๋Ÿฌ๋Š” ์‚ฌ์šฉ์ž์˜ ๋กœ๊ทธ์ธ ์ •๋ณด๋ฅผ ๊ฒ€์ฆํ•˜๊ณ , ์„ธ์…˜์„ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

๋กœ๊ทธ์ธ ๋กœ๊ทธ์•„์›ƒ ๊ธฐ๋Šฅ ์ถ”๊ฐ€
package com.tenco.blog_v1.user;

import jakarta.servlet.http.HttpSession;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;

@RequiredArgsConstructor
@Slf4j
@Controller
public class UserController {

    // DI ์ฒ˜๋ฆฌ
    private final UserRepository userRepository;
    private final HttpSession session;

    /**
     * ์ž์›์— ์š”์ฒญ์€ GET ๋ฐฉ์‹์ด์ง€๋งŒ ๋ณด์•ˆ์— ์ด์œ ๋กœ ์˜ˆ์™ธ !
     * ๋กœ๊ทธ์ธ ์ฒ˜๋ฆฌ ๋ฉ”์„œ๋“œ
     * ์š”์ฒญ ์ฃผ์†Œ POST : http://localhost:8080/login
     * @param reqDto
     * @return
     */
    @PostMapping("/login")
    public String login(UserDTO.LoginDTO reqDto) {
        try {
            User sessionUser = userRepository.findByUsernameAndPassword(reqDto.getUsername(), reqDto.getPassword());
            session.setAttribute("sessionUser", sessionUser);
            return  "redirect:/";
        } catch (Exception e) {
            // ๋กœ๊ทธ์ธ ์‹คํŒจ
            return "redirect:/login-form?error";
        }
    }

    @GetMapping("/logout")
    public String logout() {
        session.invalidate(); // ์„ธ์…˜์„ ๋ฌดํšจํ™” (๋กœ๊ทธ์•„์›ƒ)
        return "redirect:/";
    }


    /**
     * ํšŒ์›๊ฐ€์ž… ํŽ˜์ด์ง€ ์š”์ฒญ
     * ์ฃผ์†Œ์„ค๊ณ„ : http://localhost:8080/join-form
     *
     * @param model
     * @return ๋ฌธ์ž์—ด
     * ๋ฐ˜ํ™˜๋˜๋Š” ๋ฌธ์ž์—ด์„ ๋ทฐ ๋ฆฌ์กธ๋ฒ„๊ฐ€ ์ฒ˜๋ฆฌํ•˜๋ฉฐ
     * ๋จธ์Šคํƒœ์น˜ ํ…œํ”Œ๋ฆฟ ์—”์ง„์„ ํ†ตํ•ด์„œ ๋ทฐ ํŒŒ์ผ์„ ๋ Œ๋”๋ง ํ•ฉ๋‹ˆ๋‹ค.
     */
    @GetMapping("/join-form")
    public String joinForm(Model model) {
        log.info("ํšŒ์›๊ฐ€์ž… ํŽ˜์ด์ง€");
        model.addAttribute("name", "ํšŒ์›๊ฐ€์ž… ํŽ˜์ด์ง€");
        return "user/join-form"; // ํ…œํ”Œ๋ฆฟ ๊ฒฝ๋กœ : user/join-form.mustache
    }

    /**
     * ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€ ์š”์ฒญ
     * ์ฃผ์†Œ์„ค๊ณ„ : http://localhost:8080/login-form
     *
     * @param model
     * @return ๋ฌธ์ž์—ด
     * ๋ฐ˜ํ™˜๋˜๋Š” ๋ฌธ์ž์—ด์„ ๋ทฐ ๋ฆฌ์กธ๋ฒ„๊ฐ€ ์ฒ˜๋ฆฌํ•˜๋ฉฐ
     * ๋จธ์Šคํƒœ์น˜ ํ…œํ”Œ๋ฆฟ ์—”์ง„์„ ํ†ตํ•ด์„œ ๋ทฐ ํŒŒ์ผ์„ ๋ Œ๋”๋ง ํ•ฉ๋‹ˆ๋‹ค.
     */
    @GetMapping("/login-form")
    public String loginForm(Model model) {
        log.info("๋กœ๊ทธ์ธ ํŽ˜์ด์ง€");
        model.addAttribute("name", "๋กœ๊ทธ์ธ ํŽ˜์ด์ง€");
        return "user/login-form"; // ํ…œํ”Œ๋ฆฟ ๊ฒฝ๋กœ : user/join-form.mustache
    }

    /**
     * ํšŒ์› ์ •๋ณด ์ˆ˜์ • ํŽ˜์ด์ง€ ์š”์ฒญ
     * ์ฃผ์†Œ์„ค๊ณ„ : http://localhost:8080/user/update-form
     *
     * @param model
     * @return ๋ฌธ์ž์—ด
     * ๋ฐ˜ํ™˜๋˜๋Š” ๋ฌธ์ž์—ด์„ ๋ทฐ ๋ฆฌ์กธ๋ฒ„๊ฐ€ ์ฒ˜๋ฆฌํ•˜๋ฉฐ
     * ๋จธ์Šคํƒœ์น˜ ํ…œํ”Œ๋ฆฟ ์—”์ง„์„ ํ†ตํ•ด์„œ ๋ทฐ ํŒŒ์ผ์„ ๋ Œ๋”๋ง ํ•ฉ๋‹ˆ๋‹ค.
     */
    @GetMapping("/user/update-form")
    public String updateForm(Model model) {
        log.info("ํšŒ์› ์ˆ˜์ • ํŽ˜์ด์ง€");
        model.addAttribute("name", "ํšŒ์› ์ˆ˜์ • ํŽ˜์ด์ง€");
        return "user/update-form"; // ํ…œํ”Œ๋ฆฟ ๊ฒฝ๋กœ : user/join-form.mustache
    }

}

 

header.mustache ์ฝ”๋“œ ์ˆ˜์ •
<!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="/">Metacoding</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">
                {{# sessionUser}}
                <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>
                {{/sessionUser}}

                {{^sessionUser}}
                <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>
                {{/sessionUser}}

            </ul>
        </div>
    </div>
</nav>

 

์„ค์ • ํ™•์ธ
spring:
  mustache:
    servlet:
      expose-session-attributes: true  # Mustache ????? ?? ??? ??? ? ??? ??
      expose-request-attributes: true  # Mustache ????? ?? ??? ??? ? ??? ??

 

๋กœ๊ทธ์ธ ์ „
๋กœ๊ทธ์ธ ํ›„