๐Ÿ’ป ์ฝ”๋”ฉ ์ผ๊ธฐ : [Spring JDBC] 'PreparedStatement๋ฅผ ์‚ฌ์šฉํ•œ Spring JDBC ๊ตฌํ˜„' ํŽธ

ybkยท2024๋…„ 4์›” 17์ผ

spring

๋ชฉ๋ก ๋ณด๊ธฐ
11/55
post-thumbnail

๐Ÿ”” 'PreparedStatement๋ฅผ ์ด์šฉํ•œ Spring JDBC ๊ตฌํ˜„'์— ๋Œ€ํ•ด์„œ ์•Œ์•„๋ณด์ž!


๐Ÿ’Ÿ PreparedStatement

PreparedStatement : SQL ์ฟผ๋ฆฌ๋ฅผ ๋ฏธ๋ฆฌ ์ปดํŒŒ์ผํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ „์†กํ•˜๊ธฐ ์ „์— ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ „๋‹ฌ๋œ ๊ฐ’์„ ์‚ฝ์ž…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • SQL Injection ๋ฐฉ์ง€

    • SQL Injection : ๊ณต๊ฒฉ์ž๊ฐ€ ์•…์˜์ ์ธ SQL ์ฟผ๋ฆฌ๋ฅผ ์‹คํ–‰ํ•˜๋„๋ก ํ•จ์œผ๋กœ์จ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ ‘๊ทผํ•˜๊ณ  ์กฐ์ž‘ํ•˜๋Š” ๊ณต๊ฒฉ ๊ธฐ๋ฒ•์ž…๋‹ˆ๋‹ค. ๋งŒ์•ฝ ์™ธ๋ถ€์—์„œ SELECT๋ฌธ WHERE์ ˆ์—์„œ 1=1(ํ•ญ์ƒ ์ฐธ)์„ ๋„ฃ์–ด์ฃผ๊ฒŒ ๋œ๋‹ค๋ฉด ๋ชจ๋“  ์ •๋ณด๊ฐ€ ๋…ธ์ถœ๋  ์œ„ํ—˜์ด ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์ฟผ๋ฆฌ์˜ ์žฌ์‚ฌ์šฉ์„ฑ

  • ์„ฑ๋Šฅ ํ–ฅ์ƒ


๐Ÿ“ข Statement๊ณผ PreparedStatement์˜ ์ฐจ์ด

Statement:

  • ๋ฐ์ดํ„ฐ๋ฅผ ์ง์ ‘ ์ฟผ๋ฆฌ ๋ฌธ์ž์—ด์— ํฌํ•จํ•ด์„œ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.
  • SQL Injection ๊ณต๊ฒฉ์— ๋…ธ์ถœ๋  ์œ„ํ—˜์ด ์žˆ์Šต๋‹ˆ๋‹ค.
    String sql = STR."SELECT * FROM Employees WHERE LastName = '\{searchName}'";

PreparedStatement:

  • ์ฟผ๋ฆฌ ๋ฌธ์ž์—ด ๋Œ€์‹  ?๋ฅผ ์‚ฌ์šฉํ•ด ๋ฐ์ดํ„ฐ๋ฅผ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.
  • ๋ฐ์ดํ„ฐ๋ฅผ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ „๋‹ฌํ•˜๊ณ  ์ฟผ๋ฆฌ ๋ฌธ์ž์—ด์— ํฌํ•จ๋˜๊ธฐ ์ „์— DB์— ์•ˆ์ „ํ•˜๊ฒŒ ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค.
  • setXXX()๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ? ์œ„์น˜์— ๊ฐ’์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    String sql = "SELECT * FROM Employees WHERE LastName = ?";

๐Ÿ’Ÿ Statement์™€ PreparedStatement ๊ด€๋ จ ์˜ˆ์ œ

๐ŸŸฆ Statement ์ •์  ์ฒ˜๋ฆฌ

MyBean252.java

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class MyBean252 {
    private Integer id;
    private String name;
    private String unit;
    private Double price;
}

Controller25.java

@GetMapping("sub2")
public void method2(@RequestParam(value = "name", required = false) String search,
                    @ModelAttribute("products") ArrayList<MyBean252> list) throws SQLException {

    String sql = STR."""
            SELECT * FROM Products WHERE ProductName LIKE '%\{search}%'
            """;

    Statement statement = dataSource.getConnection().createStatement();
    ResultSet resultSet = statement.executeQuery(sql);

    try(resultSet; statement){
        while (resultSet.next()) {
            Integer id = resultSet.getInt(1);
            String name = resultSet.getString(2);
            String unit = resultSet.getString(5);
            Double price = resultSet.getDouble(6);

            MyBean252 bean252 = new MyBean252(id, name, unit, price);
            list.add(bean252);
        }
    }
}
  • @RequestParam(value = "name", required = false) String search : ํด๋ผ์ด์–ธํŠธ์—์„œ ํ•ด๋‹น ์š”์ฒญ์„ ๋ณด๋‚ผ ๋•Œ, name ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ์š”์ฒญ์— ๋ฐ˜๋“œ์‹œ ํฌํ•จ๋˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— URL์— name=์ด ์—†์–ด๋„ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. (์„ ํƒ์  ์ „๋‹ฌ)
  • ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์š”์ฒญํ•œ nameํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ SQL ์ฟผ๋ฆฌ์— search์— ํ• ๋‹นํ•˜์—ฌ ์ƒํ’ˆ์„ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค.
  • MyBean252 ์—์„œ id, name, unit, price๋ฅผ ๋ฆฌ์ŠคํŠธ์— ๋„ฃ์–ด sub2.jsp์—์„œ ${prodcuts}๋กœ ๋ฐ›์•„ ์ƒํ’ˆ ๋ชฉ๋ก์„ ํ™”๋ฉด์— ์ถœ๋ ฅํ•ฉ๋‹ˆ๋‹ค.

sub2.jsp

<body>
<h3>์ƒํ’ˆ๋ช…์œผ๋กœ ์ƒํ’ˆ ์กฐํšŒ</h3>
<form action="/main25/sub2">
    ์ƒํ’ˆ๋ช… :
    <input type="text" name="name" placeholder="์ƒํ’ˆ๋ช…์„ ์ž…๋ ฅํ•˜์„ธ์š”.">
    <input type="submit" value="์กฐํšŒ">
</form>
<hr>
<div>
    <c:forEach items="${products}" var="product">
        <h4>${product.id}๋ฒˆ ์ƒํ’ˆ</h4>
        <p>์ƒํ’ˆ๋ช… :
            <input type="text" value="${product.name}" readonly>
        </p>
        <p>๋‹จ์œ„ :
            <input type="text" value="${product.unit}" readonly>
        </p>
        <p> ๊ฐ€๊ฒฉ :
            <input type="text" value="${product.price}" readonly>
            ๋‹ฌ๋Ÿฌ
        </p>
    </c:forEach>
</div>
</body>


๐ŸŸฆ PreparedStatement ๋™์  ์ฒ˜๋ฆฌ

MyBean256Product.java

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class MyBean256Product {
    private Integer id;
    private String name;
    private Integer supplierId;
    private Integer categoryId;
    private String unit;
    private Double price;
}

Controller25.java

@Controller
@RequestMapping("main25")
public class Controller25 {
    @Autowired
    private DataSource dataSource;

    @GetMapping("sub6")
    public void mehtod6(String search, Model model) throws SQLException {

        var list = new ArrayList<MyBean256Product>();
        String sql = "SELECT * FROM Products WHERE ProductName LIKE ?";
        String keyword = "%"+search+"%";

        PreparedStatement preparedStatement = dataSource.getConnection().prepareStatement(sql);
        preparedStatement.setString(1, keyword);
        ResultSet resultSet = preparedStatement.executeQuery();

        try(resultSet; preparedStatement){
            while(resultSet.next()){
                Integer id = resultSet.getInt(1);
                String name = resultSet.getString(2);
                Integer supplierId = resultSet.getInt(3);
                Integer categoryId = resultSet.getInt(4);
                String unit = resultSet.getString(5);
                Double price = resultSet.getDouble(6);

                MyBean256Product bean = new MyBean256Product(id, name, supplierId, categoryId, unit, price);

                list.add(bean);
            }
        }

        model.addAttribute("products", list);
        model.addAttribute("prevSearch", search);
    }

}
  • SQL Injection ๋ฐฉ์ง€๋ฅผ ์œ„ํ•ด ๊ฒ€์ƒ‰์–ด๋ฅผ ?๋กœ ์ฒ˜๋ฆฌํ•˜์—ฌ PreparedStatement๋ฅผ ์‚ฌ์šฉํ•จ์œผ๋กœ์จ ์•ˆ์ „ํ•˜๊ฒŒ ์ฟผ๋ฆฌ๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.
  • keyword๋Š” SQL์—์„œ LIKE ์—ฐ์‚ฐ์ž๋Š” ์™€์ผ๋“œ ์นด๋“œ(%, _)๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํ•ด๋‹น ๊ฒ€์ƒ‰์–ด ์•ž ๋’ค์— ์™€์ผ๋“œ ์นด๋“œ๋ฅผ ์ถ”๊ฐ€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • PreparedStatement ์‚ฌ์šฉํ•˜์—ฌ setString()์„ ํ†ตํ•ด ์–ด๋А ์œ„์น˜์— ๊ฐ’์„ ๋„ฃ์–ด์ฃผ๋Š”์ง€ ์ •ํ•ฉ๋‹ˆ๋‹ค. ๋งŒ์•ฝ ?๊ฐ€ ์—ฌ๋Ÿฌ ๊ฐœ์ผ ๊ฒฝ์šฐ ? ์ˆ˜ ๋งŒํผ setXXX() ๋ฉ”์„œ๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.
  • ์ด์ „ ๊ฒ€์ƒ‰ ๊ธฐ๋ก์„ ์œ ์ง€ํ•˜๊ธฐ ์œ„ํ•ด์„œ ${prevSearch}๋Š” Spring MVC์˜ Model ๊ฐ์ฒด์— addAttribute() ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ถ”๊ฐ€๋ฉ๋‹ˆ๋‹ค. Controller์—์„œ Model.addAttribute("prevSearch", search)์™€ ๊ฐ™์ด ์ด์ „ ๊ฒ€์ƒ‰์–ด๋ฅผ ๋ชจ๋ธ์— ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ ๋‹ค์Œ JSP ํŒŒ์ผ์—์„œ๋Š” ${prevSearch}๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ด์ „ ๊ฒ€์ƒ‰์–ด๋ฅผ ์ถœ๋ ฅํ•ฉ๋‹ˆ๋‹ค.
    • ๋ชจ๋ธ์— ์†์„ฑ์„ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์€ ๋ฐ์ดํ„ฐ๋ฅผ ๋ทฐ๋กœ ์ „๋‹ฌํ•˜๊ธฐ ์œ„ํ•œ ๋ฉ”์ปค๋‹ˆ์ฆ˜์ž…๋‹ˆ๋‹ค.

sub6.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="jakarta.tags.core" %>
<html>
<head>
    <title>Title</title>
    <style>
        table, tr, th, td {
            border: 1px solid black;
            border-collapse: collapse;
        }

        table {
            width: 100%;
        }
    </style>
</head>
<body>
<h3>์ƒํ’ˆ ๋ชฉ๋ก</h3>
<%--action์˜ ๊ฐ’์ด ์—†๊ฑฐ๋‚˜ ์ƒ๋žต๋˜๋ฉด ํ˜„์žฌ ์š”์ฒญ ๊ฒฝ๋กœ๋กœ ๋ณด๋ƒ„--%>
<form>
    ์ƒํ’ˆ๋ช…
    <input value="${prevSearch}" name="search" type="text" placeholder="์กฐํšŒํ•  ์ƒํ’ˆ๋ช…์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”." size="30" >
<%--    form ๋‚ด์˜ button ์š”์†Œ๋Š” submit ๋ฒ„ํŠผ ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค.--%>
    <button>์กฐํšŒ</button>
<%--    <input type="submit" value="์กฐํšŒํ•˜๊ธฐ">--%>
</form>
<hr>

<c:if test="${empty products}" var="emptyProduct">
    <p style="background-color: beige; padding: 20px 20px; width: 300px">์กฐํšŒ๋œ ์ƒํ’ˆ์ด ์—†์Šต๋‹ˆ๋‹ค.</p>
</c:if>
<c:if test="${not emptyProduct}">
    <table>
        <thead style="background-color: burlywood">
        <tr>
            <th>์•„์ด๋””</th>
            <th>์ƒํ’ˆ๋ช…</th>
            <th>๊ณต๊ธ‰์—…์ฒด</th>
            <th>์นดํ…Œ๊ณ ๋ฆฌ</th>
            <th>Unit</th>
            <th>๊ฐ€๊ฒฉ</th>
        </tr>
        </thead>
        <tbody>
        <c:forEach items="${products}" var="product">
            <tr style="background-color: beige">
                <td>${product.id}</td>
                <td>${product.name}</td>
                <td>${product.supplierId}</td>
                <td>${product.categoryId}</td>
                <td>${product.unit}</td>
                <td>${product.price}</td>
            </tr>
        </c:forEach>
        </tbody>
    </table>
</c:if>
</body>
</html>
  • ์ƒํ’ˆ๋ช…์„ ๊ฒ€์ƒ‰ํ•ด์„œ ์กฐํšŒ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด ์กฐํšŒ๋œ ์ƒํ’ˆ์ด ์žˆ๋‹ค๋ฉด ํ•ด๋‹น ์ƒํ’ˆ ๋ชฉ๋ก์ด ์ถœ๋ ฅ๋˜๋ฉฐ ์กฐํšŒ๋œ ์ƒํ’ˆ์ด ์—†๋‹ค๋ฉด '์กฐํšŒ๋œ ์ƒํ’ˆ์ด ์—†์Šต๋‹ˆ๋‹ค.'๋ผ๊ณ  ์ถœ๋ ฅ๋ฉ๋‹ˆ๋‹ค.

์ถœ๋ ฅ๊ฒฐ๊ณผ

1) ์ฒซ ํ™”๋ฉด ๋ฐ ์กฐํšŒ๋œ ์ƒํ’ˆ์ด ์—†์„ ๋•Œ

2) ์ƒํ’ˆ์ด ์žˆ์„ ๋•Œ ์ƒํ’ˆ ๋ชฉ๋ก ํ™”๋ฉด

3) ์ด์ „ ๊ฒ€์ƒ‰ ๊ธฐ๋ก์ด ๋‚จ๊ฒจ์ง„ ํ™”๋ฉด

profile
๊ฐœ๋ฐœ์ž ์ค€๋น„์ƒ~

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