์ฝ˜ํ…์ธ  ๋Œ€ํ‘œ ์ด๋ฏธ์ง€ - ๐Ÿš€ ์ž๋ฐ” ์›น ๊ฐœ๋ฐœ์˜ ๋ชจ๋“  ๊ฒƒ

๐Ÿš€ ์ž๋ฐ” ์›น ๊ฐœ๋ฐœ์˜ ๋ชจ๋“  ๊ฒƒ

Servlet๋ถ€ํ„ฐ JSP, Spring MVC๊นŒ์ง€ ์™„๋ฒฝ ์ •๋ณตํ•˜๊ธฐ

์•ˆ๋…•! ์˜ค๋Š˜์€ ์ž๋ฐ” ์›น ๊ฐœ๋ฐœ์˜ ํ•ต์‹ฌ ๊ธฐ์ˆ ๋“ค์— ๋Œ€ํ•ด ์ด์•ผ๊ธฐํ•ด๋ณผ ๊ฑฐ์•ผ. โ˜• ์ž๋ฐ”๋กœ ์›น ๊ฐœ๋ฐœ์„ ์‹œ์ž‘ํ•˜๋ ค๋Š” ์‚ฌ๋žŒ์ด๋ผ๋ฉด ๋ˆ„๊ตฌ๋‚˜ ํ•œ ๋ฒˆ์ฏค์€ Servlet, JSP, Spring MVC๋ผ๋Š” ๋‹จ์–ด๋ฅผ ๋“ค์–ด๋ดค์„ ๊ฑฐ์•ผ. ๊ทผ๋ฐ ์ด๊ฒŒ ์ •ํ™•ํžˆ ๋ญ๊ณ , ์–ด๋–ป๊ฒŒ ์—ฐ๊ฒฐ๋˜๋Š”์ง€ ํ—ท๊ฐˆ๋ฆฌ์ง€ ์•Š์•„? ๊ฑฑ์ • ๋งˆ! ์นœ๊ตฌํ•œํ…Œ ์„ค๋ช…ํ•˜๋“ฏ์ด ์‰ฝ๊ณ  ์žฌ๋ฏธ์žˆ๊ฒŒ ํ’€์–ด์ค„๊ฒŒ. ๐Ÿ˜Š

Servlet ๊ธฐ์ดˆ ๊ธฐ์ˆ  JSP ๋ทฐ ํ…œํ”Œ๋ฆฟ Spring MVC ์›น ์š”์ฒญ ์ฒ˜๋ฆฌ HTTP ์š”์ฒญ ์ˆ˜์‹  ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์‹คํ–‰ ์‘๋‹ต ์ƒ์„ฑ ํด๋ผ์ด์–ธํŠธ ์ „์†ก ํ™”๋ฉด ๋ Œ๋”๋ง HTML ํ…œํ”Œ๋ฆฟ ์ž๋ฐ” ์ฝ”๋“œ ์‚ฝ์ž… ๋™์  ์ปจํ…์ธ  ์ƒ์„ฑ ์‚ฌ์šฉ์ž ํ™”๋ฉด ์ถœ๋ ฅ ํ”„๋ ˆ์ž„์›Œํฌ MVC ํŒจํ„ด ๊ตฌํ˜„ ์˜์กด์„ฑ ์ฃผ์ž… ์ž๋™ ์„ค์ • ์ƒ์‚ฐ์„ฑ ํ–ฅ์ƒ

๐ŸŽฏ ์ž๋ฐ” ์›น ๊ฐœ๋ฐœ์˜ ์‹œ์ž‘, Servlet์ด๋ž€?

์ž๋ฐ”๋กœ ์›น ๊ฐœ๋ฐœ์„ ํ•œ๋‹ค๋Š” ๊ฑด ๊ฒฐ๊ตญ ์‚ฌ์šฉ์ž์˜ ์š”์ฒญ์„ ๋ฐ›์•„์„œ ์ฒ˜๋ฆฌํ•˜๊ณ , ๊ทธ ๊ฒฐ๊ณผ๋ฅผ ๋‹ค์‹œ ๋ณด์—ฌ์ฃผ๋Š” ๊ฑฐ์•ผ. ์ด ๊ณผ์ •์˜ ๊ฐ€์žฅ ๊ธฐ๋ณธ์ด ๋˜๋Š” ๊ฒŒ ๋ฐ”๋กœ Servlet์ด์•ผ. ๐Ÿ”ง

Servlet์€ 1997๋…„์— Sun Microsystems(์ง€๊ธˆ์˜ Oracle)์—์„œ ๋ฐœํ‘œํ•œ ์ž๋ฐ” ๊ธฐ์ˆ ์ด์•ผ. ๋‹น์‹œ์—๋Š” CGI(Common Gateway Interface)๋ผ๋Š” ๋ฐฉ์‹์œผ๋กœ ์›น ์„œ๋ฒ„์™€ ํ”„๋กœ๊ทธ๋žจ์ด ํ†ต์‹ ํ–ˆ๋Š”๋ฐ, ์ด๊ฒŒ ์„ฑ๋Šฅ๋„ ๋ณ„๋กœ๊ณ  ๊ด€๋ฆฌ๋„ ์–ด๋ ค์› ์–ด. ๊ทธ๋ž˜์„œ ์ž๋ฐ” ์ง„์˜์—์„œ "์šฐ๋ฆฌ๊ฐ€ ๋” ๋‚˜์€ ๊ฑธ ๋งŒ๋“ค์ž!"๋ผ๊ณ  ํ•ด์„œ ๋‚˜์˜จ ๊ฒŒ Servlet์ด์ง€. ๐Ÿ’ก

Servlet์˜ ํ•ต์‹ฌ ๊ฐœ๋…

Servlet์€ ์ž๋ฐ”๋กœ ์ž‘์„ฑ๋œ ์„œ๋ฒ„ ์ธก ํ”„๋กœ๊ทธ๋žจ์ด์•ผ. ์›น ์„œ๋ฒ„์—์„œ ์‹คํ–‰๋˜๋ฉด์„œ ํด๋ผ์ด์–ธํŠธ(์ฃผ๋กœ ์›น ๋ธŒ๋ผ์šฐ์ €)์˜ ์š”์ฒญ์„ ๋ฐ›์•„ ์ฒ˜๋ฆฌํ•˜๊ณ , ๊ทธ ๊ฒฐ๊ณผ๋ฅผ HTML์ด๋‚˜ JSON ๊ฐ™์€ ํ˜•ํƒœ๋กœ ์‘๋‹ตํ•ด์ฃผ๋Š” ์—ญํ• ์„ ํ•ด. ๐ŸŒ

์‰ฝ๊ฒŒ ๋งํ•˜๋ฉด, ์‚ฌ์šฉ์ž๊ฐ€ ์›น ํŽ˜์ด์ง€์—์„œ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๊ฑฐ๋‚˜ ํผ์„ ์ œ์ถœํ•˜๋ฉด, ๊ทธ ์š”์ฒญ์ด ์„œ๋ฒ„๋กœ ๋‚ ์•„๊ฐ€๊ณ , Servlet์ด ๊ทธ๊ฑธ ๋ฐ›์•„์„œ "์•„, ์ด ์‚ฌ๋žŒ์ด ๋กœ๊ทธ์ธํ•˜๋ ค๊ณ  ํ•˜๋Š”๊ตฌ๋‚˜" ๋˜๋Š” "์ƒํ’ˆ ๋ชฉ๋ก์„ ๋ณด๊ณ  ์‹ถ์–ด ํ•˜๋Š”๊ตฌ๋‚˜"๋ผ๊ณ  ํŒ๋‹จํ•ด์„œ ์ ์ ˆํ•œ ์ฒ˜๋ฆฌ๋ฅผ ํ•˜๋Š” ๊ฑฐ์ง€.

Servlet์˜ ์ƒ๋ช…์ฃผ๊ธฐ (Lifecycle)

Servlet์€ ํƒœ์–ด๋‚˜์„œ ์ฃฝ์„ ๋•Œ๊นŒ์ง€ ์ •ํ•ด์ง„ ์ƒ๋ช…์ฃผ๊ธฐ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์–ด. ์ด๊ฑธ ์ดํ•ดํ•˜๋ฉด Servlet์ด ์–ด๋–ป๊ฒŒ ๋™์ž‘ํ•˜๋Š”์ง€ ํ›จ์”ฌ ๋ช…ํ™•ํ•˜๊ฒŒ ์•Œ ์ˆ˜ ์žˆ์–ด. ๐Ÿ”„

1. ์ดˆ๊ธฐํ™” ๋‹จ๊ณ„ (init)

Servlet ์ปจํ…Œ์ด๋„ˆ(Tomcat ๊ฐ™์€ ๊ฑฐ)๊ฐ€ Servlet ํด๋ž˜์Šค๋ฅผ ๋ฉ”๋ชจ๋ฆฌ์— ๋กœ๋”ฉํ•˜๊ณ , ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•ด. ๊ทธ๋ฆฌ๊ณ  init() ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•ด์„œ ์ดˆ๊ธฐํ™” ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜์ง€. ์ด ๊ณผ์ •์€ Servlet์ด ์ฒ˜์Œ ์š”์ฒญ๋  ๋•Œ ๋”ฑ ํ•œ ๋ฒˆ๋งŒ ์‹คํ–‰๋ผ. ๐Ÿš€

public void init(ServletConfig config) throws ServletException {
    // ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ์„ค์ •
    // ์„ค์ • ํŒŒ์ผ ๋กœ๋”ฉ
    // ์ดˆ๊ธฐ ๋ฆฌ์†Œ์Šค ํ• ๋‹น ๋“ฑ
}

2. ์„œ๋น„์Šค ๋‹จ๊ณ„ (service)

์‹ค์ œ๋กœ ํด๋ผ์ด์–ธํŠธ์˜ ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋‹จ๊ณ„์•ผ. service() ๋ฉ”์„œ๋“œ๊ฐ€ ํ˜ธ์ถœ๋˜๊ณ , ์ด ๋ฉ”์„œ๋“œ๋Š” HTTP ์š”์ฒญ ๋ฐฉ์‹(GET, POST ๋“ฑ)์— ๋”ฐ๋ผ doGet(), doPost() ๊ฐ™์€ ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•ด. ์ด ๋‹จ๊ณ„๋Š” ์š”์ฒญ์ด ์˜ฌ ๋•Œ๋งˆ๋‹ค ๋ฐ˜๋ณต์ ์œผ๋กœ ์‹คํ–‰๋ผ. โšก

protected void doGet(HttpServletRequest request, 
                     HttpServletResponse response) 
                     throws ServletException, IOException {
    // GET ์š”์ฒญ ์ฒ˜๋ฆฌ
    response.setContentType("text/html;charset=UTF-8");
    PrintWriter out = response.getWriter();
    out.println("<h1>Hello, Servlet!</h1>");
}

3. ์ข…๋ฃŒ ๋‹จ๊ณ„ (destroy)

Servlet ์ปจํ…Œ์ด๋„ˆ๊ฐ€ ์ข…๋ฃŒ๋˜๊ฑฐ๋‚˜ Servlet์„ ๋ฉ”๋ชจ๋ฆฌ์—์„œ ์ œ๊ฑฐํ•  ๋•Œ destroy() ๋ฉ”์„œ๋“œ๊ฐ€ ํ˜ธ์ถœ๋ผ. ์—ฌ๊ธฐ์„œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ์„ ๋‹ซ๊ฑฐ๋‚˜, ํŒŒ์ผ์„ ์ •๋ฆฌํ•˜๋Š” ๋“ฑ์˜ ๋งˆ๋ฌด๋ฆฌ ์ž‘์—…์„ ํ•ด. ์ด๊ฒƒ๋„ ์ƒ์•  ๋™์•ˆ ๋”ฑ ํ•œ ๋ฒˆ๋งŒ ์‹คํ–‰๋ผ. ๐Ÿ

public void destroy() {
    // ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ์ข…๋ฃŒ
    // ๋ฆฌ์†Œ์Šค ํ•ด์ œ
    // ๋กœ๊ทธ ๊ธฐ๋ก ๋“ฑ
}

์‹ค์ „ Servlet ์˜ˆ์ œ

์ด๋ก ๋งŒ ๋ณด๋ฉด ์žฌ๋ฏธ์—†์œผ๋‹ˆ๊นŒ, ์‹ค์ œ๋กœ ๊ฐ„๋‹จํ•œ ๋กœ๊ทธ์ธ ์ฒ˜๋ฆฌ Servlet์„ ๋งŒ๋“ค์–ด๋ณผ๊นŒ? ๐Ÿ‘จโ€๐Ÿ’ป

@WebServlet("/login")
public class LoginServlet extends HttpServlet {
    
    @Override
    protected void doPost(HttpServletRequest request, 
                         HttpServletResponse response) 
                         throws ServletException, IOException {
        
        // 1. ์š”์ฒญ ํŒŒ๋ผ๋ฏธํ„ฐ ๋ฐ›๊ธฐ
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        
        // 2. ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์ฒ˜๋ฆฌ
        boolean isValid = validateUser(username, password);
        
        // 3. ์‘๋‹ต ์ƒ์„ฑ
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();
        
        if (isValid) {
            // ์„ธ์…˜์— ์‚ฌ์šฉ์ž ์ •๋ณด ์ €์žฅ
            HttpSession session = request.getSession();
            session.setAttribute("user", username);
            
            out.println("<h1>๋กœ๊ทธ์ธ ์„ฑ๊ณต!</h1>");
            out.println("<p>ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค, " + username + "๋‹˜</p>");
        } else {
            out.println("<h1>๋กœ๊ทธ์ธ ์‹คํŒจ</h1>");
            out.println("<p>์•„์ด๋”” ๋˜๋Š” ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ํ™•์ธํ•˜์„ธ์š”.</p>");
        }
    }
    
    private boolean validateUser(String username, String password) {
        // ์‹ค์ œ๋กœ๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์กฐํšŒ
        return "admin".equals(username) && "1234".equals(password);
    }
}

๐Ÿ’ก Tip: @WebServlet ์–ด๋…ธํ…Œ์ด์…˜์€ Servlet 3.0๋ถ€ํ„ฐ ์ง€์›๋˜๋Š” ๊ธฐ๋Šฅ์ด์•ผ. ์ด์ „์—๋Š” web.xml ํŒŒ์ผ์— ์ผ์ผ์ด ๋งคํ•‘ ์ •๋ณด๋ฅผ ์ž‘์„ฑํ•ด์•ผ ํ–ˆ๋Š”๋ฐ, ์ด์ œ๋Š” ์–ด๋…ธํ…Œ์ด์…˜ ํ•˜๋‚˜๋กœ ๊ฐ„๋‹จํ•˜๊ฒŒ URL ๋งคํ•‘์„ ํ•  ์ˆ˜ ์žˆ์–ด. ํ›จ์”ฌ ํŽธ๋ฆฌํ•˜์ง€? ๐Ÿ˜Ž

Servlet์˜ ํ•œ๊ณ„

์œ„ ์˜ˆ์ œ๋ฅผ ๋ณด๋ฉด ๋ญ”๊ฐ€ ์ด์ƒํ•œ ์ ์„ ๋А๊ผˆ์„ ๊ฑฐ์•ผ. HTML ์ฝ”๋“œ๋ฅผ ์ž๋ฐ” ์ฝ”๋“œ ์•ˆ์—์„œ ๋ฌธ์ž์—ด๋กœ ์ž‘์„ฑํ•˜๊ณ  ์žˆ์ž–์•„? ๐Ÿค”

๋งž์•„, ์ด๊ฒŒ Servlet์˜ ๊ฐ€์žฅ ํฐ ๋ฌธ์ œ์ ์ด์•ผ. ํ™”๋ฉด์„ ๋งŒ๋“ค๋ ค๋ฉด HTML, CSS, JavaScript๋ฅผ ๋ชจ๋‘ ์ž๋ฐ” ์ฝ”๋“œ ์•ˆ์— ๋ฌธ์ž์—ด๋กœ ๋„ฃ์–ด์•ผ ํ•˜๋Š”๋ฐ, ์ด๊ฒŒ ์ •๋ง ๋ถˆํŽธํ•˜๊ณ  ์œ ์ง€๋ณด์ˆ˜๋„ ์–ด๋ ค์›Œ. ๋””์ž์ด๋„ˆ์™€ ํ˜‘์—…ํ•˜๊ธฐ๋„ ํž˜๋“ค๊ณ , ์ฝ”๋“œ ๊ฐ€๋…์„ฑ๋„ ๋–จ์–ด์ง€์ง€.

์˜ˆ๋ฅผ ๋“ค์–ด, ๋ณต์žกํ•œ ํ…Œ์ด๋ธ”์„ ๋งŒ๋“ ๋‹ค๊ณ  ์ƒ๊ฐํ•ด๋ด:

out.println("<table border='1'>");
out.println("  <tr>");
out.println("    <th>๋ฒˆํ˜ธ</th>");
out.println("    <th>์ด๋ฆ„</th>");
out.println("    <th>์ด๋ฉ”์ผ</th>");
out.println("  </tr>");
for (User user : userList) {
    out.println("  <tr>");
    out.println("    <td>" + user.getId() + "</td>");
    out.println("    <td>" + user.getName() + "</td>");
    out.println("    <td>" + user.getEmail() + "</td>");
    out.println("  </tr>");
}
out.println("</table>");

๋ณด๊ธฐ๋งŒ ํ•ด๋„ ๋จธ๋ฆฌ ์•„ํ”„์ง€? ๐Ÿ˜ต ์ด๋Ÿฐ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋“ฑ์žฅํ•œ ๊ฒŒ ๋ฐ”๋กœ JSP์•ผ!

๐ŸŽจ ํ™”๋ฉด์„ ์œ„ํ•œ ๊ธฐ์ˆ , JSP (JavaServer Pages)

JSP๋Š” 1999๋…„์— ๋“ฑ์žฅํ•œ ๊ธฐ์ˆ ๋กœ, Servlet์˜ ๋‹จ์ ์„ ๋ณด์™„ํ•˜๊ธฐ ์œ„ํ•ด ๋งŒ๋“ค์–ด์กŒ์–ด. ํ•ต์‹ฌ ์•„์ด๋””์–ด๋Š” ๊ฐ„๋‹จํ•ด: "HTML ์•ˆ์— ์ž๋ฐ” ์ฝ”๋“œ๋ฅผ ๋„ฃ์ž!" โœจ

Servlet์ด "์ž๋ฐ” ์ฝ”๋“œ ์•ˆ์— HTML์„ ๋„ฃ๋Š”" ๋ฐฉ์‹์ด์—ˆ๋‹ค๋ฉด, JSP๋Š” ๊ทธ ๋ฐ˜๋Œ€์•ผ. HTML ํŒŒ์ผ์ฒ˜๋Ÿผ ์ƒ๊ธด ํŒŒ์ผ์— ํ•„์š”ํ•œ ๋ถ€๋ถ„๋งŒ ์ž๋ฐ” ์ฝ”๋“œ๋ฅผ ์‚ฝ์ž…ํ•˜๋Š” ๊ฑฐ์ง€. ํ›จ์”ฌ ์ง๊ด€์ ์ด๊ณ  ํŽธ๋ฆฌํ•˜์ง€ ์•Š์•„?

JSP์˜ ๋™์ž‘ ์›๋ฆฌ

์žฌ๋ฏธ์žˆ๋Š” ์‚ฌ์‹ค์€, JSP๋„ ๊ฒฐ๊ตญ Servlet์œผ๋กœ ๋ณ€ํ™˜๋œ๋‹ค๋Š” ๊ฑฐ์•ผ! ๐Ÿ”„

1. ์‚ฌ์šฉ์ž๊ฐ€ JSP ํŽ˜์ด์ง€๋ฅผ ์š”์ฒญํ•˜๋ฉด
2. JSP ์ปจํ…Œ์ด๋„ˆ๊ฐ€ JSP ํŒŒ์ผ์„ Servlet ์ž๋ฐ” ์ฝ”๋“œ๋กœ ๋ณ€ํ™˜ํ•ด
3. ๊ทธ Servlet ์ฝ”๋“œ๋ฅผ ์ปดํŒŒ์ผํ•ด์„œ ํด๋ž˜์Šค ํŒŒ์ผ์„ ๋งŒ๋“ค๊ณ 
4. ๊ทธ ํด๋ž˜์Šค๋ฅผ ์‹คํ–‰ํ•ด์„œ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•ด

์ฒ˜์Œ ์š”์ฒญํ•  ๋•Œ๋Š” ์ด ๋ณ€ํ™˜ ๊ณผ์ • ๋•Œ๋ฌธ์— ์•ฝ๊ฐ„ ๋А๋ฆด ์ˆ˜ ์žˆ์ง€๋งŒ, ํ•œ ๋ฒˆ ๋ณ€ํ™˜๋˜๋ฉด ๊ทธ ๋‹ค์Œ๋ถ€ํ„ฐ๋Š” ๋น ๋ฅด๊ฒŒ ์‹คํ–‰๋ผ. ๋˜‘๋˜‘ํ•˜์ง€? ๐Ÿง 

JSP ๊ธฐ๋ณธ ๋ฌธ๋ฒ•

JSP์—๋Š” ์—ฌ๋Ÿฌ ๊ฐ€์ง€ ํŠน์ˆ˜ํ•œ ํƒœ๊ทธ๋“ค์ด ์žˆ์–ด. ํ•˜๋‚˜์”ฉ ์‚ดํŽด๋ณด์ž! ๐Ÿ“

1. ์Šคํฌ๋ฆฝํ‹€๋ฆฟ (Scriptlet) - <% %>

์ž๋ฐ” ์ฝ”๋“œ๋ฅผ ์ง์ ‘ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋Š” ์˜์—ญ์ด์•ผ. ๋ณ€์ˆ˜ ์„ ์–ธ, ์กฐ๊ฑด๋ฌธ, ๋ฐ˜๋ณต๋ฌธ ๋“ฑ์„ ์ž์œ ๋กญ๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด.

<%
    String username = "ํ™๊ธธ๋™";
    int age = 25;
    
    if (age >= 20) {
        out.println("์„ฑ์ธ์ž…๋‹ˆ๋‹ค.");
    }
%>

2. ํ‘œํ˜„์‹ (Expression) - <%= %>

๋ณ€์ˆ˜๋‚˜ ๋ฉ”์„œ๋“œ์˜ ๊ฒฐ๊ณผ๊ฐ’์„ ์ถœ๋ ฅํ•  ๋•Œ ์‚ฌ์šฉํ•ด. out.println()์„ ์“ฐ์ง€ ์•Š์•„๋„ ์ž๋™์œผ๋กœ ์ถœ๋ ฅ๋ผ.

<p>์‚ฌ์šฉ์ž ์ด๋ฆ„: <%= username %></p>
<p>๋‚˜์ด: <%= age %>์„ธ</p>
<p>๋‚ด๋…„ ๋‚˜์ด: <%= age + 1 %>์„ธ</p>

3. ์„ ์–ธ๋ถ€ (Declaration) - <%! %>

ํด๋ž˜์Šค ๋ ˆ๋ฒจ์˜ ๋ณ€์ˆ˜๋‚˜ ๋ฉ”์„œ๋“œ๋ฅผ ์„ ์–ธํ•  ๋•Œ ์‚ฌ์šฉํ•ด. ์ฃผ๋กœ ์—ฌ๋Ÿฌ ์š”์ฒญ์—์„œ ๊ณต์œ ํ•  ๋ฐ์ดํ„ฐ๋‚˜ ์œ ํ‹ธ๋ฆฌํ‹ฐ ๋ฉ”์„œ๋“œ๋ฅผ ๋งŒ๋“ค ๋•Œ ์จ.

<%!
    private int visitCount = 0;
    
    public String formatDate(Date date) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        return sdf.format(date);
    }
%>

4. ์ง€์‹œ์ž (Directive) - <%@ %>

JSP ํŽ˜์ด์ง€์˜ ์ „์ฒด์ ์ธ ์„ค์ •์„ ๋‹ด๋‹นํ•ด. ํŽ˜์ด์ง€ ์ธ์ฝ”๋”ฉ, import ๋ฌธ, ์—๋Ÿฌ ํŽ˜์ด์ง€ ์ง€์ • ๋“ฑ์„ ํ•  ์ˆ˜ ์žˆ์–ด.

<%@ page language="java" contentType="text/html; charset=UTF-8" 
         pageEncoding="UTF-8" %>
<%@ page import="java.util.*, java.text.*" %>
<%@ page errorPage="error.jsp" %>

์‹ค์ „ JSP ์˜ˆ์ œ

์•„๊นŒ Servlet์œผ๋กœ ๋งŒ๋“ค์—ˆ๋˜ ๋กœ๊ทธ์ธ ๊ฒฐ๊ณผ ํ™”๋ฉด์„ JSP๋กœ ๋งŒ๋“ค์–ด๋ณผ๊นŒ? ํ›จ์”ฌ ๊น”๋”ํ•ด์งˆ ๊ฑฐ์•ผ! โœจ

<%@ page language="java" contentType="text/html; charset=UTF-8" 
         pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>๋กœ๊ทธ์ธ ๊ฒฐ๊ณผ</title>
    <style>
        .success { color: green; }
        .error { color: red; }
    </style>
</head>
<body>
    <%
        String username = (String) session.getAttribute("user");
        boolean isLoggedIn = (username != null);
    %>
    
    <% if (isLoggedIn) { %>
        <div class="success">
            <h1>๋กœ๊ทธ์ธ ์„ฑ๊ณต!</h1>
            <p>ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค, <%= username %>๋‹˜</p>
            <p>๋งˆ์ง€๋ง‰ ๋กœ๊ทธ์ธ: <%= new java.util.Date() %></p>
        </div>
    <% } else { %>
        <div class="error">
            <h1>๋กœ๊ทธ์ธ ์‹คํŒจ</h1>
            <p>์•„์ด๋”” ๋˜๋Š” ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ํ™•์ธํ•˜์„ธ์š”.</p>
            <a href="login.html">๋‹ค์‹œ ๋กœ๊ทธ์ธํ•˜๊ธฐ</a>
        </div>
    <% } %>
</body>
</html>

์–ด๋•Œ? Servlet ๋ฒ„์ „๋ณด๋‹ค ํ›จ์”ฌ ๋ณด๊ธฐ ์ข‹์ง€? HTML ๊ตฌ์กฐ๊ฐ€ ๋ช…ํ™•ํ•˜๊ฒŒ ๋ณด์ด๊ณ , ์ž๋ฐ” ์ฝ”๋“œ๋Š” ํ•„์š”ํ•œ ๋ถ€๋ถ„์—๋งŒ ๋“ค์–ด๊ฐ€ ์žˆ์–ด. ๐Ÿ‘

JSP ์•ก์…˜ ํƒœ๊ทธ์™€ EL/JSTL

JSP๋ฅผ ๋” ๊ฐ•๋ ฅํ•˜๊ฒŒ ๋งŒ๋“ค์–ด์ฃผ๋Š” ๋„๊ตฌ๋“ค์ด ์žˆ์–ด. ๋ฐ”๋กœ ์•ก์…˜ ํƒœ๊ทธ, EL(Expression Language), JSTL(JSP Standard Tag Library)์ด์•ผ. ๐Ÿ› ๏ธ

EL (Expression Language)

EL์€ JSP์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋” ์‰ฝ๊ฒŒ ์ ‘๊ทผํ•˜๊ณ  ์ถœ๋ ฅํ•˜๊ธฐ ์œ„ํ•œ ํ‘œํ˜„ ์–ธ์–ด์•ผ. ${} ํ˜•ํƒœ๋กœ ์‚ฌ์šฉํ•ด.

<!-- ๊ธฐ์กด ๋ฐฉ์‹ -->
<%= request.getAttribute("username") %>

<!-- EL ๋ฐฉ์‹ -->
${username}

<!-- ๊ฐ์ฒด ์†์„ฑ ์ ‘๊ทผ -->
${user.name}
${user.email}

<!-- ์—ฐ์‚ฐ๋„ ๊ฐ€๋Šฅ -->
${price * quantity}
${age >= 20 ? "์„ฑ์ธ" : "๋ฏธ์„ฑ๋…„์ž"}

ํ›จ์”ฌ ๊ฐ„๊ฒฐํ•˜๊ณ  ์ฝ๊ธฐ ์‰ฝ์ง€? EL์€ ์ž๋™์œผ๋กœ null ์ฒดํฌ๋„ ํ•ด์ฃผ๊ณ , ํ˜•๋ณ€ํ™˜๋„ ์•Œ์•„์„œ ํ•ด์ค˜์„œ ์ •๋ง ํŽธ๋ฆฌํ•ด. ๐Ÿ˜Š

JSTL (JSP Standard Tag Library)

JSTL์€ ๋ฐ˜๋ณต๋ฌธ, ์กฐ๊ฑด๋ฌธ, ํฌ๋งทํŒ… ๋“ฑ ์ž์ฃผ ์‚ฌ์šฉํ•˜๋Š” ๊ธฐ๋Šฅ๋“ค์„ ํƒœ๊ทธ ํ˜•ํƒœ๋กœ ์ œ๊ณตํ•ด. ์Šคํฌ๋ฆฝํ‹€๋ฆฟ์„ ์“ฐ์ง€ ์•Š๊ณ ๋„ ๋ณต์žกํ•œ ๋กœ์ง์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด.

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<!-- ์กฐ๊ฑด๋ฌธ -->
<c:if test="${age >= 20}">
    <p>์„ฑ์ธ์ž…๋‹ˆ๋‹ค.</p>
</c:if>

<c:choose>
    <c:when test="${score >= 90}">
        <p>A ํ•™์ </p>
    </c:when>
    <c:when test="${score >= 80}">
        <p>B ํ•™์ </p>
    </c:when>
    <c:otherwise>
        <p>C ํ•™์ </p>
    </c:otherwise>
</c:choose>

<!-- ๋ฐ˜๋ณต๋ฌธ -->
<table>
    <c:forEach var="user" items="${userList}">
        <tr>
            <td>${user.id}</td>
            <td>${user.name}</td>
            <td>${user.email}</td>
        </tr>
    </c:forEach>
</table>

JSTL์„ ์‚ฌ์šฉํ•˜๋ฉด JSP ํŽ˜์ด์ง€์—์„œ ์ž๋ฐ” ์ฝ”๋“œ๋ฅผ ๊ฑฐ์˜ ์‚ฌ์šฉํ•˜์ง€ ์•Š์•„๋„ ๋ผ. ์ฝ”๋“œ๊ฐ€ ํ›จ์”ฌ ๊น”๋”ํ•ด์ง€๊ณ , ๋””์ž์ด๋„ˆ๋„ ์ดํ•ดํ•˜๊ธฐ ์‰ฌ์›Œ์ ธ. ๐ŸŽจ

JSP์˜ ํ•œ๊ณ„์™€ ๋ฌธ์ œ์ 

JSP๊ฐ€ Servlet๋ณด๋‹ค ํ›จ์”ฌ ํŽธ๋ฆฌํ•˜๊ธด ํ•˜์ง€๋งŒ, ์—ฌ์ „ํžˆ ๋ฌธ์ œ๊ฐ€ ์žˆ์–ด. ๐Ÿค”

1. ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง๊ณผ ํ™”๋ฉด์ด ์„ž์ž„
JSP ์•ˆ์— ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ ‘๊ทผ ์ฝ”๋“œ, ๊ณ„์‚ฐ ๋กœ์ง ๋“ฑ์„ ๋„ฃ๋‹ค ๋ณด๋ฉด ์ฝ”๋“œ๊ฐ€ ๋ณต์žกํ•ด์ง€๊ณ  ์œ ์ง€๋ณด์ˆ˜๊ฐ€ ์–ด๋ ค์›Œ์ ธ.

2. ํ…Œ์ŠคํŠธ๊ฐ€ ์–ด๋ ค์›€
JSP๋Š” ์›น ์ปจํ…Œ์ด๋„ˆ ์•ˆ์—์„œ๋งŒ ์‹คํ–‰๋˜๊ธฐ ๋•Œ๋ฌธ์— ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜๊ธฐ ํž˜๋“ค์–ด.

3. ์žฌ์‚ฌ์šฉ์„ฑ์ด ๋–จ์–ด์ง
๋น„์Šทํ•œ ์ฝ”๋“œ๋ฅผ ์—ฌ๋Ÿฌ JSP ํŒŒ์ผ์— ๋ณต์‚ฌํ•ด์„œ ๋ถ™์—ฌ๋„ฃ๊ฒŒ ๋˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์•„.

์ด๋Ÿฐ ๋ฌธ์ œ๋“ค์„ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋“ฑ์žฅํ•œ ๊ฐœ๋…์ด ๋ฐ”๋กœ MVC ํŒจํ„ด์ด์•ผ! ๐ŸŽฏ

๐Ÿ—๏ธ MVC ํŒจํ„ด์˜ ๋“ฑ์žฅ

Model ๋ฐ์ดํ„ฐ์™€ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๋™ ๊ณ„์‚ฐ ๋ฐ ์ฒ˜๋ฆฌ View ํ™”๋ฉด ํ‘œ์‹œ JSP ํŽ˜์ด์ง€ HTML ๋ Œ๋”๋ง Controller ํ๋ฆ„ ์ œ์–ด ์š”์ฒญ ์ฒ˜๋ฆฌ ์‘๋‹ต ๊ฒฐ์ • ์‚ฌ์šฉ์ž ์š”์ฒญ

MVC๋Š” Model-View-Controller์˜ ์•ฝ์ž๋กœ, ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์„ธ ๊ฐ€์ง€ ์—ญํ• ๋กœ ๋ถ„๋ฆฌํ•˜๋Š” ๋””์ž์ธ ํŒจํ„ด์ด์•ผ. ๐ŸŽญ

Model (๋ชจ๋ธ): ๋ฐ์ดํ„ฐ์™€ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ๋‹ด๋‹นํ•ด. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ํ†ต์‹ ํ•˜๊ณ , ๊ณ„์‚ฐ์„ ์ˆ˜ํ–‰ํ•˜๊ณ , ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€๊ณตํ•˜๋Š” ์—ญํ• ์ด์ง€.

View (๋ทฐ): ์‚ฌ์šฉ์ž์—๊ฒŒ ๋ณด์—ฌ์ง€๋Š” ํ™”๋ฉด์„ ๋‹ด๋‹นํ•ด. JSP๊ฐ€ ์—ฌ๊ธฐ์— ํ•ด๋‹นํ•˜๊ณ , ์ˆœ์ˆ˜ํ•˜๊ฒŒ ๋ฐ์ดํ„ฐ๋ฅผ ํ‘œ์‹œํ•˜๋Š” ์—ญํ• ๋งŒ ํ•ด.

Controller (์ปจํŠธ๋กค๋Ÿฌ): ์‚ฌ์šฉ์ž์˜ ์š”์ฒญ์„ ๋ฐ›์•„์„œ Model๊ณผ View๋ฅผ ์—ฐ๊ฒฐํ•ด์ฃผ๋Š” ์ค‘๊ฐ„ ๊ด€๋ฆฌ์ž ์—ญํ• ์ด์•ผ. Servlet์ด ์ฃผ๋กœ ์ด ์—ญํ• ์„ ๋งก์•„.

์ด๋ ‡๊ฒŒ ๋ถ„๋ฆฌํ•˜๋ฉด ๊ฐ ๋ถ€๋ถ„์„ ๋…๋ฆฝ์ ์œผ๋กœ ๊ฐœ๋ฐœํ•˜๊ณ  ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ์–ด์„œ ํ›จ์”ฌ ํšจ์œจ์ ์ด์•ผ! ๐Ÿš€

MVC ํŒจํ„ด ์‹ค์ „ ์˜ˆ์ œ

๊ฐ„๋‹จํ•œ ๊ฒŒ์‹œํŒ ๋ชฉ๋ก ์กฐํšŒ ๊ธฐ๋Šฅ์„ MVC ํŒจํ„ด์œผ๋กœ ๋งŒ๋“ค์–ด๋ณผ๊นŒ? ๐Ÿ“‹

Model - Board.java (๋ฐ์ดํ„ฐ ํด๋ž˜์Šค)

public class Board {
    private int id;
    private String title;
    private String content;
    private String author;
    private Date createdDate;
    
    // ์ƒ์„ฑ์ž, getter, setter ์ƒ๋žต
}

Model - BoardDAO.java (๋ฐ์ดํ„ฐ ์ ‘๊ทผ ๊ฐ์ฒด)

public class BoardDAO {
    private Connection conn;
    
    public List<Board> getAllBoards() {
        List<Board> boards = new ArrayList<>();
        String sql = "SELECT * FROM board ORDER BY created_date DESC";
        
        try (PreparedStatement pstmt = conn.prepareStatement(sql);
             ResultSet rs = pstmt.executeQuery()) {
            
            while (rs.next()) {
                Board board = new Board();
                board.setId(rs.getInt("id"));
                board.setTitle(rs.getString("title"));
                board.setContent(rs.getString("content"));
                board.setAuthor(rs.getString("author"));
                board.setCreatedDate(rs.getDate("created_date"));
                boards.add(board);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        
        return boards;
    }
}

Controller - BoardListServlet.java

@WebServlet("/board/list")
public class BoardListServlet extends HttpServlet {
    
    @Override
    protected void doGet(HttpServletRequest request, 
                        HttpServletResponse response) 
                        throws ServletException, IOException {
        
        // 1. Model์„ ํ†ตํ•ด ๋ฐ์ดํ„ฐ ์กฐํšŒ
        BoardDAO dao = new BoardDAO();
        List<Board> boards = dao.getAllBoards();
        
        // 2. ๋ฐ์ดํ„ฐ๋ฅผ request์— ์ €์žฅ
        request.setAttribute("boards", boards);
        
        // 3. View๋กœ ํฌ์›Œ๋”ฉ
        RequestDispatcher dispatcher = 
            request.getRequestDispatcher("/WEB-INF/views/boardList.jsp");
        dispatcher.forward(request, response);
    }
}

View - boardList.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
    <title>๊ฒŒ์‹œํŒ ๋ชฉ๋ก</title>
</head>
<body>
    <h1>๊ฒŒ์‹œํŒ</h1>
    <table border="1">
        <tr>
            <th>๋ฒˆํ˜ธ</th>
            <th>์ œ๋ชฉ</th>
            <th>์ž‘์„ฑ์ž</th>
            <th>์ž‘์„ฑ์ผ</th>
        </tr>
        <c:forEach var="board" items="${boards}">
            <tr>
                <td>${board.id}</td>
                <td>${board.title}</td>
                <td>${board.author}</td>
                <td>${board.createdDate}</td>
            </tr>
        </c:forEach>
    </table>
</body>
</html>

์ด๋ ‡๊ฒŒ ๋ถ„๋ฆฌํ•˜๋‹ˆ๊นŒ ๊ฐ ๋ถ€๋ถ„์˜ ์—ญํ• ์ด ๋ช…ํ™•ํ•˜์ง€? Model์€ ๋ฐ์ดํ„ฐ๋งŒ, View๋Š” ํ™”๋ฉด๋งŒ, Controller๋Š” ํ๋ฆ„ ์ œ์–ด๋งŒ ๋‹ด๋‹นํ•ด. ๋‚˜์ค‘์— ํ™”๋ฉด ๋””์ž์ธ์„ ๋ฐ”๊พธ๊ณ  ์‹ถ์œผ๋ฉด JSP๋งŒ ์ˆ˜์ •ํ•˜๋ฉด ๋˜๊ณ , ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ตฌ์กฐ๊ฐ€ ๋ฐ”๋€Œ๋ฉด DAO๋งŒ ์ˆ˜์ •ํ•˜๋ฉด ๋ผ. ๐Ÿ‘Œ

๐ŸŒŸ Spring MVC ํ”„๋ ˆ์ž„์›Œํฌ์˜ ๋“ฑ์žฅ

MVC ํŒจํ„ด์œผ๋กœ ๊ฐœ๋ฐœํ•˜๋ฉด ์ข‹๊ธด ํ•œ๋ฐ, ๋งค๋ฒˆ Servlet์„ ๋งŒ๋“ค๊ณ  web.xml์— ๋“ฑ๋กํ•˜๊ณ , ๊ณตํ†ต ๊ธฐ๋Šฅ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ๋ฐ˜๋ณตํ•ด์„œ ์ž‘์„ฑํ•˜๋Š” ๊ฒŒ ๋ฒˆ๊ฑฐ๋กœ์›Œ. ๊ทธ๋ž˜์„œ ๋“ฑ์žฅํ•œ ๊ฒŒ ๋ฐ”๋กœ Spring MVC ํ”„๋ ˆ์ž„์›Œํฌ์•ผ! ๐ŸŽ‰

Spring MVC๋Š” 2004๋…„์— ์ฒ˜์Œ ๋“ฑ์žฅํ•œ ์ดํ›„, ์ž๋ฐ” ์›น ๊ฐœ๋ฐœ์˜ ์‚ฌ์‹ค์ƒ ํ‘œ์ค€์ด ๋˜์—ˆ์–ด. ์ „ ์„ธ๊ณ„ ์ˆ˜๋งŽ์€ ๊ธฐ์—…๋“ค์ด Spring์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๊ณ , ์žฌ๋Šฅ๋„ท ๊ฐ™์€ ํ”Œ๋žซํผ์—์„œ๋„ Spring ๊ฐœ๋ฐœ์ž๋ฅผ ๋งŽ์ด ์ฐพ๊ณ  ์žˆ์ง€. ๐Ÿ’ผ

Spring MVC์˜ ํ•ต์‹ฌ ๊ฐœ๋…

1. DispatcherServlet (ํ”„๋ก ํŠธ ์ปจํŠธ๋กค๋Ÿฌ)

Spring MVC์˜ ํ•ต์‹ฌ์€ DispatcherServlet์ด์•ผ. ์ด๊ฒŒ ๋ชจ๋“  ์š”์ฒญ์„ ๋ฐ›์•„์„œ ์ ์ ˆํ•œ ์ปจํŠธ๋กค๋Ÿฌ๋กœ ๋ถ„๋ฐฐํ•ด์ฃผ๋Š” ์—ญํ• ์„ ํ•ด. ์šฐ๋ฆฌ๊ฐ€ ์ง์ ‘ Servlet์„ ๋งŒ๋“ค ํ•„์š”๊ฐ€ ์—†์–ด์ ธ! ๐ŸŽฏ

DispatcherServlet์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ผ๋“ค์„ ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌํ•ด์ค˜:
- ์š”์ฒญ URL ๋ถ„์„
- ์ ์ ˆํ•œ ์ปจํŠธ๋กค๋Ÿฌ ์ฐพ๊ธฐ
- ํŒŒ๋ผ๋ฏธํ„ฐ ๋ฐ”์ธ๋”ฉ
- ๋ทฐ ๋ฆฌ์กธ๋น™
- ์˜ˆ์™ธ ์ฒ˜๋ฆฌ
- ์ธํ„ฐ์…‰ํ„ฐ ์‹คํ–‰

2. @Controller์™€ @RequestMapping

Spring์—์„œ๋Š” ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•ด์„œ ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ์ •์˜ํ•ด. @Controller ์–ด๋…ธํ…Œ์ด์…˜์„ ํด๋ž˜์Šค์— ๋ถ™์ด๊ณ , @RequestMapping์œผ๋กœ URL์„ ๋งคํ•‘ํ•˜๋ฉด ๋! โœจ

@Controller
@RequestMapping("/board")
public class BoardController {
    
    @Autowired
    private BoardService boardService;
    
    @GetMapping("/list")
    public String list(Model model) {
        List<Board> boards = boardService.getAllBoards();
        model.addAttribute("boards", boards);
        return "board/list";  // ๋ทฐ ์ด๋ฆ„
    }
    
    @GetMapping("/detail/{id}")
    public String detail(@PathVariable int id, Model model) {
        Board board = boardService.getBoard(id);
        model.addAttribute("board", board);
        return "board/detail";
    }
    
    @PostMapping("/write")
    public String write(@ModelAttribute Board board) {
        boardService.saveBoard(board);
        return "redirect:/board/list";
    }
}

๋ณด์ด์ง€? Servlet์„ ์ƒ์†๋ฐ›์ง€๋„ ์•Š๊ณ , doGet/doPost ๋ฉ”์„œ๋“œ๋„ ์—†์–ด. ๊ทธ๋ƒฅ ์ผ๋ฐ˜ ๋ฉ”์„œ๋“œ์ฒ˜๋Ÿผ ์ž‘์„ฑํ•˜๋ฉด Spring์ด ์•Œ์•„์„œ ์ฒ˜๋ฆฌํ•ด์ค˜. ์ •๋ง ํŽธ๋ฆฌํ•˜์ง€? ๐Ÿ˜

3. ์˜์กด์„ฑ ์ฃผ์ž… (Dependency Injection)

Spring์˜ ๊ฐ€์žฅ ๊ฐ•๋ ฅํ•œ ๊ธฐ๋Šฅ ์ค‘ ํ•˜๋‚˜๊ฐ€ ๋ฐ”๋กœ ์˜์กด์„ฑ ์ฃผ์ž…์ด์•ผ. @Autowired ์–ด๋…ธํ…Œ์ด์…˜๋งŒ ๋ถ™์ด๋ฉด ํ•„์š”ํ•œ ๊ฐ์ฒด๋ฅผ ์ž๋™์œผ๋กœ ์ฃผ์ž…ํ•ด์ค˜. ๐Ÿ’‰

@Service
public class BoardService {
    
    @Autowired
    private BoardDAO boardDAO;
    
    public List<Board> getAllBoards() {
        return boardDAO.getAllBoards();
    }
    
    public Board getBoard(int id) {
        return boardDAO.getBoard(id);
    }
    
    @Transactional
    public void saveBoard(Board board) {
        boardDAO.saveBoard(board);
    }
}

์ง์ ‘ new BoardDAO() ๊ฐ™์€ ์ฝ”๋“œ๋ฅผ ์“ฐ์ง€ ์•Š์•„๋„ ๋ผ. Spring ์ปจํ…Œ์ด๋„ˆ๊ฐ€ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ๊ด€๋ฆฌํ•ด์ฃผ๊ธฐ ๋•Œ๋ฌธ์—, ์šฐ๋ฆฌ๋Š” ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์—๋งŒ ์ง‘์ค‘ํ•  ์ˆ˜ ์žˆ์–ด. ๐ŸŽฏ

Spring MVC์˜ ์š”์ฒญ ์ฒ˜๋ฆฌ ํ๋ฆ„

1. ํด๋ผ์ด์–ธํŠธ ์š”์ฒญ ์ „์†ก 2. Dispatcher Servlet ์š”์ฒญ ์ˆ˜์‹  3. Handler Mapping ์ปจํŠธ๋กค๋Ÿฌ ์ฐพ๊ธฐ 4. Handler Adapter ์ปจํŠธ๋กค๋Ÿฌ ์‹คํ–‰ 5. Controller ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง 6. Service ์„œ๋น„์Šค ์ฒ˜๋ฆฌ 7. View Resolver ๋ทฐ ๊ฒฐ์ • 8. View ๋ Œ๋”๋ง 9. ํด๋ผ์ด์–ธํŠธ ์‘๋‹ต ์ˆ˜์‹  Spring MVC ์ฒ˜๋ฆฌ ํ๋ฆ„ ์„ค๋ช… 1-2. ํด๋ผ์ด์–ธํŠธ ์š”์ฒญ์ด DispatcherServlet์œผ๋กœ ์ „๋‹ฌ 3-4. ์ ์ ˆํ•œ ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ์ฐพ์•„ ์‹คํ–‰ 5-6. ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ์ฒ˜๋ฆฌ ํ›„ ๋ทฐ ์ด๋ฆ„ ๋ฐ˜ํ™˜ 7-8. ViewResolver๊ฐ€ ์‹ค์ œ ๋ทฐ๋ฅผ ์ฐพ์•„ ๋ Œ๋”๋ง 9. ์ตœ์ข… ๊ฒฐ๊ณผ๋ฅผ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ์‘๋‹ต

๋ณต์žกํ•ด ๋ณด์ด์ง€๋งŒ, ์‹ค์ œ๋กœ ๊ฐœ๋ฐœ์ž๊ฐ€ ์‹ ๊ฒฝ ์จ์•ผ ํ•  ๋ถ€๋ถ„์€ 5๋ฒˆ Controller์™€ 6๋ฒˆ Service ์ •๋„์•ผ. ๋‚˜๋จธ์ง€๋Š” Spring์ด ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌํ•ด์ค˜! ๐Ÿค–

Spring MVC์˜ ์ฃผ์š” ์–ด๋…ธํ…Œ์ด์…˜

Spring MVC๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ์ž์ฃผ ์“ฐ๋Š” ์–ด๋…ธํ…Œ์ด์…˜๋“ค์„ ์ •๋ฆฌํ•ด๋ณผ๊ฒŒ. ๐Ÿ“Œ

์–ด๋…ธํ…Œ์ด์…˜ ์„ค๋ช… ์‚ฌ์šฉ ์˜ˆ์‹œ
@Controller ์ปจํŠธ๋กค๋Ÿฌ ํด๋ž˜์Šค ์ง€์ • @Controller
public class UserController { }
@RestController RESTful API ์ปจํŠธ๋กค๋Ÿฌ
(JSON ์‘๋‹ต)
@RestController
public class ApiController { }
@RequestMapping URL ๋งคํ•‘ (๋ชจ๋“  HTTP ๋ฉ”์„œ๋“œ) @RequestMapping("/user")
@GetMapping GET ์š”์ฒญ ๋งคํ•‘ @GetMapping("/list")
@PostMapping POST ์š”์ฒญ ๋งคํ•‘ @PostMapping("/save")
@PutMapping PUT ์š”์ฒญ ๋งคํ•‘ (์ˆ˜์ •) @PutMapping("/update/{id}")
@DeleteMapping DELETE ์š”์ฒญ ๋งคํ•‘ (์‚ญ์ œ) @DeleteMapping("/delete/{id}")
@RequestParam ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ ๋ฐ”์ธ๋”ฉ @RequestParam String name
@PathVariable URL ๊ฒฝ๋กœ ๋ณ€์ˆ˜ ๋ฐ”์ธ๋”ฉ @PathVariable int id
@ModelAttribute ํผ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ์ฒด๋กœ ๋ฐ”์ธ๋”ฉ @ModelAttribute User user
@RequestBody HTTP ์š”์ฒญ ๋ณธ๋ฌธ์„ ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜ @RequestBody User user
@ResponseBody ๋ฐ˜ํ™˜๊ฐ’์„ HTTP ์‘๋‹ต ๋ณธ๋ฌธ์œผ๋กœ @ResponseBody String result

์‹ค์ „ Spring MVC ์˜ˆ์ œ

์ด์ œ ์‹ค์ œ๋กœ Spring MVC๋กœ RESTful API๋ฅผ ๋งŒ๋“ค์–ด๋ณผ๊นŒ? ์‚ฌ์šฉ์ž ๊ด€๋ฆฌ API๋ฅผ ์˜ˆ์ œ๋กœ ๋“ค์–ด๋ณผ๊ฒŒ. ๐Ÿ‘จโ€๐Ÿ’ป

Entity ํด๋ž˜์Šค

@Entity
@Table(name = "users")
public class User {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(nullable = false, unique = true)
    private String username;
    
    @Column(nullable = false)
    private String password;
    
    @Column(nullable = false)
    private String email;
    
    @CreatedDate
    private LocalDateTime createdDate;
    
    // getter, setter, ์ƒ์„ฑ์ž ์ƒ๋žต
}

Repository ์ธํ„ฐํŽ˜์ด์Šค

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    
    Optional<User> findByUsername(String username);
    
    boolean existsByUsername(String username);
    
    boolean existsByEmail(String email);
    
    List<User> findByEmailContaining(String keyword);
}

JPA๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๊ธฐ๋ณธ์ ์ธ CRUD ๋ฉ”์„œ๋“œ๋Š” ์ž๋™์œผ๋กœ ์ œ๊ณต๋˜๊ณ , ๋ฉ”์„œ๋“œ ์ด๋ฆ„๋งŒ์œผ๋กœ ์ฟผ๋ฆฌ๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์–ด. ์ •๋ง ํŽธ๋ฆฌํ•˜์ง€? โœจ

Service ํด๋ž˜์Šค

@Service
@Transactional
public class UserService {
    
    @Autowired
    private UserRepository userRepository;
    
    @Autowired
    private PasswordEncoder passwordEncoder;
    
    public List<User> getAllUsers() {
        return userRepository.findAll();
    }
    
    public User getUser(Long id) {
        return userRepository.findById(id)
            .orElseThrow(() -> new UserNotFoundException("์‚ฌ์šฉ์ž๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."));
    }
    
    public User createUser(UserDto userDto) {
        // ์ค‘๋ณต ์ฒดํฌ
        if (userRepository.existsByUsername(userDto.getUsername())) {
            throw new DuplicateUserException("์ด๋ฏธ ์กด์žฌํ•˜๋Š” ์‚ฌ์šฉ์ž๋ช…์ž…๋‹ˆ๋‹ค.");
        }
        
        // ๋น„๋ฐ€๋ฒˆํ˜ธ ์•”ํ˜ธํ™”
        User user = new User();
        user.setUsername(userDto.getUsername());
        user.setPassword(passwordEncoder.encode(userDto.getPassword()));
        user.setEmail(userDto.getEmail());
        
        return userRepository.save(user);
    }
    
    public User updateUser(Long id, UserDto userDto) {
        User user = getUser(id);
        user.setEmail(userDto.getEmail());
        return userRepository.save(user);
    }
    
    public void deleteUser(Long id) {
        userRepository.deleteById(id);
    }
}

Controller ํด๋ž˜์Šค

@RestController
@RequestMapping("/api/users")
public class UserController {
    
    @Autowired
    private UserService userService;
    
    // ์ „์ฒด ์‚ฌ์šฉ์ž ์กฐํšŒ
    @GetMapping
    public ResponseEntity<List<User>> getAllUsers() {
        List<User> users = userService.getAllUsers();
        return ResponseEntity.ok(users);
    }
    
    // ํŠน์ • ์‚ฌ์šฉ์ž ์กฐํšŒ
    @GetMapping("/{id}")
    public ResponseEntity<User> getUser(@PathVariable Long id) {
        User user = userService.getUser(id);
        return ResponseEntity.ok(user);
    }
    
    // ์‚ฌ์šฉ์ž ์ƒ์„ฑ
    @PostMapping
    public ResponseEntity<User> createUser(@Valid @RequestBody UserDto userDto) {
        User user = userService.createUser(userDto);
        return ResponseEntity.status(HttpStatus.CREATED).body(user);
    }
    
    // ์‚ฌ์šฉ์ž ์ˆ˜์ •
    @PutMapping("/{id}")
    public ResponseEntity<User> updateUser(
            @PathVariable Long id,
            @Valid @RequestBody UserDto userDto) {
        User user = userService.updateUser(id, userDto);
        return ResponseEntity.ok(user);
    }
    
    // ์‚ฌ์šฉ์ž ์‚ญ์ œ
    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
        userService.deleteUser(id);
        return ResponseEntity.noContent().build();
    }
    
    // ์˜ˆ์™ธ ์ฒ˜๋ฆฌ
    @ExceptionHandler(UserNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleUserNotFound(UserNotFoundException e) {
        ErrorResponse error = new ErrorResponse("USER_NOT_FOUND", e.getMessage());
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
    }
}

์ด๋ ‡๊ฒŒ ์ž‘์„ฑํ•˜๋ฉด RESTful API๊ฐ€ ์™„์„ฑ๋ผ! ๊ฐ ๋ฉ”์„œ๋“œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ HTTP ์š”์ฒญ์— ์‘๋‹ตํ•ด:

- GET /api/users โ†’ ์ „์ฒด ์‚ฌ์šฉ์ž ๋ชฉ๋ก
- GET /api/users/1 โ†’ ID๊ฐ€ 1์ธ ์‚ฌ์šฉ์ž ์ •๋ณด
- POST /api/users โ†’ ์ƒˆ ์‚ฌ์šฉ์ž ์ƒ์„ฑ
- PUT /api/users/1 โ†’ ID๊ฐ€ 1์ธ ์‚ฌ์šฉ์ž ์ˆ˜์ •
- DELETE /api/users/1 โ†’ ID๊ฐ€ 1์ธ ์‚ฌ์šฉ์ž ์‚ญ์ œ

๊น”๋”ํ•˜๊ณ  ์ง๊ด€์ ์ด์ง€? ๐Ÿ‘Œ

Spring Boot์˜ ๋“ฑ์žฅ

Spring MVC๋„ ์ถฉ๋ถ„ํžˆ ๊ฐ•๋ ฅํ•˜์ง€๋งŒ, ์„ค์ •์ด ๋ณต์žกํ•˜๋‹ค๋Š” ๋‹จ์ ์ด ์žˆ์—ˆ์–ด. XML ์„ค์ • ํŒŒ์ผ์„ ์ž‘์„ฑํ•˜๊ณ , ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์˜์กด์„ฑ์„ ๊ด€๋ฆฌํ•˜๊ณ , ์„œ๋ฒ„๋ฅผ ์„ค์ •ํ•˜๋Š” ๊ฒŒ ๋ฒˆ๊ฑฐ๋กœ์› ์ง€. ๐Ÿ˜“

๊ทธ๋ž˜์„œ 2014๋…„์— Spring Boot๊ฐ€ ๋“ฑ์žฅํ–ˆ์–ด! Spring Boot๋Š” "์„ค์ •๋ณด๋‹ค ๊ด€๋ก€(Convention over Configuration)"๋ผ๋Š” ์ฒ ํ•™์œผ๋กœ, ๋ณต์žกํ•œ ์„ค์ • ์—†์ด๋„ ๋ฐ”๋กœ ์‹คํ–‰ ๊ฐ€๋Šฅํ•œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค˜. ๐Ÿš€

Spring Boot์˜ ์ฃผ์š” ํŠน์ง•

1. ์ž๋™ ์„ค์ • (Auto Configuration)
ํด๋ž˜์ŠคํŒจ์Šค์— ์žˆ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๋ณด๊ณ  ์ž๋™์œผ๋กœ ์„ค์ •์„ ํ•ด์ค˜. ์˜ˆ๋ฅผ ๋“ค์–ด, H2 ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๊ฐ€ ์žˆ์œผ๋ฉด ์ž๋™์œผ๋กœ ๋ฐ์ดํ„ฐ์†Œ์Šค๋ฅผ ์„ค์ •ํ•˜๊ณ , Thymeleaf๊ฐ€ ์žˆ์œผ๋ฉด ๋ทฐ ๋ฆฌ์กธ๋ฒ„๋ฅผ ์„ค์ •ํ•ด์ค˜.

2. ๋‚ด์žฅ ์„œ๋ฒ„
Tomcat, Jetty, Undertow ๊ฐ™์€ ์„œ๋ฒ„๊ฐ€ ๋‚ด์žฅ๋˜์–ด ์žˆ์–ด์„œ ๋ณ„๋„๋กœ ์„œ๋ฒ„๋ฅผ ์„ค์น˜ํ•  ํ•„์š”๊ฐ€ ์—†์–ด. JAR ํŒŒ์ผ ํ•˜๋‚˜๋กœ ์‹คํ–‰ ๊ฐ€๋Šฅํ•ด!

3. ์Šคํƒ€ํ„ฐ ์˜์กด์„ฑ
spring-boot-starter-web, spring-boot-starter-data-jpa ๊ฐ™์€ ์Šคํƒ€ํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ•˜๋ฉด ํ•„์š”ํ•œ ๋ชจ๋“  ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ์ž๋™์œผ๋กœ ํฌํ•จ๋ผ.

4. ํ”„๋กœ๋•์…˜ ์ค€๋น„ ๊ธฐ๋Šฅ
ํ—ฌ์Šค ์ฒดํฌ, ๋ฉ”ํŠธ๋ฆญ, ๋ชจ๋‹ˆํ„ฐ๋ง ๊ฐ™์€ ์šด์˜์— ํ•„์š”ํ•œ ๊ธฐ๋Šฅ๋“ค์ด ๊ธฐ๋ณธ์œผ๋กœ ์ œ๊ณต๋ผ.

Spring Boot ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹œ์ž‘ํ•˜๊ธฐ

@SpringBootApplication
public class MyApplication {
    
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

์ด๊ฒŒ ์ „๋ถ€์•ผ! ์ด ์ฝ”๋“œ๋งŒ ์žˆ์œผ๋ฉด Spring Boot ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์‹คํ–‰๋ผ. @SpringBootApplication ์–ด๋…ธํ…Œ์ด์…˜ ํ•˜๋‚˜๊ฐ€ ๋‹ค์Œ ์„ธ ๊ฐ€์ง€๋ฅผ ํฌํ•จํ•˜๊ณ  ์žˆ์–ด:

- @Configuration: ์„ค์ • ํด๋ž˜์Šค์ž„์„ ํ‘œ์‹œ
- @EnableAutoConfiguration: ์ž๋™ ์„ค์ • ํ™œ์„ฑํ™”
- @ComponentScan: ์ปดํฌ๋„ŒํŠธ ์Šค์บ” ํ™œ์„ฑํ™”

์ •๋ง ๊ฐ„๋‹จํ•˜์ง€? ๐Ÿ˜Š

application.properties ์„ค์ •

Spring Boot์—์„œ๋Š” XML ๋Œ€์‹  application.properties ๋˜๋Š” application.yml ํŒŒ์ผ๋กœ ์„ค์ •์„ ๊ด€๋ฆฌํ•ด. ํ›จ์”ฌ ๊ฐ„๋‹จํ•˜๊ณ  ์ง๊ด€์ ์ด์•ผ. ๐Ÿ“„

# ์„œ๋ฒ„ ํฌํŠธ ์„ค์ •
server.port=8080

# ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„ค์ •
spring.datasource.url=jdbc:mysql://localhost:3306/mydb
spring.datasource.username=root
spring.datasource.password=1234
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# JPA ์„ค์ •
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true

# ๋กœ๊น… ๋ ˆ๋ฒจ
logging.level.root=INFO
logging.level.com.myapp=DEBUG

# ํŒŒ์ผ ์—…๋กœ๋“œ ์„ค์ •
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=10MB

# Thymeleaf ์„ค์ •
spring.thymeleaf.cache=false
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html

์ด๋ ‡๊ฒŒ ๊ฐ„๋‹จํ•œ ์„ค์ •๋งŒ์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ, JPA ์„ค์ •, ๋กœ๊น…, ํŒŒ์ผ ์—…๋กœ๋“œ ๋“ฑ ๋ชจ๋“  ๊ฒŒ ๋™์ž‘ํ•ด. ์˜ˆ์ „์—๋Š” ์ด๋Ÿฐ ์„ค์ •์„ XML๋กœ ์ˆ˜์‹ญ ์ค„์”ฉ ์ž‘์„ฑํ•ด์•ผ ํ–ˆ๋Š”๋ฐ, ์ •๋ง ํŽธํ•ด์กŒ์ง€? ๐ŸŽ‰

๐Ÿ”ฅ ์ตœ์‹  ํŠธ๋ Œ๋“œ์™€ ์‹ค๋ฌด ํŒ

์ด์ œ ์‹ค๋ฌด์—์„œ ์ž์ฃผ ์‚ฌ์šฉํ•˜๋Š” ํŒจํ„ด๊ณผ ์ตœ์‹  ํŠธ๋ Œ๋“œ๋ฅผ ์•Œ์•„๋ณผ๊นŒ? ์žฌ๋Šฅ๋„ท ๊ฐ™์€ ํ”Œ๋žซํผ์—์„œ ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ํ•  ๋•Œ ์•Œ์•„๋‘๋ฉด ์ •๋ง ์œ ์šฉํ•  ๊ฑฐ์•ผ! ๐Ÿ’ก

1. RESTful API ์„ค๊ณ„ ์›์น™

๋ฆฌ์†Œ์Šค ์ค‘์‹ฌ ์„ค๊ณ„ URL์€ ๋™์‚ฌ๊ฐ€ ์•„๋‹Œ ๋ช…์‚ฌ๋กœ ํ‘œํ˜„ํ•˜๊ณ , HTTP ๋ฉ”์„œ๋“œ๋กœ ํ–‰์œ„๋ฅผ ๋‚˜ํƒ€๋‚ด.

โŒ ๋‚˜์œ ์˜ˆ:
- /getUsers
- /createUser
- /updateUser/1
- /deleteUser/1

โœ… ์ข‹์€ ์˜ˆ:
- GET /users (๋ชฉ๋ก ์กฐํšŒ)
- POST /users (์ƒ์„ฑ)
- PUT /users/1 (์ˆ˜์ •)
- DELETE /users/1 (์‚ญ์ œ)

์ ์ ˆํ•œ HTTP ์ƒํƒœ ์ฝ”๋“œ ์‚ฌ์šฉ
- 200 OK: ์„ฑ๊ณต
- 201 Created: ์ƒ์„ฑ ์„ฑ๊ณต
- 204 No Content: ์„ฑ๊ณตํ–ˆ์ง€๋งŒ ๋ฐ˜ํ™˜ํ•  ๋‚ด์šฉ ์—†์Œ
- 400 Bad Request: ์ž˜๋ชป๋œ ์š”์ฒญ
- 401 Unauthorized: ์ธ์ฆ ํ•„์š”
- 403 Forbidden: ๊ถŒํ•œ ์—†์Œ
- 404 Not Found: ๋ฆฌ์†Œ์Šค ์—†์Œ
- 500 Internal Server Error: ์„œ๋ฒ„ ์˜ค๋ฅ˜

2. DTO (Data Transfer Object) ํŒจํ„ด

Entity๋ฅผ ์ง์ ‘ ํด๋ผ์ด์–ธํŠธ์— ๋…ธ์ถœํ•˜์ง€ ๋ง๊ณ , DTO๋ฅผ ์‚ฌ์šฉํ•ด์„œ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋งŒ ์ „๋‹ฌํ•˜๋Š” ๊ฒŒ ์ข‹์•„. ๐Ÿ”’

// Entity (๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ…Œ์ด๋ธ”๊ณผ ๋งคํ•‘)
@Entity
public class User {
    private Long id;
    private String username;
    private String password;  // ๋ฏผ๊ฐํ•œ ์ •๋ณด
    private String email;
    private LocalDateTime createdDate;
}

// DTO (ํด๋ผ์ด์–ธํŠธ์™€ ํ†ต์‹ )
public class UserResponseDto {
    private Long id;
    private String username;
    private String email;
    // password๋Š” ํฌํ•จํ•˜์ง€ ์•Š์Œ!
    
    public UserResponseDto(User user) {
        this.id = user.getId();
        this.username = user.getUsername();
        this.email = user.getEmail();
    }
}

// Controller์—์„œ ์‚ฌ์šฉ
@GetMapping("/{id}")
public ResponseEntity<UserResponseDto> getUser(@PathVariable Long id) {
    User user = userService.getUser(id);
    UserResponseDto dto = new UserResponseDto(user);
    return ResponseEntity.ok(dto);
}

๐Ÿ’ก Tip: DTO๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋น„๋ฐ€๋ฒˆํ˜ธ ๊ฐ™์€ ๋ฏผ๊ฐํ•œ ์ •๋ณด๋ฅผ ์‹ค์ˆ˜๋กœ ๋…ธ์ถœํ•˜๋Š” ๊ฑธ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ์–ด. ๋˜ํ•œ API ๋ฒ„์ „์ด ๋ฐ”๋€Œ์–ด๋„ Entity๋Š” ๊ทธ๋Œ€๋กœ ์œ ์ง€ํ•˜๋ฉด์„œ DTO๋งŒ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ์–ด์„œ ์œ ์—ฐ์„ฑ์ด ๋†’์•„์ ธ!

3. ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ์ „๋žต

์ „์—ญ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ชจ๋“  ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์˜ˆ์™ธ๋ฅผ ํ•œ ๊ณณ์—์„œ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด. ๐Ÿ›ก๏ธ

@RestControllerAdvice
public class GlobalExceptionHandler {
    
    @ExceptionHandler(UserNotFoundException.class)
    public ResponseEntity<ErrorResponse> handleUserNotFound(UserNotFoundException e) {
        ErrorResponse error = ErrorResponse.builder()
            .code("USER_NOT_FOUND")
            .message(e.getMessage())
            .timestamp(LocalDateTime.now())
            .build();
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
    }
    
    @ExceptionHandler(DuplicateUserException.class)
    public ResponseEntity<ErrorResponse> handleDuplicateUser(DuplicateUserException e) {
        ErrorResponse error = ErrorResponse.builder()
            .code("DUPLICATE_USER")
            .message(e.getMessage())
            .timestamp(LocalDateTime.now())
            .build();
        return ResponseEntity.status(HttpStatus.CONFLICT).body(error);
    }
    
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ErrorResponse> handleValidationError(
            MethodArgumentNotValidException e) {
        List<String> errors = e.getBindingResult()
            .getFieldErrors()
            .stream()
            .map(error -> error.getField() + ": " + error.getDefaultMessage())
            .collect(Collectors.toList());
            
        ErrorResponse error = ErrorResponse.builder()
            .code("VALIDATION_ERROR")
            .message("์ž…๋ ฅ๊ฐ’ ๊ฒ€์ฆ ์‹คํŒจ")
            .errors(errors)
            .timestamp(LocalDateTime.now())
            .build();
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error);
    }
    
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleGeneralError(Exception e) {
        ErrorResponse error = ErrorResponse.builder()
            .code("INTERNAL_ERROR")
            .message("์„œ๋ฒ„ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.")
            .timestamp(LocalDateTime.now())
            .build();
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
    }
}

4. ์œ ํšจ์„ฑ ๊ฒ€์ฆ (Validation)

Bean Validation์„ ์‚ฌ์šฉํ•˜๋ฉด ์ž…๋ ฅ๊ฐ’ ๊ฒ€์ฆ์„ ๊ฐ„๋‹จํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด. โœ…

public class UserCreateDto {
    
    @NotBlank(message = "์‚ฌ์šฉ์ž๋ช…์€ ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค.")
    @Size(min = 3, max = 20, message = "์‚ฌ์šฉ์ž๋ช…์€ 3~20์ž์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค.")
    @Pattern(regexp = "^[a-zA-Z0-9_]+$", 
             message = "์‚ฌ์šฉ์ž๋ช…์€ ์˜๋ฌธ, ์ˆซ์ž, ์–ธ๋”์Šค์ฝ”์–ด๋งŒ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.")
    private String username;
    
    @NotBlank(message = "๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค.")
    @Size(min = 8, message = "๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” ์ตœ์†Œ 8์ž ์ด์ƒ์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.")
    private String password;
    
    @NotBlank(message = "์ด๋ฉ”์ผ์€ ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค.")
    @Email(message = "์˜ฌ๋ฐ”๋ฅธ ์ด๋ฉ”์ผ ํ˜•์‹์ด ์•„๋‹™๋‹ˆ๋‹ค.")
    private String email;
    
    @Min(value = 0, message = "๋‚˜์ด๋Š” 0 ์ด์ƒ์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.")
    @Max(value = 150, message = "๋‚˜์ด๋Š” 150 ์ดํ•˜์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค.")
    private Integer age;
}

// Controller์—์„œ ์‚ฌ์šฉ
@PostMapping
public ResponseEntity<User> createUser(@Valid @RequestBody UserCreateDto dto) {
    // @Valid ์–ด๋…ธํ…Œ์ด์…˜์ด ์ž๋™์œผ๋กœ ๊ฒ€์ฆ์„ ์ˆ˜ํ–‰
    User user = userService.createUser(dto);
    return ResponseEntity.status(HttpStatus.CREATED).body(user);
}

5. ํŽ˜์ด์ง•๊ณผ ์ •๋ ฌ

๋Œ€๋Ÿ‰์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค๋ฃฐ ๋•Œ๋Š” ํŽ˜์ด์ง•์ด ํ•„์ˆ˜์•ผ. Spring Data JPA๋Š” ํŽ˜์ด์ง•์„ ์•„์ฃผ ์‰ฝ๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค˜. ๐Ÿ“„

// Repository
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    Page<User> findByUsernameContaining(String keyword, Pageable pageable);
}

// Service
@Service
public class UserService {
    
    @Autowired
    private UserRepository userRepository;
    
    public Page<User> searchUsers(String keyword, int page, int size, String sortBy) {
        Pageable pageable = PageRequest.of(page, size, Sort.by(sortBy).descending());
        return userRepository.findByUsernameContaining(keyword, pageable);
    }
}

// Controller
@GetMapping("/search")
public ResponseEntity<Page<UserResponseDto>> searchUsers(
        @RequestParam String keyword,
        @RequestParam(defaultValue = "0") int page,
        @RequestParam(defaultValue = "10") int size,
        @RequestParam(defaultValue = "createdDate") String sortBy) {
    
    Page<User> users = userService.searchUsers(keyword, page, size, sortBy);
    Page<UserResponseDto> dtos = users.map(UserResponseDto::new);
    return ResponseEntity.ok(dtos);
}

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ํด๋ผ์ด์–ธํŠธ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์š”์ฒญ์œผ๋กœ ํŽ˜์ด์ง•๋œ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ›์„ ์ˆ˜ ์žˆ์–ด:
GET /api/users/search?keyword=kim&page=0&size=10&sortBy=createdDate

6. ๋ณด์•ˆ (Spring Security)

์‹ค๋ฌด์—์„œ๋Š” ๋ณด์•ˆ์ด ์ •๋ง ์ค‘์š”ํ•ด. Spring Security๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ธ์ฆ๊ณผ ๊ถŒํ•œ ๊ด€๋ฆฌ๋ฅผ ์‰ฝ๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์–ด. ๐Ÿ”

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf().disable()
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/public/**").permitAll()
                .requestMatchers("/api/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
            )
            .sessionManagement(session -> session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            )
            .addFilterBefore(jwtAuthenticationFilter(), 
                           UsernamePasswordAuthenticationFilter.class);
        
        return http.build();
    }
    
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

7. ํ…Œ์ŠคํŠธ ์ž‘์„ฑ

์ข‹์€ ๊ฐœ๋ฐœ์ž๋Š” ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•ด. Spring Boot๋Š” ํ…Œ์ŠคํŠธ ์ž‘์„ฑ์„ ์œ„ํ•œ ๋‹ค์–‘ํ•œ ๋„๊ตฌ๋ฅผ ์ œ๊ณตํ•ด. ๐Ÿงช

@SpringBootTest
@AutoConfigureMockMvc
class UserControllerTest {
    
    @Autowired
    private MockMvc mockMvc;
    
    @Autowired
    private ObjectMapper objectMapper;
    
    @MockBean
    private UserService userService;
    
    @Test
    @DisplayName("์‚ฌ์šฉ์ž ์ƒ์„ฑ ํ…Œ์ŠคํŠธ")
    void createUser() throws Exception {
        // Given
        UserCreateDto dto = new UserCreateDto();
        dto.setUsername("testuser");
        dto.setPassword("password123");
        dto.setEmail("test@example.com");
        
        User user = new User();
        user.setId(1L);
        user.setUsername(dto.getUsername());
        user.setEmail(dto.getEmail());
        
        when(userService.createUser(any())).thenReturn(user);
        
        // When & Then
        mockMvc.perform(post("/api/users")
                .contentType(MediaType.APPLICATION_JSON)
                .content(objectMapper.writeValueAsString(dto)))
            .andExpect(status().isCreated())
            .andExpect(jsonPath("$.id").value(1))
            .andExpect(jsonPath("$.username").value("testuser"))
            .andExpect(jsonPath("$.email").value("test@example.com"));
    }
    
    @Test
    @DisplayName("์‚ฌ์šฉ์ž ์กฐํšŒ ํ…Œ์ŠคํŠธ")
    void getUser() throws Exception {
        // Given
        User user = new User();
        user.setId(1L);
        user.setUsername("testuser");
        user.setEmail("test@example.com");
        
        when(userService.getUser(1L)).thenReturn(user);
        
        // When & Then
        mockMvc.perform(get("/api/users/1"))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.username").value("testuser"));
    }
}

๐ŸŽ“ ํ•™์Šต ๋กœ๋“œ๋งต๊ณผ ์ถ”์ฒœ ์ž๋ฃŒ

์ž๋ฐ” ์›น ๊ฐœ๋ฐœ์„ ์ฒ˜์Œ ์‹œ์ž‘ํ•˜๋Š” ์‚ฌ๋žŒ๋“ค์„ ์œ„ํ•œ ํ•™์Šต ์ˆœ์„œ๋ฅผ ์ •๋ฆฌํ•ด๋ณผ๊ฒŒ. ๐Ÿ“š

์ž๋ฐ” ์›น ๊ฐœ๋ฐœ ํ•™์Šต ๋กœ๋“œ๋งต 1๋‹จ๊ณ„ ์ž๋ฐ” ๊ธฐ์ดˆ ๋ฌธ๋ฒ•, OOP 2๋‹จ๊ณ„ Servlet/JSP ์›น ๊ธฐ์ดˆ 3๋‹จ๊ณ„ MVC ํŒจํ„ด ์„ค๊ณ„ ํŒจํ„ด 4๋‹จ๊ณ„ Spring ํ”„๋ ˆ์ž„์›Œํฌ 5๋‹จ๊ณ„ Spring Boot ์‹ค๋ฌด ๊ธฐ์ˆ  6๋‹จ๊ณ„ JPA/DB ๋ฐ์ดํ„ฐ ๊ด€๋ฆฌ 7๋‹จ๊ณ„ ๋ณด์•ˆ/ํ…Œ์ŠคํŠธ ํ’ˆ์งˆ ํ–ฅ์ƒ ๊ฐ ๋‹จ๊ณ„๋ณ„ ํ•™์Šต ๊ธฐ๊ฐ„ (์ฐธ๊ณ ์šฉ) 1๋‹จ๊ณ„: 2-3๊ฐœ์›” (์ž๋ฐ” ๊ธฐ์ดˆ ๋ฌธ๋ฒ•, ๊ฐ์ฒด์ง€ํ–ฅ) 2๋‹จ๊ณ„: 1-2๊ฐœ์›” (Servlet, JSP, HTTP ์ดํ•ด) 3๋‹จ๊ณ„: 2-3์ฃผ (MVC ํŒจํ„ด, ๋””์ž์ธ ํŒจํ„ด) 4-7๋‹จ๊ณ„: 3-4๊ฐœ์›” (Spring ์ƒํƒœ๊ณ„ ์ „๋ฐ˜) ์ด ํ•™์Šต ๊ธฐ๊ฐ„: ์•ฝ 6-9๊ฐœ์›” ์‹ค๋ฌด ํ”„๋กœ์ ํŠธ ๊ฒฝํ—˜: ์ถ”๊ฐ€ 3-6๊ฐœ์›” ์ง€์†์ ์ธ ํ•™์Šต๊ณผ ์‹ค์Šต์ด ์ค‘์š”!

์ถ”์ฒœ ํ•™์Šต ์ž๋ฃŒ

๐Ÿ“– ์ฑ…

1. ์ดˆ๊ธ‰
- "Head First Servlets and JSP" - ๊ทธ๋ฆผ์œผ๋กœ ์‰ฝ๊ฒŒ ๋ฐฐ์šฐ๋Š” ์„œ๋ธ”๋ฆฟ๊ณผ JSP
- "์ž๋ฐ” ์›น ํ”„๋กœ๊ทธ๋ž˜๋ฐ Next Step" - ์‹ค์Šต ์ค‘์‹ฌ์˜ ์ž…๋ฌธ์„œ

2. ์ค‘๊ธ‰
- "ํ† ๋น„์˜ ์Šคํ”„๋ง 3.1" - Spring์˜ ๋ฐ”์ด๋ธ”
- "์Šคํ”„๋ง ๋ถ€ํŠธ์™€ AWS๋กœ ํ˜ผ์ž ๊ตฌํ˜„ํ•˜๋Š” ์›น ์„œ๋น„์Šค" - ์‹ค์ „ ํ”„๋กœ์ ํŠธ

3. ๊ณ ๊ธ‰
- "์Šคํ”„๋ง ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์ฝ”๋”ฉ ๊ณต์ž‘์†Œ" - MSA ์•„ํ‚คํ…์ฒ˜
- "์ž๋ฐ” ORM ํ‘œ์ค€ JPA ํ”„๋กœ๊ทธ๋ž˜๋ฐ" - JPA ์™„๋ฒฝ ๊ฐ€์ด๋“œ

๐ŸŒ ์˜จ๋ผ์ธ ๊ฐ•์˜

- ์ธํ”„๋Ÿฐ: "์Šคํ”„๋ง ์ž…๋ฌธ", "์Šคํ”„๋ง ํ•ต์‹ฌ ์›๋ฆฌ" (๊น€์˜ํ•œ)
- ์œ ๋ฐ๋ฏธ: "Spring Framework Master Class"
- ๋ฐฑ๊ธฐ์„ ์˜ ์Šคํ”„๋ง ๋ถ€ํŠธ ์‹œ๋ฆฌ์ฆˆ
- ์ƒํ™œ์ฝ”๋”ฉ: ์„œ๋ธ”๋ฆฟ๊ณผ JSP ๊ธฐ์ดˆ

๐Ÿ’ป ์‹ค์Šต ํ”„๋กœ์ ํŠธ ์•„์ด๋””์–ด

1. ๊ฒŒ์‹œํŒ ์‹œ์Šคํ…œ
- ํšŒ์›๊ฐ€์ž…/๋กœ๊ทธ์ธ
- CRUD ๊ธฐ๋Šฅ
- ํŽ˜์ด์ง•, ๊ฒ€์ƒ‰
- ํŒŒ์ผ ์—…๋กœ๋“œ

2. ์‡ผํ•‘๋ชฐ
- ์ƒํ’ˆ ๊ด€๋ฆฌ
- ์žฅ๋ฐ”๊ตฌ๋‹ˆ
- ์ฃผ๋ฌธ/๊ฒฐ์ œ
- ๋ฆฌ๋ทฐ ์‹œ์Šคํ…œ

3. ๋ธ”๋กœ๊ทธ ํ”Œ๋žซํผ
- ๋งˆํฌ๋‹ค์šด ์—๋””ํ„ฐ
- ํƒœ๊ทธ ์‹œ์Šคํ…œ
- ๋Œ“๊ธ€ ๊ธฐ๋Šฅ
- RSS ํ”ผ๋“œ

4. TODO ๊ด€๋ฆฌ ์•ฑ
- RESTful API
- ์‹ค์‹œ๊ฐ„ ์•Œ๋ฆผ
- ํ˜‘์—… ๊ธฐ๋Šฅ
- ํ†ต๊ณ„ ๋Œ€์‹œ๋ณด๋“œ

๐Ÿš€ ์‹ค๋ฌด์—์„œ์˜ ํ™œ์šฉ

์ด์ œ ์‹ค๋ฌด์—์„œ ์–ด๋–ป๊ฒŒ ํ™œ์šฉ๋˜๋Š”์ง€ ์•Œ์•„๋ณผ๊นŒ? ์žฌ๋Šฅ๋„ท ๊ฐ™์€ ํ”Œ๋žซํผ์—์„œ ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ํ•  ๋•Œ ์ฐธ๊ณ ํ•˜๋ฉด ์ข‹์„ ๊ฑฐ์•ผ! ๐Ÿ’ผ

ํ˜„๋Œ€์ ์ธ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์•„ํ‚คํ…์ฒ˜

์š”์ฆ˜ ์‹ค๋ฌด์—์„œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๊ตฌ์กฐ๋ฅผ ๋งŽ์ด ์‚ฌ์šฉํ•ด:

ํ”„๋ก ํŠธ์—”๋“œ (React, Vue, Angular)
        โ†“ REST API
๋ฐฑ์—”๋“œ (Spring Boot)
        โ†“
์„œ๋น„์Šค ๊ณ„์ธต (๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง)
        โ†“
๋ฐ์ดํ„ฐ ์ ‘๊ทผ ๊ณ„์ธต (JPA, MyBatis)
        โ†“
๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค (MySQL, PostgreSQL)

๊ณ„์ธต๋ณ„ ์—ญํ• 

Controller ๊ณ„์ธต
- HTTP ์š”์ฒญ/์‘๋‹ต ์ฒ˜๋ฆฌ
- ์ž…๋ ฅ๊ฐ’ ๊ฒ€์ฆ
- ์ธ์ฆ/๊ถŒํ•œ ์ฒดํฌ
- ์˜ˆ์™ธ ์ฒ˜๋ฆฌ

Service ๊ณ„์ธต
- ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ๊ตฌํ˜„
- ํŠธ๋žœ์žญ์…˜ ๊ด€๋ฆฌ
- ์—ฌ๋Ÿฌ Repository ์กฐํ•ฉ
- ๋„๋ฉ”์ธ ๊ทœ์น™ ์ ์šฉ

Repository ๊ณ„์ธต
- ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ ‘๊ทผ
- CRUD ์—ฐ์‚ฐ
- ์ฟผ๋ฆฌ ์‹คํ–‰
- ๋ฐ์ดํ„ฐ ๋ณ€ํ™˜

์„ฑ๋Šฅ ์ตœ์ ํ™” ํŒ

1. N+1 ๋ฌธ์ œ ํ•ด๊ฒฐ JPA๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ๊ฐ€์žฅ ํ”ํ•œ ์„ฑ๋Šฅ ๋ฌธ์ œ์•ผ. Fetch Join์ด๋‚˜ Entity Graph๋ฅผ ์‚ฌ์šฉํ•ด์„œ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์–ด.

2. ์บ์‹ฑ ํ™œ์šฉ
์ž์ฃผ ์กฐํšŒ๋˜๋Š” ๋ฐ์ดํ„ฐ๋Š” Redis๋‚˜ Spring Cache๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์บ์‹ฑํ•˜๋ฉด ์„ฑ๋Šฅ์ด ํฌ๊ฒŒ ํ–ฅ์ƒ๋ผ.

3. ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ
์‹œ๊ฐ„์ด ์˜ค๋ž˜ ๊ฑธ๋ฆฌ๋Š” ์ž‘์—…์€ @Async๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๋น„๋™๊ธฐ๋กœ ์ฒ˜๋ฆฌํ•˜๋ฉด ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์ด ์ข‹์•„์ ธ.

4. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ธ๋ฑ์Šค
๊ฒ€์ƒ‰์ด ์ž์ฃผ ์ผ์–ด๋‚˜๋Š” ์ปฌ๋Ÿผ์—๋Š” ์ธ๋ฑ์Šค๋ฅผ ์ƒ์„ฑํ•ด์„œ ์กฐํšŒ ์†๋„๋ฅผ ๋†’์—ฌ.

5. ํŽ˜์ด์ง• ์ฒ˜๋ฆฌ
๋Œ€๋Ÿ‰์˜ ๋ฐ์ดํ„ฐ๋ฅผ ํ•œ ๋ฒˆ์— ์กฐํšŒํ•˜์ง€ ๋ง๊ณ  ํŽ˜์ด์ง•์„ ์ ์šฉํ•ด์„œ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰์„ ์ค„์—ฌ.

๋ณด์•ˆ ์ฒดํฌ๋ฆฌ์ŠคํŠธ

โœ… SQL Injection ๋ฐฉ์ง€ (PreparedStatement ์‚ฌ์šฉ)
โœ… XSS ๊ณต๊ฒฉ ๋ฐฉ์ง€ (์ž…๋ ฅ๊ฐ’ ์ด์Šค์ผ€์ดํ”„ ์ฒ˜๋ฆฌ)
โœ… CSRF ํ† ํฐ ์‚ฌ์šฉ
โœ… ๋น„๋ฐ€๋ฒˆํ˜ธ ์•”ํ˜ธํ™” (BCrypt)
โœ… HTTPS ์‚ฌ์šฉ
โœ… JWT ํ† ํฐ ๋งŒ๋ฃŒ ์‹œ๊ฐ„ ์„ค์ •
โœ… ๋ฏผ๊ฐํ•œ ์ •๋ณด ๋กœ๊น… ๊ธˆ์ง€
โœ… ๊ถŒํ•œ ์ฒดํฌ (๋ฉ”์„œ๋“œ ๋ ˆ๋ฒจ)
โœ… ํŒŒ์ผ ์—…๋กœ๋“œ ๊ฒ€์ฆ
โœ… Rate Limiting ์ ์šฉ

๐ŸŽฏ ๋งˆ๋ฌด๋ฆฌํ•˜๋ฉฐ

์™€, ์ •๋ง ๊ธด ์—ฌ์ •์ด์—ˆ์ง€? ๐Ÿ˜… Servlet๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด์„œ JSP, MVC ํŒจํ„ด, Spring MVC, ๊ทธ๋ฆฌ๊ณ  Spring Boot๊นŒ์ง€ ์ž๋ฐ” ์›น ๊ฐœ๋ฐœ์˜ ์ „์ฒด ํ๋ฆ„์„ ์‚ดํŽด๋ดค์–ด.

์ฒ˜์Œ์—๋Š” ๋ณต์žกํ•ด ๋ณด์ผ ์ˆ˜ ์žˆ์ง€๋งŒ, ํ•˜๋‚˜์”ฉ ์ฐจ๊ทผ์ฐจ๊ทผ ๋ฐฐ์›Œ๊ฐ€๋‹ค ๋ณด๋ฉด ์–ด๋А์ƒˆ ์‹ค๋ฌด์—์„œ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์ˆ˜์ค€์ด ๋  ๊ฑฐ์•ผ. ์ค‘์š”ํ•œ ๊ฑด ๊พธ์ค€ํžˆ ์‹ค์Šตํ•˜๊ณ , ์ž‘์€ ํ”„๋กœ์ ํŠธ๋ผ๋„ ์ง์ ‘ ๋งŒ๋“ค์–ด๋ณด๋Š” ๊ฑฐ์•ผ. ๐Ÿ’ช

์žฌ๋Šฅ๋„ท ๊ฐ™์€ ํ”Œ๋žซํผ์—์„œ๋Š” Spring Boot๋ฅผ ํ™œ์šฉํ•œ ๋‹ค์–‘ํ•œ ํ”„๋กœ์ ํŠธ๋“ค์ด ์ง„ํ–‰๋˜๊ณ  ์žˆ์–ด. ๊ธฐ์ดˆ๋ฅผ ํƒ„ํƒ„ํžˆ ๋‹ค์ง€๊ณ  ๋‚˜๋ฉด, ์‹ค์ „ ํ”„๋กœ์ ํŠธ์— ์ฐธ์—ฌํ•ด์„œ ๊ฒฝํ—˜์„ ์Œ“๋Š” ๊ฒƒ๋„ ์ข‹์€ ๋ฐฉ๋ฒ•์ด์•ผ. ์‹ค๋ฌด ๊ฒฝํ—˜์„ ํ†ตํ•ด ๋ฐฐ์šฐ๋Š” ๊ฒŒ ๊ฐ€์žฅ ๋น ๋ฅด๊ณ  ํšจ๊ณผ์ ์ด๊ฑฐ๋“ ! ๐Ÿš€

์ž๋ฐ” ์›น ๊ฐœ๋ฐœ์€ ๊ณ„์† ๋ฐœ์ „ํ•˜๊ณ  ์žˆ์–ด. Spring 6, Spring Boot 3, Virtual Threads, GraalVM ๊ฐ™์€ ์ƒˆ๋กœ์šด ๊ธฐ์ˆ ๋“ค์ด ๊ณ„์† ๋‚˜์˜ค๊ณ  ์žˆ์ง€. ํ•˜์ง€๋งŒ ๊ธฐ๋ณธ ์›๋ฆฌ๋Š” ๋ณ€ํ•˜์ง€ ์•Š์•„. Servlet, JSP, MVC ํŒจํ„ด ๊ฐ™์€ ๊ธฐ์ดˆ๋ฅผ ์ž˜ ์ดํ•ดํ•˜๊ณ  ์žˆ์œผ๋ฉด, ์ƒˆ๋กœ์šด ๊ธฐ์ˆ ๋„ ์‰ฝ๊ฒŒ ๋ฐฐ์šธ ์ˆ˜ ์žˆ์–ด.

์ด ๊ธ€์ด ์ž๋ฐ” ์›น ๊ฐœ๋ฐœ์„ ์‹œ์ž‘ํ•˜๋Š” ๋ฐ ๋„์›€์ด ๋˜์—ˆ์œผ๋ฉด ์ข‹๊ฒ ์–ด. ๊ถ๊ธˆํ•œ ์ ์ด ์žˆ๊ฑฐ๋‚˜ ๋” ์•Œ๊ณ  ์‹ถ์€ ๋‚ด์šฉ์ด ์žˆ๋‹ค๋ฉด, ์žฌ๋Šฅ๋„ท์˜ ์ง€์‹์ธ์˜ ์ˆฒ์—์„œ ๋‹ค๋ฅธ ๊ธ€๋“ค๋„ ์ฐพ์•„๋ณด๊ณ , ์ „๋ฌธ๊ฐ€๋“ค๊ณผ ์†Œํ†ตํ•ด๋ณด๋Š” ๊ฒƒ๋„ ์ถ”์ฒœํ•ด! ๐ŸŒณ

์ž, ์ด์ œ ์ฝ”๋”ฉ์„ ์‹œ์ž‘ํ•ด๋ณผ๊นŒ? ํ™”์ดํŒ…! ๐ŸŽ‰

โœจ Happy Coding! โœจ

๋Œ“๊ธ€ ์ž‘์„ฑ

์ด ๊ธ€์— ๋Œ€ํ•œ ์—ฌ๋Ÿฌ๋ถ„์˜ ์ƒ๊ฐ์„ ๋“ค๋ ค์ฃผ์„ธ์š”

๋Œ“๊ธ€ 0
๋Œ“๊ธ€์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘์ž…๋‹ˆ๋‹ค.

์•„์ง ๋Œ“๊ธ€์ด ์—†์Šต๋‹ˆ๋‹ค.