๐Ÿฏ[TIL] 250708-027

byoยท2025๋…„ 7์›” 9์ผ

๐Ÿ’ซ JAVA

๐Ÿ To-do-list with DTO

๐ŸŒฟ to_do_list_with_dto.git

์ผ๋‹จ ์‚ฌ์šฉ์ž๋ถ€ํ„ฐ...

โž• ADD ์‚ฌ์šฉ์ž๋Š” ์ผ๋‹จ add๋งŒ! ํšŒ์› ํŽธ์ง‘/ํƒˆํ‡ด๋Š” ์ผ๋‹จ ๋‚˜์ค‘์—.. ์กฐํšŒ๋Š” ํ•„์š”์—†๋‹ค

๐Ÿง  User.java

@Data
@Builder
public class User {
    private Integer id;
    private String username;
    private String password;
}
  • ํ‰๋ฒ”ํ•œ ์‚ฌ์šฉ์ž ์—”ํ‹ฐํ‹ฐ์˜ ๋ชจ์Šต์ด๋‹ค.
  • ์—ฌ๊ธฐ์„œ username์ด unique ํ•˜๋‹ค๋ฉด? ํšŒ์›๊ฐ€์ž…/๋กœ๊ทธ์ธ ํ•  ๋•Œ id๋Š” ํ•„์š”์—†๋‹ค.

๐Ÿ—„๏ธ UserRepository.java

@Repository
@RequiredArgsConstructor
public class UserRepository {
    private final JdbcTemplate jdbcTemplate;

    private final RowMapper<User> userRowMapper = (resultSet, rowNum) -> {
        User user = User.builder()
                .id(resultSet.getInt("id"))
                .username(resultSet.getString("username"))
                .password(resultSet.getString("password"))
                .build();
        return user;
    };

    public User findByUsername(String username) {
        String sql = "SELECT * FROM users WHERE username = ?";
        try {
            return jdbcTemplate.queryForObject(sql, userRowMapper, username);
        } catch (EmptyResultDataAccessException e) {
            return null;
        }
    }

    public int save(User user) {
        String sql = "INSERT INTO users (username, password) VALUES (?, ?)";
        return jdbcTemplate.update(sql, user.getUsername(), user.getPassword());
    }
}
  • ์ผ๋‹จ save ๊ธฐ๋Šฅ๋งŒ ์ž‘์„ฑํ•ด๋„ ๋˜๋Š”๋ฐ, ํšŒ์›๊ฐ€์ž… ์‹œ ์ด๋ฏธ ์กด์žฌํ•˜๋Š” username์„ ์ž…๋ ฅํ•˜๋ฉด ์—๋Ÿฌ์ฒ˜๋ฆฌ๋ฅผ ํ•˜๊ธฐ ์œ„ํ•ด findByUsername์ด๋ž‘ jdbc ์‚ฌ์šฉ์„ ์œ„ํ•œ mapper ๊นŒ์ง€ ๋งŒ๋“ค์–ด๋‘”๋‹ค. repository๋Š” dto๋ž‘ ๊ด€๊ณ„์—†์ด ํ•˜๋˜๋Œ€๋กœ crud๋ฅผ ์ž‘์„ฑ์„ ์ผ๋‹จ ํ•˜๋ฉด๋œ๋‹ค.

๐Ÿ’ซ ํšŒ์›๊ฐ€์ž… ๊ธฐ๋Šฅ
๐Ÿšš SignupDto.java

@Getter
@Setter
public class SignupDto {
    @NotBlank(message = "์•„์ด๋””๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”")
    @Size(min=3, max=10, message = "์•„์ด๋””๋Š” 3~10์ž์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค")
    private String username;

    @NotBlank(message = "๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”")
    @Size(min=6, max=20, message = "๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” 6~20์ž์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค")
    private String password;
}
  • ํšŒ์›๊ฐ€์ž… dto์˜ ๋ชจ์Šต์ด๋‹ค..
  • ์—”ํ‹ฐํ‹ฐ์—์„œ ํ•„์š”ํ•œ ๊ฐ’๋งŒ ๋–ผ์˜ฌ์ˆ˜์žˆ๋‹ค.. ๋งˆ์น˜ ํ…Œ์ด๋ธ”์˜ ๋ทฐ๊ฐ™๋‹ค..
    ๊ทผ๋ฐ ์ด์ œ ๋ฐ์ดํ„ฐ์—†๊ณ  ํ‹€๋งŒ ์žˆ๋Š” ๋ทฐ..๋Š” ๋‚ด๋‡Œํ”ผ์…œ์ด๋‹ค..
  • โœ… validation
    ์ž์นด๋ฅดํƒ€ ๋ฐธ๋ฆฌ๋ฐ์ด์…˜์ด ์–ด๋…ธํ…Œ์ด์…˜๋งŒ ๋‹ฌ๋ฉด ์•Œ์•„์„œ ๋นˆ์นธ์ด๋ž‘ ๋ฌธ์ž์—ด ๊ธธ์ด๊นŒ์ง€ ์ฒดํฌํ•ด์ค€๋‹ค. wow ๊ฑฐ์˜ ๋งˆ๋ฒ•. ๊ทผ๋ฐ ์ด์ œ ๊ฐ‘์ž๊ธฐ ๋งˆ์ฃผ์น˜๋‹ˆ๊น ์•„์ง ์ต์ˆ™ํ•˜์ง„ ์•Š๋‹ค..

๐Ÿ“ก SignupController.java

@Controller
@RequiredArgsConstructor
public class SignupController {
    private final UserRepository userRepository;

    @GetMapping("/signup")
    public String showSingup(Model model) {
        model.addAttribute("signupDto", new SignupDto());
        return "signup";
    }

    @PostMapping("/signup")
    public String doSignup(
            @Valid @ModelAttribute("signupDto") SignupDto signupDTO,
            BindingResult bindingResult,
            Model model
    ) {
        if (bindingResult.hasErrors()) {
            return "signup";
        }

        // ์ค‘๋ณต ๊ฐ€์ž… ์—ฌ๋ถ€ ์ฒดํฌ
        if (userRepository.findByUsername((signupDTO.getUsername())) != null) {
            model.addAttribute("error", "์ด๋ฏธ ์‚ฌ์šฉ์ค‘์ธ ์•„์ด๋””์ž…๋‹ˆ๋‹ค");
            return "signup";
        }

        User user = User.builder()
                .username(signupDTO.getUsername())
                .password(signupDTO.getPassword())
                .build();

        userRepository.save(user);
        System.out.println("signupDto is null? " + (signupDTO == null));

        return "redirect:/login?registered";
    }
}
๊ตฌ๋ฌธ์„ค๋ช…
showSingupํด๋ผ์ด์–ธํŠธ(์›น์„œ๋ฒ„)๊ฐ€ get ์š”์ฒญ์„ ํ–ˆ์„๋•Œ = ์ฃผ์†Œ์ฐฝ์— /signup ์ฃผ์†Œ๋กœ ๋“ค์–ด์™”์„ ๋•Œ ์ผ๋‹จ ํšŒ์›๊ฐ€์ž… dto๋ฅผ ๋ชจ๋ธ์— ๋‹ด์•„์„œ ๋ณด๋‚ด๋ฉด ๋œ๋‹ค.
doSignupํด๋ผ์ด์–ธํŠธ๊ฐ€ form[method=post]์„ ํ†ตํ•ด model์— th:object="${signupDto}" ์ด๋ ‡๊ฒŒ dto๋ฅผ ๋‹ด์•„ ๋ณด๋‚ด์ค„๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์—, dto๋ฅผ ๋ฐ›์•„์ฃผ๋Š” @Valid @ModelAttribute("signupDto") SignupDto signupDTO ์ถ”๊ฐ€ํ•œ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ์•Œ์•„์„œ ๋ฐ์ดํ„ฐ๊ฐ€ dto์— ๋ฐ›์•„์ง€๋ฉด์„œ validation ์ฒดํฌ๋ฅผ ํ•ด์ค€๋‹ค. ์™€!
if (bindingResult.hasErrors())@Valid์˜ ๊ฒฐ๊ณผ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋Š” bindingResult์— ๋‹ด๊ธฐ๊ธฐ ๋•Œ๋ฌธ์— ์„ ์–ธ์„ ํ•ด์ฃผ์–ด์•ผ ํ•œ๋‹ค.. ๊ทธ๋ฆฌ๊ณ  ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋ฅผ ํ™”๋ฉด์— ์ถœ๋ ฅ์„ ํ•ด์ค˜์•ผ ํ•˜๊ธฐ๋•Œ๋ฌธ์— ์–ด์จŒ๋“  signup ํ™”๋ฉด์„ ๋‹ค์‹œ ๋„์›Œ์ค€๋‹ค.
์ค‘๋ณต ๊ฐ€์ž… ์—ฌ๋ถ€ ์ฒดํฌ์ง€๊ธˆ ๋‹จ๊ณ„์—์„  Repository์— findByUsername ์ฟผ๋ฆฌ๊ฐ€ ์—†๊ธฐ๋•Œ๋ฌธ์— ์ผ๋‹จ ํŒจ์Šค
User user = User.builder() ใ…คใ…คใ…คใ…คใ…คใ…คใ…คใ…คใ…คใ…คใ…คใ…คใ…คใ…คใ…คใ…คใ…คใ…คใ…คใ…คใ…คใ…คใ…คใ…คใ…ค์ „์— ํ• ๋• ํด๋ผ์ด์–ธํŠธ์—์„œ ๋˜์ง„ ์—”ํ‹ฐํ‹ฐ ์ž์ฒด๋ฅผ ๊ทธ๋Œ€๋กœ ๋ฆฌํฌ์ง€ํ† ๋ฆฌ์— ์ €์žฅํ•˜๋ฉด ๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ์ปจํŠธ๋กค๋Ÿฌ์—์„œ new๋กœ ์ƒ์„ฑํ•ด์ค„ ํ•„์š”๊ฐ€ ์—†์—ˆ๋Š”๋ฐ ์ด์   save ํ•˜๊ธฐ ์œ„ํ•ด์„  dto๋กœ ๋ฐ›์€ ๋‚ด์šฉ์„ entity๋กœ ๋ณ€ํ™˜ํ•ด์„œ ์ €์žฅํ•ด ์ฃผ์–ด์•ผ ํ•œ๋‹ค..

๐Ÿ–ผ๏ธ Signup.html

<body>
<h1>๐Ÿ“ ํšŒ์›๊ฐ€์ž…</h1>
<div th:if="${error}" class="error" th:text="${error}"></div>
<form th:action="@{/signup}" th:object="${signupDto}" method="post" class="form-container">
    <p>
        <label>
            ์•„์ด๋””:
            <input type="text" th:field="*{username}"/>
            <div th:if="${#fields.hasErrors('username')}" th:errors="*{username}"></div>
        </label>
    </p>
    <p>
        <label>
            ํŒจ์Šค์›Œ๋“œ:
            <input type="password" th:field="*{password}"/>
            <div th:if="${#fields.hasErrors('password')}" th:errors="*{password}"></div>
        </label>
    </p>
    <button type="submit">๊ฐ€์ž…ํ•˜๊ธฐ</button>
    <a th:href="@{/login}" class="cancel">๋กœ๊ทธ์ธ</a>
</form>
</body>
  • ์ƒˆ๋กœ์šด ๋ฌธ๋ฒ• :
    <div th:if="${#fields.hasErrors('username')}" th:errors="*{username}"></div>

๋กœ๊ทธ์ธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ ์ „ ์„ธ์…˜์— ๊ด€ํ•ด ์•Œ์•„๋ณด๊ธฐ

๐Ÿ’ก ์„ธ์…˜

stateless html : ์ƒํƒœ๊ฐ€ ์—†๋‹ค. ์„œ๋ฒ„๊ฐ€ ๋“ค์–ด์˜ค๋Š” ์‚ฌ๋žŒ์˜ ์ƒํƒœ๋ฅผ ๊ธฐ์–ตํ•˜์ง€ ์•Š๋Š”๋‹ค.

  • ๋งŒ์•ฝ ๊ธฐ์–ต์„ ํ•˜๋ฉด์€???
    statefull -> ์„œ๋ฒ„: A์œ ์ €๊ฐ€ ๋“ค์–ด์™”๊ตฐ, ํšŒ์›๊ฐ€์ž…์„ ํ•˜๊ณ  ํˆฌ๋‘๋ฅผ ์ƒ์„ฑํ–ˆ๊ตฐ,, ์•Œ๊ณ ์žˆ๋‹ค.

  • ์ƒํƒœ๊ฐ€ ์—†๋‹ค๋Š”๊ฑด???
    stateless -> ์„œ๋ฒ„: A์ธ์ง€ ์•„๋‹Œ์ง€ ๋งค๋ฒˆ ๋ชจ๋ฅธ๋‹ค. ๋ฌด์—‡์„ ํ•˜๊ฑด๊ฐ„์— ๊ณ„์†ํ•ด์„œ password๋ฅผ ๋ฐ›์•„ A์ž„์„ ํŒ๋ณ„ํ•ด์•ผ ํ•œ๋‹ค.

๐Ÿ”“ ๊ทผ๋ฐ ๊ณ„์†ํ•ด์„œ ๋น„๋ฒˆ์„ ๋„คํŠธ์›Œํฌ์— ํƒœ์šฐ๋Š”๊ฒƒ์€??? ๋„ˆ๋ฌด ๋ณด์•ˆ์— ์ทจ์•ฝํ•˜๋‹ค.

์ด๊ฑฐ์Šฌ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด

  • ์„ธ์…˜ ๋ฐฉ์‹์ด ์žˆ๋‹ค :
    ์„œ๋ฒ„๊ฐ€ ์ธ์ฆ๋œ ์‚ฌ์šฉ์ž์—๊ฒŒ ๋„คํŠธ์›Œํฌ์— ํƒœ์šธ ์ƒˆ๋กœ์šด ์—ด์‡ ๋ฅผ ์ฃผ๋Š” ๋ฐฉ์‹ :
    ๋กœ๊ทธ์ธ ์‹œ ๋น„๋ฒˆ์„ ๋ฐ›๊ณ , ์„œ๋ฒ„ ๋ฉ”๋ชจ๋ฆฌ์— id, token ์„ ์ƒ์„ฑํ•˜์—ฌ ์ด๊ฒƒ์„ ์ฟ ํ‚ค ๊ฐ™์€๊ฒƒ์— ๋‹ด์•„์„œ ์‚ฌ์šฉ์ž์—๊ฒŒ ๋ณด๋‚ด์ค€๋‹ค. ์‚ฌ์šฉ์ž(ํด๋ผ์ด์–ธํŠธ)๋Š” ์ด ์ฟ ํ‚ค ์•ˆ์˜ ํ† ํฐ์„ ํ•ญ์ƒ ๊ฐ–๊ณ  ์„œ๋น„์Šค๋ฅผ ์š”์ฒญํ•œ๋‹ค. ์„œ๋ฒ„๋Š” ํด๋ผ์ด์–ธํŠธ์˜ ๋„คํŠธ์›Œํฌ ์š”์ฒญ์„ ๊ณ„์† ๋“ฃ๊ณ  ์žˆ๋‹ค๊ฐ€ ์ผ์ • ์‹œ๊ฐ„ ์ด์ƒ ์ ‘์†์ด ์—†์œผ๋ฉด ์„ธ์…˜์„ ๋Š๋Š”๋‹ค. ์ด๋•Œ ์„ธ์…˜์€ ๋งŒ๋ฃŒ๋งŒ ๋˜๊ธฐ ๋•Œ๋ฌธ์— ์„ธ์…˜์•„์ด๋””๊ฐ€ ๊ทธ๋Œ€๋กœ ๋‚จ์•„ ์žˆ์–ด๋„ ๋”์ด์ƒ ๊ฐ™์€ ์„ธ์…˜์œผ๋กœ๋Š” ์—ฐ๊ฒฐํ•  ์ˆ˜ ์—†๋‹ค.
HttpSession httpSession,
httpSession.setAttribute("user", user);

๐Ÿ’ซ ๋กœ๊ทธ์ธ ๊ธฐ๋Šฅ

๐Ÿšš LoginDto

@Getter
@Setter
public class SignupDto {
    @NotBlank(message = "์•„์ด๋””๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”")
    @Size(min=3, max=10, message = "์•„์ด๋””๋Š” 3~10์ž์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค")
    private String username;

    @NotBlank(message = "๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”")
    @Size(min=6, max=20, message = "๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” 6~20์ž์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค")
    private String password;
}

๐Ÿ“ก Controller

@Controller
@RequiredArgsConstructor
public class LoginController {
    private final UserRepository userRepository;

    @GetMapping({"/", "/login"})
    public String showLogin(Model model) {
        model.addAttribute("loginDto", new LoginDto());
        return "login";
    }

    @PostMapping("/login")
    public String doLogin(@Valid @ModelAttribute("loginDto") LoginDto loginDto, BindingResult bindingResult, HttpSession httpSession, Model model) {
        if (bindingResult.hasErrors()) {
            return "login";
        }
        try {
            User user = userRepository.findByUsername(loginDto.getUsername());

            if (!user.getPassword().equals(loginDto.getPassword())) {
                model.addAttribute("error", "๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์˜ฌ๋ฐ”๋ฅด์ง€ ์•Š์Šต๋‹ˆ๋‹ค");

                return "login";
            }
            httpSession.setAttribute("user", user);

            return "redirect:/todos";
        } catch (Exception e) {
            model.addAttribute("error", "์กด์žฌํ•˜์ง€ ์•Š๋Š” ์‚ฌ์šฉ์ž์ž…๋‹ˆ๋‹ค.");

            return "login";
        }
    }

    @GetMapping("/logout")
    public String logout(HttpSession session) {
        session.invalidate();
        return "redirect:/login";
    }
}
  • ํšŒ์›๊ฐ€์ž…๊ณผ ๋น„์Šทํ•˜๊ฒŒ ๋งŒ๋“ค๋ฉด ๋œ๋‹ค..
  • ๋กœ๊ทธ์•„์›ƒ์€ ์„ธ์…˜์„ ๋งŒ๋ฃŒ์‹œํ‚ค๋ฉด ๋œ๋‹ค.

๐Ÿ–ผ๏ธ login.html

<body>
<h1>๐Ÿ”‘ ๋กœ๊ทธ์ธ</h1>
<div th:if="${param.registered}" class="success">
    ๐ŸŽ‰ ํšŒ์›๊ฐ€์ž…์ด ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค! ๋กœ๊ทธ์ธ ํ•ด์ฃผ์„ธ์š”.
</div>
<div th:if="${error}" class="error" th:text="${error}"></div>
<form th:action="@{/login}" th:object="${loginDto}" method="post" class="form-container">
    <p>
        <label>
            ์•„์ด๋””:
            <input type="text" th:field="*{username}" />
        </label>
    <div th:if="${#fields.hasErrors('username')}" th:errors="*{username}"></div>
    </p>
    <p>
        <label>
            ํŒจ์Šค์›Œ๋“œ:
            <input type="password" th:field="*{password}" />
        </label>
    <div th:if="${#fields.hasErrors('password')}" th:errors="*{password}"></div>
    </p>
    <button type="submit">๋กœ๊ทธ์ธ</button>
    <a th:href="@{/signup}" class="cancel">ํšŒ์›๊ฐ€์ž…</a>
</form>
</body>
  • ์™ ์ง€ signup ํผ์ด๋ž‘ ๊ฐ™์ด์“ธ์ˆ˜ ์žˆ์„๋“ฏํ•œ ๋А๋‚Œ์ด๊ธดํ•œ๋ฐ ๊ทธ๋Ÿผ dto ์“ฐ๋Š” ๋ถ€๋ถ„ ํฌํ•จํ•ด์„œ ์‚ผํ•ญ์—ฐ์‚ฐ์ž๊ฐ€ ์—„์ฒญ ๋Š˜์–ด๋‚ ๋“ฏ.. ๊ฑ ๋ถ„๋ฆฌ๋˜์–ด ์žˆ๋Š”๊ฒŒ ์ดํ•ดํ•˜๊ธด ํŽธํ•˜๋‹ค

Todo

๐Ÿง  Todo.java

@Data
@Builder
public class Todo {
    private Integer id;
    private String title;
    private boolean completed;
    private Integer userId;
}

๐Ÿ—„๏ธ TodoRepository

@Repository
@RequiredArgsConstructor
public class TodoRepository {
    private final JdbcTemplate jdbcTemplate;

    // ๐ŸŽฏ ResultSet โ†’ Todo ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜ํ•ด์ฃผ๋Š” RowMapper ์ •์˜
    private final RowMapper<Todo> todoRowMapper = (resultSet, rowNum) -> {
        return Todo.builder()
                .id(resultSet.getInt("id"))
                .title(resultSet.getString("title"))
                .completed(resultSet.getBoolean("completed"))
                .userId(resultSet.getInt("user_id"))
                .build();
    };

    // โœ… ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž์˜ todo ๋ชฉ๋ก๋งŒ ์กฐํšŒ (๋ณด์•ˆ ์ค‘์š”)
    public List<Todo> findAllByUserId(int userId) {
        String sql = "SELECT * FROM todo WHERE user_id = ? ORDER BY id";
        return jdbcTemplate.query(sql, todoRowMapper, userId);
    }

    // โœ… ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž์˜ ํŠน์ • todo 1๊ฐœ ์กฐํšŒ
    // โš ๏ธ userId ์กฐ๊ฑด์„ ํ•จ๊ป˜ ๊ฑธ์–ด์•ผ ๋‹ค๋ฅธ ์‚ฌ์šฉ์ž์˜ todo ์ ‘๊ทผ ์ฐจ๋‹จ ๊ฐ€๋Šฅ
    public Todo findByIdAndUserId(int id, int userId) {
        String sql = "SELECT * FROM todo WHERE id = ? AND user_id = ?";
        return jdbcTemplate.queryForObject(sql, todoRowMapper, id, userId);
    }

    // โœ… ์ƒˆ todo ๋“ฑ๋ก (๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž์˜ userId๋ฅผ ํ•จ๊ป˜ ์ €์žฅ)
    public int save(Todo todo) {
        String sql = "INSERT INTO todo (user_id, title, completed) VALUES (?, ?, ?)";
        return jdbcTemplate.update(sql, todo.getUserId(), todo.getTitle(), todo.isCompleted());
    }

    // โœ… ๊ธฐ์กด todo ์ˆ˜์ • (ํ•ด๋‹น ์‚ฌ์šฉ์ž์˜ todo์ธ์ง€ ๊ฒ€์ฆํ•˜๊ธฐ ์œ„ํ•ด userId ์กฐ๊ฑด ํฌํ•จ)
    public int update(Todo todo) {
        String sql = "UPDATE todo SET title = ?, completed = ? WHERE id = ? AND user_id = ?";
        return jdbcTemplate.update(sql, todo.getTitle(), todo.isCompleted(), todo.getId(), todo.getUserId());
    }

    // โœ… todo ์‚ญ์ œ (์‚ฌ์šฉ์ž ๋ณธ์ธ์˜ todo์ธ์ง€ ํ™•์ธ ์œ„ํ•ด userId ํฌํ•จ)
    public int deleteByIdAndUserId(int id, int userId) {
        String sql = "DELETE FROM todo WHERE id = ? AND user_id = ?";
        return jdbcTemplate.update(sql, id, userId);
    }
}
  • ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธํ•œ ํ›„ ๋‹ค๋ฅธ ์‚ฌ๋žŒ์˜ todo์— ์ ‘๊ทผํ•˜๊ฑฐ๋‚˜ ์‚ญ์ œํ•˜์ง€ ๋ชปํ•˜๋„๋ก ํ•˜๊ธฐ ์œ„ํ•ด userId๋กœ ๋ช…ํ™•ํžˆ ์‹๋ณ„ํ•œ๋‹ค.
  • ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์ฟผ๋ฆฌ ์กฐ๊ฑด์—์„œ ๊ถŒํ•œ ์ฒดํฌ๋ฅผ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์ˆ˜ํ–‰ํ•˜๊ฒŒ ๋˜์–ด, ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ์‹ค์ˆ˜๋กœ ์ธ์ฆ์„ ๋น ๋œจ๋ ค๋„ ์•ˆ์ „์žฅ์น˜ ์—ญํ• ์„ ํ•˜๊ฒŒ ๋œ๋‹ค.

๐Ÿ“ก ToDoController

// TodoController๋Š” ์‚ฌ์šฉ์ž์˜ To-do ๋ฆฌ์ŠคํŠธ๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ์ปจํŠธ๋กค๋Ÿฌ์ž…๋‹ˆ๋‹ค.
@Controller
@RequestMapping("/todos") // /todos๋กœ ์‹œ์ž‘ํ•˜๋Š” URL์„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
@RequiredArgsConstructor // final ํ•„๋“œ๋ฅผ ์ž๋™์œผ๋กœ ์ƒ์„ฑ์ž ์ฃผ์ž…ํ•ฉ๋‹ˆ๋‹ค.
public class TodoController {

    // Todo ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•  Repository ์ฃผ์ž…
    private final TodoRepository todoRepository;

    // ํ˜„์žฌ ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž๋ฅผ ์„ธ์…˜์—์„œ ๊ฐ€์ ธ์˜ค๋Š” ์œ ํ‹ธ๋ฆฌํ‹ฐ ๋ฉ”์„œ๋“œ
    private User getCurrentUser(HttpSession session) {
        return (User) session.getAttribute("user");
    }

    // ์ „์ฒด To-do ๋ฆฌ์ŠคํŠธ๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” ๋ฉ”์„œ๋“œ
    @GetMapping
    public String list(HttpSession session, Model model) {
        User user = getCurrentUser(session); // ํ˜„์žฌ ์‚ฌ์šฉ์ž ์กฐํšŒ
        System.out.println(getCurrentUser(session));
        if(user == null) {
            return "redirect:/login"; // ๋กœ๊ทธ์ธ ์•ˆํ–ˆ์œผ๋ฉด ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ
        }

        // ํ˜„์žฌ ์‚ฌ์šฉ์ž ID๋กœ ํ•  ์ผ ๋ชฉ๋ก ์กฐํšŒ
        List<Todo> list = todoRepository.findAllByUserId(user.getId());

        // ๋ทฐ์— ํ•  ์ผ ๋ชฉ๋ก ์ „๋‹ฌ
        model.addAttribute("todos", list);

        return "todo-list"; // todo-list.html ๋ทฐ ๋ Œ๋”๋ง
    }

    // ํ•  ์ผ ์ถ”๊ฐ€ ํผ ์š”์ฒญ ์ฒ˜๋ฆฌ
    @GetMapping("/add")
    public String addForm(HttpSession httpSession, Model model) {
        if (getCurrentUser(httpSession) == null) return "redirect:/login"; // ๋กœ๊ทธ์ธ ํ™•์ธ
        model.addAttribute("todoDto", new TodoDto()); // ๋นˆ ํผ ๊ฐ์ฒด ์ „๋‹ฌ
        return "todo-form"; // todo-form.html ๋ทฐ ๋ Œ๋”๋ง
    }

    // ํ•  ์ผ ์ถ”๊ฐ€ ์š”์ฒญ ์ฒ˜๋ฆฌ
    @PostMapping("/add")
    public String add(
            @Valid @ModelAttribute TodoDto todoDto, // ์ž…๋ ฅ๊ฐ’ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ
            BindingResult bindingResult, // ๊ฒ€์‚ฌ ๊ฒฐ๊ณผ ์ €์žฅ
            HttpSession httpSession
    ) {
        if(bindingResult.hasErrors()) return "todo-form"; // ์—๋Ÿฌ๊ฐ€ ์žˆ์œผ๋ฉด ๋‹ค์‹œ ํผ์œผ๋กœ
        User user = getCurrentUser(httpSession); // ๋กœ๊ทธ์ธ ์‚ฌ์šฉ์ž ์กฐํšŒ

        // Todo ๊ฐ์ฒด ์ƒ์„ฑ ๋ฐ ์ €์žฅ
        Todo todo = Todo.builder()
                .userId(user.getId())
                .title(todoDto.getTitle())
                .completed(todoDto.isCompleted())
                .build();

        todoRepository.save(todo); // DB์— ์ €์žฅ

        return "redirect:/todos"; // ๋ชฉ๋ก ํŽ˜์ด์ง€๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ
    }

    // ํ•  ์ผ ์ˆ˜์ • ํผ ์š”์ฒญ ์ฒ˜๋ฆฌ
    @GetMapping("/edit/{id}")
    public String editForm(
            @PathVariable int id, // ์ˆ˜์ •ํ•  To-do์˜ ID
            Model model,
            HttpSession httpSession
    ){
        User user = getCurrentUser(httpSession);
        if (user == null) return "redirect:/login";

        // ํ•ด๋‹น ID์™€ ์‚ฌ์šฉ์ž ID๋กœ ํ•  ์ผ ์กฐํšŒ
        Todo todo = todoRepository.findByIdAndUserId(id, user.getId());

        // DTO๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ํผ์— ์ „๋‹ฌ
        TodoDto dto = new TodoDto();
        dto.setId(todo.getId());
        dto.setTitle(todo.getTitle());
        dto.setCompleted(todo.isCompleted());

        model.addAttribute("todoDto", dto);
        return "todo-form"; // ๊ฐ™์€ ํผ์„ ์žฌ์‚ฌ์šฉ
    }

    // ํ•  ์ผ ์ˆ˜์ • ์š”์ฒญ ์ฒ˜๋ฆฌ
    @PostMapping("/edit")
    public String edit(
            @Valid @ModelAttribute TodoDto todoDto,
            BindingResult bindingResult,
            HttpSession httpSession
    ) {
        if(bindingResult.hasErrors()) return "todo-form"; // ์—๋Ÿฌ ์‹œ ํผ์œผ๋กœ

        User user = getCurrentUser(httpSession);

        // ์ˆ˜์ •๋œ ๋‚ด์šฉ์œผ๋กœ Todo ๊ฐ์ฒด ์ƒ์„ฑ
        Todo todo = Todo.builder()
                .id(todoDto.getId())
                .title(todoDto.getTitle())
                .completed(todoDto.isCompleted())
                .userId(user.getId())
                .build();

        todoRepository.update(todo); // ์—…๋ฐ์ดํŠธ ์‹คํ–‰
        return "redirect:/todos";
    }

    // ํ•  ์ผ ์‚ญ์ œ ์š”์ฒญ ์ฒ˜๋ฆฌ
    @PostMapping("/delete/{id}")
    public String delete(
            @PathVariable int id,
            HttpSession httpSession
    ) {
        User user = getCurrentUser(httpSession);
        // ์‚ฌ์šฉ์ž ID์™€ ํ•จ๊ป˜ ์‚ญ์ œ (๋ณด์•ˆ์ƒ ํ•„์š”)
        todoRepository.deleteByIdAndUserId(id, user.getId());
        return "redirect:/todos";
    }
}

๐Ÿ’กํ•ต์‹ฌ ์š”์•ฝ

โœ… @Valid @ModelAttribute์™€ BindingResult๋Š” ๋ฐ˜๋“œ์‹œ ์—ฐ๋‹ฌ์•„ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.

  • @ModelAttribute๋กœ ๋ฐ”์ธ๋”ฉ๋œ ๊ฐ์ฒด์— ๋Œ€ํ•ด ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ ์šฉํ•  ๋•Œ @Valid๋ฅผ ๋ถ™์ž„
  • BindingResult๋Š” ๊ฒ€์‚ฌ ๊ฒฐ๊ณผ๋ฅผ ๋‹ด๋Š” ๊ฐ์ฒด์ด๋ฉฐ, @Valid ๋ฐ”๋กœ ๋’ค์— ์„ ์–ธํ•ด์•ผ ์˜ค๋ฅ˜๋ฅผ ๊ฐ์ง€ํ•  ์ˆ˜ ์žˆ์Œ
  • ์˜ฌ๋ฐ”๋ฅธ ์˜ˆ์‹œ:
    public String submit(@Valid @ModelAttribute FormDto formDto, BindingResult result)
    ๐Ÿ” ๋“ฑ๋ก๊ณผ ์ˆ˜์ • ํผ์„ ํ•˜๋‚˜์˜ ํ…œํ”Œ๋ฆฟ(todo-form)์œผ๋กœ ์žฌ์‚ฌ์šฉ
    @GetMapping("/add") โ†’ ๋นˆ DTO ์ „๋‹ฌ

@GetMapping("/edit/{id}") โ†’ ๊ธฐ์กด ์—”ํ‹ฐํ‹ฐ๋ฅผ DTO๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ์ „๋‹ฌ

๋‘˜ ๋‹ค ๊ฐ™์€ ํผ์„ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ todo-form.html์„ ์žฌํ™œ์šฉํ•  ์ˆ˜ ์žˆ์Œ

๐Ÿ” ์‚ญ์ œ๋Š” @PostMapping์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ๋ณด์•ˆ์ƒ ์•ˆ์ „ํ•˜๋‹ค.

  • @GetMapping("/delete/{id}")์ฒ˜๋Ÿผ GET ์š”์ฒญ์œผ๋กœ ์‚ญ์ œํ•˜์ง€ ์•Š๋„๋ก ์ฃผ์˜
  • @PostMapping์„ ์‚ฌ์šฉํ•˜๋ฉด CSRF ๋ฐฉ์–ด๊ฐ€ ๊ฐ€๋Šฅํ•˜๊ณ , ์‹ค์ˆ˜๋กœ ๋งํฌ๋ฅผ ํด๋ฆญํ•ด์„œ ์‚ญ์ œ๋˜๋Š” ์‚ฌ๊ณ ๋ฅผ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.

๋” ์ถ”๊ฐ€ํ•  ์‚ฌํ•ญ : ๋น„๋ฐ€๋ฒˆํ˜ธ ์ €์žฅ ๊ธฐ๋Šฅ ๋‹ฌ์•„๋ณด๊ธฐ

profile
๐Ÿ—‚๏ธ hamstern

0๊ฐœ์˜ ๋Œ“๊ธ€