๐ŸŽฝ ์ธํ”„๋Ÿฐ ์›Œ๋ฐ์—… ํด๋Ÿฝ 0๊ธฐ ๋ฐฑ์—”๋“œ ๋„ค ๋ฒˆ์งธ ๊ณผ์ œ

nyangยท2024๋…„ 2์›” 22์ผ
0

์ธํ”„๋Ÿฐ

๋ชฉ๋ก ๋ณด๊ธฐ
4/7
post-thumbnail

์ถœ์ฒ˜
https://inf.run/XKQg

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์‚ฌ์šฉํ•ด ๋งŒ๋“œ๋Š” API ๊ฐ•์˜์—์„œ JdbcTemplate์„ ์‚ฌ์šฉํ•˜์—ฌ DB์— SQL๋ฌธ์„ ์‹คํ–‰ํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ํ•™์Šตํ–ˆ๋‹ค.

๐ŸŒˆ JdbcTemplate์ด๋ž€?

JDBC(Java Database Connectivity)์˜ ํ•ต์‹ฌ ๋ถ€๋ถ„์„ ๋‹ด๋‹นํ•˜๋Š” ์ค‘์‹ฌ ๋Œ€๋ฆฌ์ž์ด๋‹ค.
๊ฐ„๋‹จํžˆ ๋งํ•˜๋ฉด, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€์˜ ์ƒํ˜ธ ์ž‘์šฉ์„ ๋„์™€์ฃผ๋Š” ๋„๊ตฌ๋ผ๊ณ  ์ƒ๊ฐํ•˜๋ฉด ๋œ๋‹ค.

  1. SQL ์ฟผ๋ฆฌ๋ฅผ ์‹คํ–‰ํ•˜๊ณ , ๊ทธ ๊ฒฐ๊ณผ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๋“ฑ์˜ JDBC ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•œ๋‹ค.
    ์šฐ๋ฆฌ๊ฐ€ ์ž‘์„ฑํ•˜๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ฝ”๋“œ๋Š” SQL ์ฟผ๋ฆฌ๋ฅผ ์ œ๊ณตํ•˜๊ณ  ๊ฒฐ๊ณผ๋ฅผ ์ถ”์ถœํ•˜๋Š” ์—ญํ• ์„ ํ•œ๋‹ค.
  • ์ด ํด๋ž˜์Šค๋Š” ResultSets์„ ์ฒ˜๋ฆฌํ•˜๊ณ  JDBC ์˜ˆ์™ธ๋ฅผ ๋‹ค๋ฃจ์–ด ์ฃผ๊ธฐ์ ์œผ๋กœ ๋ฐœ์ƒํ•˜๋Š” ๋ฌธ์ œ๋“ค์„ ์ฒ˜๋ฆฌํ•ด์ค€๋‹ค.
  • PreparedStatementCreator๋Š” Connection๊ณผ SQL, ๊ทธ๋ฆฌ๊ณ  ํ•„์š”ํ•œ ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๋ฐ›์•„์„œ ์ค€๋น„๋œ ๋ฌธ์„ ๋งŒ๋“ค์–ด์ฃผ๋Š” ์—ญํ• ์„ ํ•œ๋‹ค.
  • ResultSetExtractor๋Š” ResultSet์—์„œ ๊ฐ’์„ ์ถ”์ถœํ•œ๋‹ค.
  • ์ด ์™ธ์—๋„ PreparedStatementSetter๋‚˜ RowMapper ๊ฐ™์€ ๋‹ค์–‘ํ•œ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
  1. ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋“œ ํ™˜๊ฒฝ์—์„œ๋„ ์•ˆ์ „ํ•˜๊ฒŒ ๋™์ž‘ํ•œ๋‹ค.
    ์„œ๋น„์Šค ๋‚ด์—์„œ ์ง์ ‘ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ปจํ…์ŠคํŠธ์—์„œ ๋นˆ(Bean)์œผ๋กœ ์„ค์ •ํ•˜์—ฌ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
    ์ค‘์š”ํ•œ ์ ์€ DataSource(๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€์˜ ์—ฐ๊ฒฐ์„ ๋‚˜ํƒ€๋‚ด๋Š” ๊ฐ์ฒด)๋Š” ํ•ญ์ƒ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ปจํ…์ŠคํŠธ์— ๋นˆ์œผ๋กœ ์„ค์ •๋˜์–ด์•ผ ํ•œ๋‹ค.

โ›ณ๏ธ Table ์ƒ์„ฑ

create table fruit
(
    id              bigint auto_increment,
    name            varchar(20),
    warehousingDate date,
    price           long,
    is_sold boolean default 0,
    primary key (id)
);

๋จผ์ €, fruit ํ…Œ์ด๋ธ”์— ์–ด๋–ค ์นผ๋Ÿผ์ด ํ•„์š”ํ•œ์ง€ ํ™•์ธํ–ˆ๋‹ค.
๋ฌธ์ œ 1์˜ HTTP ์š”์ฒญ ๋ณธ๋ฌธ์—์„œ name, warehosuingDate, price๊ฐ€ ํ•„์š”ํ•˜๊ณ , ๋ฌธ์ œ 2, ๋ฌธ์ œ 3์„ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ํ•ด๋‹น ๊ณผ์ผ์ด ํŒ๋งค๋œ ์ƒํƒœ์ธ์ง€๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š”is_sold ๋ผ๋Š” ์นผ๋Ÿผ์„ ์ถ”๊ฐ€ํ–ˆ๋‹ค.

๋ฐ์ดํ„ฐ์˜ ํ‘œํ˜„ ๋ฒ”์œ„๋ฅผ ํ™•์žฅํ•˜๊ธฐ ์œ„ํ•ด price ์นผ๋Ÿผ์˜ ๋ฐ์ดํ„ฐ ํƒ€์ž…์œผ๋กœ long์„ ์„ ํƒํ–ˆ๋‹ค.
๋˜ํ•œ, is_sold ์นผ๋Ÿผ์—๋Š” default๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ธฐ๋ณธ๊ฐ’์ด 0์ด ๋˜๋„๋ก ์„ค์ •ํ–ˆ๋‹ค.
(๊ณผ์ผ์ด ํŒ๋งค๋˜์ง€ ์•Š์€ ์ƒํƒœ์ผ ๋•Œ, ๋ณ„๋„์˜ ๊ฐ’ ์ง€์ • ์—†์ด 0์ด ์ž๋™์œผ๋กœ ํ• ๋‹น๋˜์–ด ์ง๊ด€์ ์ด๊ณ  ๋ฐ์ดํ„ฐ์˜ ์ผ๊ด€์„ฑ์„ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.)


โ›ณ๏ธ ๋ฌธ์ œ1

๐Ÿ’ป ์ตœ์ข… ์ฝ”๋“œ

public record FruitInfoRequest(String name, LocalDate warehousingDate, long price) {
}

@RestController
@RequiredArgsConstructor
public class FruitController {

    private final JdbcTemplate jdbcTemplate;

    @PostMapping("/api/v1/fruit")
    public void saveFruit(@RequestBody FruitInfoRequest request) {
        String sql = "insert into fruit (name, warehousingDate, price) values (?, ?, ?)";
        jdbcTemplate.update(sql, request.name(), request.warehousingDate(), request.price());
    }
}

HTTP POST ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฉ”์†Œ๋“œ๋กœ, /api/v1/fruit ์—”๋“œํฌ์ธํŠธ์— ๋งคํ•‘๋˜์–ด ์žˆ๋‹ค.
ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ „์†กํ•œ HTTP ์š”์ฒญ์˜ ๋ณธ๋ฌธ(@RequestBody FruitInfoRequest request)์—์„œ ๋ฐ›์€ ๊ณผ์ผ ์ •๋ณด๋ฅผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ 'fruit' ํ…Œ์ด๋ธ”์— ์ €์žฅํ•œ๋‹ค.

๋ฉ”์†Œ๋“œ ๋‚ด๋ถ€์—์„œ๋Š” SQL ์ฟผ๋ฆฌ๋ฅผ ์ •์˜ํ•˜๊ณ , JdbcTemplate์˜ update ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํ•ด๋‹น ์ฟผ๋ฆฌ๋ฅผ ์‹คํ–‰ํ•œ๋‹ค.
์ด๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ƒˆ๋กœ์šด ๊ณผ์ผ ์ •๋ณด๊ฐ€ ์ž…๋ ฅ๋˜๋ฉฐ, SQL ์ฟผ๋ฆฌ์—์„œ๋Š” name, warehousingDate, price ๊ฐ’์ด FruitInfoRequest ๊ฐ์ฒด์—์„œ ๊ฐ€์ ธ์˜จ ๊ฐ’์œผ๋กœ ๋Œ€์ฒด๋œ๋‹ค.

โ–ถ๏ธ ์‹คํ–‰ ๊ฒฐ๊ณผ


โ›ณ๏ธ ๋ฌธ์ œ2

๐Ÿ’ป ์ตœ์ข… ์ฝ”๋“œ

public record FruitIdRequest(long id) {}

@RestController
@RequiredArgsConstructor
public class FruitController {

	private final JdbcTemplate jdbcTemplate;

    @PutMapping("/api/v1/fruit")
    public void updateFruit(@RequestBody FruitIdRequest request) {
        String readSql = "select * from fruit where id = ?";
        boolean isFruitNotExist = jdbcTemplate.query(readSql, (rs, rowNum) -> 0, request.id()).isEmpty();
        if (isFruitNotExist) {
            throw new IllegalArgumentException("ํ•ด๋‹นํ•˜๋Š” ๊ณผ์ผ์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.");
        }

        String sql = "update fruit set is_sold = 1 where id = ?";
        jdbcTemplate.update(sql, request.id());
    }
}

HTTP PUT ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๋Š” updateFruit ๋ฉ”์†Œ๋“œ๋กœ,/api/v1/fruit ์—”๋“œํฌ์ธํŠธ์— ๋งคํ•‘๋˜์–ด ์žˆ๋‹ค.

ํด๋ผ์ด์–ธํŠธ๋กœ๋ถ€ํ„ฐ ๋ฐ›์€ FruitIdRequest ๊ฐ์ฒด๋ฅผ ํ†ตํ•ด ํŠน์ • ๊ณผ์ผ์˜ ID๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค.
ํ•ด๋‹น ID๋กœ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ 'fruit' ํ…Œ์ด๋ธ”์„ ์กฐํšŒํ•˜๋Š” SQL ์ฟผ๋ฆฌ๋ฅผ ์‹คํ–‰ํ•œ๋‹ค.
๋งŒ์•ฝ ์กฐํšŒ ๊ฒฐ๊ณผ๊ฐ€ ์—†์œผ๋ฉด (= isEmpty()๊ฐ€ true๋ผ๋ฉด), ํ•ด๋‹นํ•˜๋Š” ID์˜ ๊ณผ์ผ์ด ์กด์žฌํ•˜์ง€ ์•Š๋Š”๋‹ค๋Š” ์˜ˆ์™ธ๋ฅผ ๋˜์ง„๋‹ค.

์กฐํšŒ ๊ฒฐ๊ณผ๊ฐ€ ์žˆ๋‹ค๋ฉด, ํ•ด๋‹นํ•˜๋Š” ID์˜ ๊ณผ์ผ์„ 'fruit' ํ…Œ์ด๋ธ”์—์„œ ์ฐพ์•„ is_sold ์นผ๋Ÿผ์„ 1๋กœ ์—…๋ฐ์ดํŠธํ•˜์—ฌ ํ•ด๋‹น ๊ณผ์ผ์„ ํŒ๋งค๋œ ์ƒํƒœ๋กœ ๋ณ€๊ฒฝํ•œ๋‹ค.

โ–ถ๏ธ ์‹คํ–‰ ๊ฒฐ๊ณผ


โ›ณ๏ธ ๋ฌธ์ œ3

๐Ÿ’ป ์ตœ์ข… ์ฝ”๋“œ

public record FruitAmountResponse(long salesAmount, long notSalesAmount) {
}

@RestController
@RequiredArgsConstructor
public class FruitController {

    private final JdbcTemplate jdbcTemplate;
    
    @GetMapping("/api/v1/fruit/stat")
    public FruitAmountResponse getAmount(@RequestParam String name) {
        String readSql = "select * from fruit where name = ?";
        boolean isFruitNotExist = jdbcTemplate.query(readSql, (rs, rowNum) -> 0, name).isEmpty();
        if (isFruitNotExist) {
            throw new IllegalArgumentException("ํ•ด๋‹นํ•˜๋Š” ๊ณผ์ผ์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.");
        }

        String sql1 = "select sum(price) as salesAmount from fruit where is_sold = 1";
        String sql2 = "select sum(price) as notSalesAmount from fruit where is_sold = 0";

        Long salesAmount = jdbcTemplate.queryForObject(sql1, (rs, rowNum) -> rs.getLong("salesAmount"));
        Long notSalesAmount = jdbcTemplate.queryForObject(sql2, (rs, rowNum) -> rs.getLong("notSalesAmount"));

        return new FruitAmountResponse(salesAmount, notSalesAmount);
    }
}

HTTP GET ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๋Š” getAmount ๋ฉ”์†Œ๋“œ๋กœ, /api/v1/fruit/stat ์—”๋“œํฌ์ธํŠธ์— ๋งคํ•‘๋˜์–ด ์žˆ๋‹ค.

ํด๋ผ์ด์–ธํŠธ๋กœ๋ถ€ํ„ฐ ๋ฐ›์€ ๊ณผ์ผ์˜ ์ด๋ฆ„(@RequestParam String name)์„ ๊ธฐ๋ฐ˜์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ 'fruit' ํ…Œ์ด๋ธ”์„ ์กฐํšŒํ•œ๋‹ค.
๋ฌธ์ œ 2์ฒ˜๋Ÿผ ๊ณผ์ผ์ด ์กด์žฌํ•˜์ง€ ์•Š์œผ๋ฉด ์˜ˆ์™ธ๋ฅผ ๋˜์ ธ ๊ฒ€์ฆํ–ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ๊ณผ์ผ์ด ์กด์žฌํ•œ๋‹ค๋ฉด, is_sold๊ฐ€ 1(ํŒ๋งค๋œ ์ƒํƒœ)์ธ ๊ณผ์ผ์˜ ๊ฐ€๊ฒฉ ํ•ฉ๊ณ„๋ฅผ ์กฐํšŒํ•˜๋Š” ์ฟผ๋ฆฌ(sql1)์™€ is_sold๊ฐ€ 0(๋ฏธํŒ๋งค ์ƒํƒœ)์ธ ๊ณผ์ผ์˜ ๊ฐ€๊ฒฉ ํ•ฉ๊ณ„๋ฅผ ์กฐํšŒํ•˜๋Š” ์ฟผ๋ฆฌ(sql2)๋ฅผ ์‹คํ–‰ํ•˜์—ฌ ํŒ”๋ฆฐ ๊ธˆ์•ก๊ณผ ํŒ”๋ฆฌ์ง€ ์•Š์€ ๊ธˆ์•ก์„ ์กฐํšŒํ•˜๊ณ , ์ด๋ฅผ ์‘๋‹ต์œผ๋กœ ๋ฐ˜ํ™˜ํ–ˆ๋‹ค.

โ–ถ๏ธ ์‹คํ–‰ ๊ฒฐ๊ณผ

๐Ÿ“š ์ฐธ๊ณ ์ž๋ฃŒ
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/jdbc/core/JdbcTemplate.html

profile
๐Ÿพ

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