๐Ÿ“ŒSpring ์ž…๋ฌธ ๊ณผ์ œ ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ…

euphonyยท2025๋…„ 2์›” 4์ผ
0

๋‚ด์ผ๋ฐฐ์›€์บ ํ”„

๋ชฉ๋ก ๋ณด๊ธฐ
50/66

๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์ผ์น˜ํ•ด๋„ ์‚ญ์ œ๊ฐ€ ์•ˆ๋˜๋Š” ๋ฌธ์ œ

์‚ญ์ œ ๊ธฐ๋Šฅ ํ…Œ์ŠคํŠธ ๋„์ค‘, ์˜ฌ๋ฐ”๋ฅธ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•ด๋„ 404 ์—๋Ÿฌ๊ฐ€ ๋œจ๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ๋‹ค. 404 ์—๋Ÿฌ๋ฅผ ๋˜์ง€๋„๋ก ์„ค์ •ํ•œ ๋ถ€๋ถ„์€ DB์— ์žˆ๋Š” ๋น„๋ฐ€๋ฒˆํ˜ธ์™€ ์ž…๋ ฅํ•œ ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ํ‹€๋ฆฌ๋Š” ๊ฒฝ์šฐ ๋ฐœ์ƒํ•œ๋‹ค.

์ด๊ฒƒ์ €๊ฒƒ ์‹œ๋„ํ•ด๋ณด๋‹ค Repository Layer์—์„œ ์ •ํ™•ํ•œ scheduleId์™€ password๊ฐ€ ์ถœ๋ ฅ๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•œ ํ›„, ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๊ฒ€์ฆํ•˜๋Š” Service Layer์—์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ถœ๋ ฅํ•ด๋ดค๋‹ค.

System.out.println("db password = " + current.getPassword());
System.out.println("input password = " + password);
System.out.println("equals = " + current.getPassword().equals(password));

๊ทธ ๊ฒฐ๊ณผ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์•˜๋‹ค. JSON ํ˜•์‹์˜ ๋ฌธ์ž์—ด์ด ๊ทธ๋Œ€๋กœ ๋“ค์–ด์˜จ ๊ฒƒ์ด๋‹ค.

Controller์—์„œ ๋ณด๋‹ˆ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ String ๊ทธ๋Œ€๋กœ ๋„ฃ์€ ๊ฒƒ์ด ๋ฌธ์ œ์˜€๋‹ค.

@DeleteMapping("/{scheduleId}")
public ResponseEntity<Void> deleteSchedule(
        @PathVariable Long scheduleId,
        @RequestBody(required = false) String password
) {
    scheduleService.deleteSchedule(scheduleId, password);![](https://velog.velcdn.com/images/3uomlkh/post/36205978-55e3-4764-b9d5-e29b265f8e2f/image.png)

    return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
  • inputPassword โ†’ {"password": "1111"} (JSON ๊ฐ์ฒด)
  • dbPassword โ†’ "1111" (String)

๋”ฐ๋ผ์„œ String ํƒ€์ž…์ด ์•„๋‹Œ ScheduleRequestDto ํƒ€์ž…์œผ๋กœ ๋ณ€๊ฒฝํ•œ ๋’ค dto์—์„œ getPassword()๋ฅผ ํ•ด์„œ ๋น„๋ฐ€๋ฒˆํ˜ธ๋งŒ ๋„˜๊ฒจ์ฃผ์—ˆ๋‹ค.

@DeleteMapping("/{scheduleId}")
public ResponseEntity<Void> deleteSchedule(
        @PathVariable Long scheduleId,
        @RequestBody ScheduleRequestDto dto
) {
    scheduleService.deleteSchedule(scheduleId, dto.getPassword());
    return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}

๋ณ€๊ฒฝ ํ›„ ๋‹ค์‹œ ๋กœ๊ทธ๋ฅผ ์ถœ๋ ฅํ•ด๋ณด๋‹ˆ ์›ํ•˜๋˜ ๊ฒฐ๊ณผ๊ฐ€ ๋‚˜์™”๋‹ค.

๋˜ํ•œ Postamn์—์„œ์˜ ํ…Œ์ŠคํŠธ๋„ ์„ฑ๊ณตํ•˜์˜€๋‹ค.

์ด ๊ณผ์ •์„ ํ†ตํ•ด @RequestBody String ๋Œ€์‹  DTO ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค๋Š” ๊ฒƒ์„ ์•Œ๊ฒŒ ๋˜์—ˆ๋‹ค.

์—ฌ๊ธฐ์„œ ๋˜ ์ˆ˜์ •๋œ ๋ถ€๋ถ„์ด ์žˆ๋Š”๋ฐ, required = false๋กœ ์ ์€ ๋ถ€๋ถ„์„ ์‚ญ์ œํ•œ ๋ถ€๋ถ„์ด๋‹ค. @RequestBody(required = true) ๊ฐ€ ์„ค์ •๋˜์–ด ์žˆ์œผ๋ฉด, ํด๋ผ์ด์–ธํŠธ๋Š” JSON์„ ๋ฐ˜๋“œ์‹œ ๋ณด๋‚ด์•ผ ํ•˜์ง€๋งŒ, ํŠน์ • ํ•„๋“œ๋Š” null์ด ๋  ์ˆ˜๋„ ์žˆ๋‹ค. ์ด๋•Œ, @RequestBody(required = false)๋กœ ๋ณ€๊ฒฝํ•˜๋ฉด ์š”์ฒญ ๋ณธ๋ฌธ์ด ์—†์–ด๋„ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š๋Š”๋‹ค. ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์„ ํƒ ์‚ฌํ•ญ์ด๋ผ๋ฉด false๋กœ ํ•˜๋Š” ๊ฒƒ๋„ ๊ณ ๋ คํ•  ์ˆ˜ ์žˆ์ง€๋งŒ ๋ณดํ†ต ๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” ํ•„์ˆ˜๊ฐ’์ด๊ธฐ ๋•Œ๋ฌธ์— ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค.

queryForObject() vs query()

์˜ˆ์™ธ ์ฒ˜๋ฆฌ๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๋„์ค‘, ์›ํ•˜๋Š” ๊ณณ์—์„œ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š๊ณ  ์—‰๋šฑํ•œ ๊ณณ์—์„œ ๋ฐœ์ƒํ•ด์„œ ๊ทธ ์›์ธ์„ ์ฐพ์•„๋ณด์•˜๋‹ค.

์ผ์ •์„ ์ˆ˜์ •ํ•˜๋Š” ๋ถ€๋ถ„ ์ค‘ findScheduleByIdWithPassword()์—์„œ ๋‚ด๊ฐ€ ์„ค์ •ํ•œ ScheduleNotFoundException์ด ์•„๋‹ˆ๋ผ EmptyResultDataAccessException์ด ๋‚ฌ๋‹ค.

@Transactional
@Override
public ScheduleResponseDto updateSchedule(Long scheduleId, ScheduleRequestDto dto) {
    Schedule current = scheduleRepository.findScheduleByIdWithPassword(scheduleId);

    if (current == null) {
        throw new ScheduleNotFoundException("์ˆ˜์ •ํ•  ์ผ์ •์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.");
    }

	// ์ค‘๋žต..

}

ํ•ด๋‹น ๋ฉ”์„œ๋“œ๊ฐ€ ๊ตฌํ˜„๋˜์–ด์žˆ๋Š” Repository๋กœ ๊ฐ€์„œ ์‚ดํŽด๋ณด๋‹ˆ, ๋‹ค์Œ๊ณผ ๊ฐ™์ด queryForObject()๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋Š” ๊ฒƒ์ด ๋ฌธ์ œ์˜€๋‹ค. queryForObject()๋Š” ๊ฒฐ๊ณผ๊ฐ€ ์—†์œผ๋ฉด null์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ ์˜ˆ์™ธ๋ฅผ ๋˜์ง€๊ธฐ ๋•Œ๋ฌธ์—, Service์—์„œ null ์ฒดํฌ๋ฅผ ํ•  ์ˆ˜ ์—†๋˜ ๊ฒƒ์ด์—ˆ๋‹ค!

@Override
public Schedule findScheduleByIdWithPassword(Long scheduleId) {
    return jdbcTemplate.queryForObject(
            "SELECT * FROM schedules WHERE scheduleId = ?",
            scheduleWithPasswordRowMapper(),
            scheduleId
    );
}

๋”ฐ๋ผ์„œ queryForObject() ๋Œ€์‹  query()๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค์ง€ ์•Š๊ณ , ๊ฒฐ๊ณผ๊ฐ€ ์—†์œผ๋ฉด null์„ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ์ˆ˜์ •ํ–ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  Service์—์„œ null ์ฒดํฌ ํ›„ ์‚ฌ์šฉ์ž ์ •์˜ ์˜ˆ์™ธ์ธ ScheduleNotFoundException์„ ๋˜์ง€๋Š” ๊ฒƒ์— ์„ฑ๊ณตํ–ˆ๋‹ค.

@Override
public Schedule findScheduleByIdWithPassword(Long scheduleId) {
    String query = "SELECT * FROM schedules WHERE scheduleId = ? AND is_deleted = FALSE";
    List<Schedule> schedules = jdbcTemplate.query(query, scheduleWithPasswordRowMapper(), scheduleId);
    return schedules.isEmpty() ? null : schedules.get(0);
}

Postman์—์„œ๋„ ๋‚ด๊ฐ€ ์›ํ•˜๋Š” ๋Œ€ ์ƒํƒœ์ฝ”๋“œ์™€ ๋ฉ”์„ธ์ง€๊ฐ€ ์ถœ๋ ฅ๋˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.
Image

ํ”ผ๋“œ๋ฐฑ

โœ”๏ธ ์ž˜ํ•œ ์ 

  • ์ƒ์„ฑ ์‹œ์—๋Š” Created๋ฅผ delete ์‹œ์—๋Š” No content์™€ ๊ฐ™์ด ์ƒํƒœ ์ฝ”๋“œ๋ฅผ ์ ์ ˆํ•˜๊ฒŒ ์ž˜ ์‚ฌ์šฉํ–ˆ๋‹ค.
  • ์ฝ”๋“œ ๊ตฌํ˜„ ๋“ฑ์ด ๊น”๋”ํ•˜๊ฒŒ ์ž˜ ๋˜์–ด ์žˆ๋‹ค.

โœ”๏ธ ์ƒ๊ฐํ•ด๋ณผ ์ 

  • Repository์—์„œ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋Š” ์ฟผ๋ฆฌ๋ฌธ์„ ์ƒ์ˆ˜๋กœ ์ •์˜ํ•˜๋ฉด ์ค‘๋ณต์„ ์ค„์ด๊ณ  ์ฝ”๋“œ ๊ฐ€๋…์„ฑ์„ ๋†’์ผ ์ˆ˜ ์žˆ๋‹ค.
  • ๊ฐ ๋ฉ”์†Œ๋“œ์™€ ํŒŒ๋ผ๋ฏธํ„ฐ์— ๋Œ€ํ•ด ๊ฐ„๋‹จํ•œ ์ฃผ์„์„ ์ถ”๊ฐ€ํ•˜๋ฉด ์ฝ”๋“œ๊ฐ€ ๋” ๋ช…ํ™•ํ•ด์ง„๋‹ค. Java Docs๋ฅผ ๊ฒ€์ƒ‰ํ•ด ์ ์šฉํ•ด๋ณด์ž!
  • findSchedules ๋ฉ”์†Œ๋“œ์—์„œ AND 1=1 ์กฐ๊ฑด์„ ์‚ฌ์šฉํ•˜๋Š” ๋ถ€๋ถ„์ด ๊ผญ ํ•„์š”ํ• ๊นŒ?
  • ์—๋Ÿฌ ๋ฉ”์„ธ์ง€๋ฅผ ์ข€ ๋” ๊ตฌ์ฒด์ ์œผ๋กœ ์ž‘์„ฑํ•˜๋ฉด ์ข‹๋‹ค.
  • DB ์—…๋ฐ์ดํŠธ์™€ ๊ด€๋ จ๋œ ๋ชจ๋“  ์ž‘์—…์„ ํŠธ๋žœ์žญ์…˜ ๋‚ด์—์„œ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค. @Transactional ๋ฐ @Transactional(readOnly=true) ์˜ ์ฐจ์ด์ ์„ ์ดํ•ดํ•˜๊ณ  ์ ์ ˆํ•˜๊ฒŒ ์‚ฌ์šฉํ•ด๋ณด์ž.

โœ”๏ธ ๊ถ๊ธˆํ–ˆ๋˜ ์  & ์–ด๋ ค์› ๋˜ ์ 

  • API ๋ช…์„ธ์„œ๋ฅผ ์„ค๊ณ„ํ•˜๊ณ  ๋งŒ๋“œ๋Š” ๋ถ€๋ถ„์—์„œ ์‹œ๊ฐ„ ์˜ค๋ž˜ ๊ฑธ๋ ธ๋Š”๋ฐ, ์–ด๋–ป๊ฒŒ ํ•˜๋ฉด ๋น ๋ฅด๊ณ  ๊น”๋”ํ•˜๊ฒŒ ์„ค๊ณ„๋ฅผ ํ•  ์ˆ˜ ์žˆ์„๊นŒ?
    • API๋Š” ๋ฆฌ์†Œ์Šค ์ค‘์‹ฌ์œผ๋กœ ์„ค๊ณ„ํ•ด์•ผ ํ•œ๋‹ค. ์Šค์ผ€์ฅด, ์œ ์ €, ๊ฒŒ์‹œ๋ฌผ๊ณผ ๊ฐ™์€ ๋ฆฌ์†Œ์Šค๋“ค์ด ์ •ํ•ด์ง„๋‹ค๋ฉด ๊ฐ ๋ฆฌ์†Œ์Šค์— ๋Œ€ํ•ด CRUD ์ž‘์—…์„ ์–ด๋–ป๊ฒŒ ํ•  ๊ฒƒ์ธ์ง€ ์ •์˜ํ•œ๋‹ค.
    • API ์„ค๊ณ„๋ฅผ ๋ฐ˜๋ณตํ•˜๊ณ , ํ”ผ๋“œ๋ฐฑ์„ ๋ฐ›์•„ ๊ฐœ์„ ํ•˜๋Š” ์—ฐ์Šต์„ ๋งŽ์ด ํ•˜์ž.
  • ํ…Œ์ด๋ธ” ๊ฐ„ ์—ฐ๊ด€๊ด€๊ณ„๋ฅผ ์„ค์ •ํ•˜๊ณ  ์„œ๋กœ ์—ฐ๊ฒฐํ•˜๋Š” ๋ถ€๋ถ„์ด ์–ด๋ ค์› ๋‹ค.
    • ERD๋ฅผ ๊ทธ๋ ค๋ณด๋ฉด์„œ ๊ด€๊ณ„๋ฅผ ์‹œ๊ฐ์ ์œผ๋กœ ์ดํ•ดํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๊ฑฐ๋‚˜ ํ…Œ์ด๋ธ” ๊ฐ„ ๊ด€๊ณ„ ์„ค์ •์„ ๋จผ์ € ๋ชจ๋ธ๋ง ํ•œ ํ›„ ์ด๋ฅผ ์—”ํ‹ฐํ‹ฐ๋กœ ๊ตฌํ˜„ํ•˜๋Š” ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•ด๋ณด์ž.
  • Service Layer์˜ ์ˆ˜์ •, ์‚ญ์ œ ๋“ฑ์˜ ๋ฉ”์„œ๋“œ ๋‚ด๋ถ€์—์„œ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๋ฅผ ํ–ˆ๋Š”๋ฐ, ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๋ถ€๋ถ„์„ ๋”ฐ๋กœ ๋นผ์•ผํ• ์ง€ ๊ณ ๋ฏผ์ด์—ˆ๋‹ค.
    • ์˜ˆ์™ธ๋ฅผ ์„œ๋น„์Šค์—์„œ ๋˜์ง€๊ณ , ์ด๋ฅผ ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ์žก์•„์„œ ์ ์ ˆํ•œ ์‘๋‹ต์œผ๋กœ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋ฐฉ์‹์ด ์ข‹์€ ๊ฒƒ ๊ฐ™๋‹ค. ์ด๋ ‡๊ฒŒ ๋˜๋ฉด ๋‚˜์ค‘์— ๋ฐฐ์šธ ControllerAdvice๋ฅผ ํ†ตํ•ด ์ „์—ญ์œผ๋กœ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๋ฅผ ํ•  ์ˆ˜ ์žˆ๋‹ค.
    • ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง๊ณผ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๋ฅผ ๋ถ„๋ฆฌํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด, ์„œ๋น„์Šค ๋ ˆ์ด์–ด์—์„œ๋Š” ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง๋งŒ ๋‹ด๋‹นํ•˜๊ณ  ์˜ˆ์™ธ์ฒ˜๋ฆฌ๋Š” ๋ณ„๋„์˜ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ํด๋ž˜์Šค๋‚˜ ์ง€๊ธˆ ์ ์šฉ ๋˜์–ด์žˆ๋Š” @ExceptionHandler์—์„œ ๋‹ด๋‹นํ•˜๊ฒŒ ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ๋ถ„๋ฆฌ๋ฅผ ์ง„ํ–‰ํ•  ์ˆ˜๋„ ์žˆ๋‹ค.
  • ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ์‹œ, ๋ชจ๋‘ Service์—์„œ ์ฒ˜๋ฆฌํ–ˆ์ง€๋งŒ Repository์—์„œ null์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋ถ€๋ถ„๋„ null์„ ๊ผญ ๋ฐ˜ํ™˜ํ•˜๊ฒŒ ํ•œ ๋’ค Service์—์„œ ์ฒ˜๋ฆฌ๋ฅผ ํ•ด์•ผํ•˜๋Š” ๊ฒƒ์ธ์ง€ ๊ณ ๋ฏผ๋˜์—ˆ๋‹ค.
    • Repository๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€์˜ ์ƒํ˜ธ์ž‘์šฉ๋งŒ ๋‹ด๋‹นํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ฐ์ดํ„ฐ๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์œผ๋ฉด null์„ ๋ฐ˜ํ™˜ํ•˜๊ฑฐ๋‚˜ ๋นˆ ๋ฆฌ์ŠคํŠธ๋ฅผ ๋ฐ˜ํ™˜ํ•œ ๋’ค, ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ์ž์ฒด๋Š” ์„œ๋น„์Šค ๋ ˆ์ด์–ด์—์„œ ํ•˜๋„๋ก ์„ค๊ณ„ํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.

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

๊ด€๋ จ ์ฑ„์šฉ ์ •๋ณด