Home > Spring > ๐Ÿƒ[Spring] API์—์„œ ์˜ˆ์™ธ ๋˜์ง€๊ธฐ.

๐Ÿƒ[Spring] API์—์„œ ์˜ˆ์™ธ ๋˜์ง€๊ธฐ.
Spring Framework

๐Ÿƒ[Spring] API์—์„œ ์˜ˆ์™ธ ๋˜์ง€๊ธฐ.

Java ๋ฐฑ์—”๋“œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ API ํ˜ธ์ถœ ์ค‘ ์˜ˆ์™ธ๋ฅผ ๋˜์ง€๋ฉด, ์˜ˆ์™ธ๊ฐ€ ์ ์ ˆํ•˜๊ฒŒ ์ฒ˜๋ฆฌ๋˜์ง€ ์•Š๋Š” ํ•œ ํด๋ผ์ด์–ธํŠธ์— ์˜ค๋ฅ˜ ์‘๋‹ต์ด ๋ฐ˜ํ™˜๋ฉ๋‹ˆ๋‹ค.

๊ธฐ๋ณธ์ ์œผ๋กœ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•œ ์‹œ์ ์—์„œ ์‹คํ–‰์„ ์ค‘๋‹จํ•˜๊ณ , ์˜ˆ์™ธ์— ๋Œ€ํ•œ ์ฒ˜๋ฆฌ๋ฅผ ํ•˜์ง€ ์•Š์œผ๋ฉด ๋‚ด๋ถ€ ์„œ๋ฒ„ ์˜ค๋ฅ˜(HTTP 500)๋ฅผ ํด๋ผ์ด์–ธํŠธ์— ๋ฐ˜ํ™˜ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

1๏ธโƒฃ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ํ๋ฆ„.

1. ์˜ˆ์™ธ ๋ฐœ์ƒ.

  • API์˜ ์ปจํŠธ๋กค๋Ÿฌ๋‚˜ ์„œ๋น„์Šค ๋ ˆ์ด์–ด์—์„œ ํŠน์ • ๋กœ์ง์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๋„์ค‘ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ์‹คํ–‰ ํ๋ฆ„์ด ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๋ธ”๋ก์œผ๋กœ ๋„˜์–ด๊ฐ€๊ฑฐ๋‚˜, ์˜ˆ์™ธ๊ฐ€ ํ˜ธ์ถœ๋œ ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด ์ƒ์œ„๋กœ ์ „ํŒŒ๋ฉ๋‹ˆ๋‹ค.

2. ์˜ˆ์™ธ ์ „ํŒŒ.

  • ๋งŒ์•ฝ ํ•ด๋‹น ์˜ˆ์™ธ๋ฅผ try-catch ๋ธ”๋ก ๋‚ด์—์„œ ์ฒ˜๋ฆฌํ•˜์ง€ ์•Š์œผ๋ฉด ์˜ˆ์™ธ๋Š” ํ˜ธ์ถœ๋œ ๋ฉ”์„œ๋“œ๋ฅผ ๋”ฐ๋ผ ์ƒ์œ„๋กœ ์ „ํŒŒ๋ฉ๋‹ˆ๋‹ค.
  • ์ด ๊ณผ์ •์—์„œ ์ตœ์ข…์ ์œผ๋กœ ์ปจํŠธ๋กค๋Ÿฌ ๋ ˆ๋ฒจ์ด๋‚˜ ๊ทธ ์ด์ƒ์—์„œ ์˜ˆ์™ธ๋ฅผ ์žก์ง€ ์•Š์œผ๋ฉด, Spring Framework์™€ ๊ฐ™์€ ๋ฐฑ์—”๋“œ ํ”„๋ ˆ์ž„์›Œํฌ๊ฐ€ ์˜ˆ์™ธ๋ฅผ ๋ฐ›์•„ ์ฒ˜๋ฆฌํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

3. Spring์˜ ๊ธฐ๋ณธ ๋™์ž‘.

  • Spring MVC์—์„œ๋Š” ์˜ˆ์™ธ๊ฐ€ ์ „ํŒŒ๋˜์–ด ์ปจํŠธ๋กค๋Ÿฌ๊นŒ์ง€ ๋„๋‹ฌํ•˜๊ณ ๋„ ์ฒ˜๋ฆฌ๋˜์ง€ ์•Š์œผ๋ฉด, Spring์ด DefaultHandlerExceptionResolver๋ฅผ ํ†ตํ•ด ๊ธฐ๋ณธ์ ์ธ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๋ฅผ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
  • ์ฒ˜๋ฆฌ๋˜์ง€ ์•Š์€ ์˜ˆ์™ธ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ HTTP ์ƒํƒœ ์ฝ”๋“œ 500(๋‚ด๋ถ€ ์„œ๋ฒ„ ์˜ค๋ฅ˜)๋กœ ๋งคํ•‘๋˜์–ด ํด๋ผ์ด์–ธํŠธ์— ๋ฐ˜ํ™˜๋ฉ๋‹ˆ๋‹ค.
  • ํด๋ผ์ด์–ธํŠธ๋Š” ์ด ์ƒํƒœ ์ฝ”๋“œ๋ฅผ ํ†ตํ•ด ์„œ๋ฒ„์—์„œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Œ์„ ์ธ์‹ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

2๏ธโƒฃ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•œ ๋ฐฉ๋ฒ•.

1. @ExceptionHandler ์‚ฌ์šฉ.

  • Spring MVC์—์„œ๋Š” ์ปจํŠธ๋กค๋Ÿฌ ๋ ˆ๋ฒจ์—์„œ ์˜ˆ์™ธ๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด @ExceptionHandler ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ํŠน์ • ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ํ•ด๋‹น ๋ฉ”์„œ๋“œ๊ฐ€ ํ˜ธ์ถœ๋˜์–ด ์˜ˆ์™ธ๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ณ , ์ ์ ˆํ•œ ์‘๋‹ต์„ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
@RestController
public class MyController {
    @GetMapping("/example")
    public String example() {
        if (someConditionFails()) {
            throw new MyCustomException("Something went wrong!");
        }
        return "success";
    }
    
    @ExceptionHandler(MyCustomException.class)
    public ResponseEntity<String> handleMyCustomException(MyCustomException ex) {
        return new ResponseEntity<>(ex.getMessage(), HttpStatus.BAD_REQUEST);
    }
}

์ด ๊ฒฝ์šฐ MyCustomException์ด ๋ฐœ์ƒํ•˜๋ฉด HTTP ์ƒํƒœ ์ฝ”๋“œ 400๊ณผ ํ•จ๊ป˜ ์˜ˆ์™ธ ๋ฉ”์‹œ์ง€๊ฐ€ ๋ฐ˜ํ™˜๋ฉ๋‹ˆ๋‹ค.

2. @ControllerAdvice ์‚ฌ์šฉ.

  • @ControllerAdvice๋Š” ์ „์—ญ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๊ธฐ๋ฅผ ์ •์˜ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.
  • ์ด๋ฅผ ํ†ตํ•ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ „๋ฐ˜์— ๋ฐœ์ƒํ•˜๋Š” ์˜ˆ์™ธ๋ฅผ ์ค‘์•™์—์„œ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
@ControllerAdvice
public class GlobalExceptionHandler {
    
    @ExceptionHandler(MyCustomException.class)
    public ResponseEntity<String> handleMyCustomException(MyCustomException ex) {
        return new ResponseEntity<>(ex.getMessage(), HttpStatus.BAD_REQUEST);
    }
    
    @ExceptionHandler(Exception.class)
    public ResponseEntity<String> handleGeneralException(Exception ex) {
        return new ResponseEntity<>("An unexpected error occurred.", HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

์ด ๊ฒฝ์šฐ ํŠน์ • ์˜ˆ์™ธ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ๋ชจ๋“  ์ผ๋ฐ˜์ ์ธ ์˜ˆ์™ธ๋„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

3. ResponseEntity์™€ ํ•จ๊ป˜ ์˜ˆ์™ธ๋ฅผ ๋ฐ˜ํ™˜.

  • ResponseEntity๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ง์ ‘ ์˜ˆ์™ธ ๋ฐœ์ƒ ์‹œ HTTP ์‘๋‹ต๊ณผ ์ƒํƒœ ์ฝ”๋“œ๋ฅผ ๋ฐ˜ํ™˜ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.
@GetMapping("/example")
public ResponseEntity<String> example() {
    if (someConditionFails()) {
        return new ResponseEntity<>("Custom error message", HttpStatus.BAD_REQUEST);
    }
    return new ResponseEntiry<>("success", HttpStatus.OK);
}

3๏ธโƒฃ ์‹ค์ œ ์ฝ”๋“œ ์‚ฌ๋ก€.

// UserUpdateRequest - DTO
public class UserUpdateRequest {
    private long id;
    private String name;
    
    public long getId() {
        return id;
    }
    
    public String getName() {
        return name;
    }
}

// UserController - Controller
@PutMapping("/user")
public void updateUser(@RequestBody UserUpdateRequest request) {
    String sql = "UPDATE user SET name = ? WHERE id = ?";
    jdbcTemplate.update(sql, request.getName(), request.getId());
}

@DeleteMapping("/user")
public void deleteUser(@RequestParam String name) {
    String sql = "DELETE FROM user WHERE name = ?";
    jdbcTemplate.update(sql, name);
}

1. ๋ฌธ์ œ ์ƒํ™ฉ.

  • ์œ„ ์ฝ”๋“œ์—์„œ์˜ ๋ฌธ์ œ ์ƒํ™ฉ์€ ์—†๋Š” ์œ ์ €๋ฅผ ์—…๋ฐ์ดํŠธ ํ•˜๊ฑฐ๋‚˜ ์‚ญ์ œํ•˜๋ ค ํ•ด๋„ 200 OK๊ฐ€ ๋‚˜์˜จ๋‹ค๋Š” ์ ์ž…๋‹ˆ๋‹ค.

2. ํ•ด๊ฒฐ ๋ฐฉ์•ˆ.

  • API์—์„œ ์˜ˆ์™ธ๋ฅผ ๋˜์ € 500 Internal Server Error์ด ๋‚˜์˜ค๊ฒŒ ํ•˜๋ฉด๋ฉ๋‹ˆ๋‹ค.
@GetMapping("/user/error-test")
public void errorTest() {
    throw new IllegalArgumentException();
}

  • POSTMAN์„ ํ™œ์šฉํ•˜์—ฌ http://localhost:8080/user/error-test๋กœ GET ํ˜ธ์ถœ์„ ๋ณด๋‚ด๋ฉด 500 Internal Server Error์ด ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

4๏ธโƒฃ ํ™œ์šฉ ์˜ˆ์‹œ.

// UserUpdateRequest - DTO
public class UserUpdateRequest {
    private long id;
    private String name;
    
    public long getId() {
        return id;
    }
    
    public String getName() {
        return name;
    }
}

// UserController - Controller
@PutMapping("/user")
public void updateUser(@RequestBody UserUpdateRequest request) {
    String readSql = "SELECT * FROM user WHERE id = ?";
    boolean isUserExist = jdbcTemplate.query(readSql, (rs, rowNum) -> 0 request.getId()).isEmpty();
    
    if (isUserNotExist) {
        throw new IllegalArgumentException();
    }
    
    String sql = "UPDATE user SET name = ? WHERE id = ?";
    jdbcTemplate.update(sql, request.getName(), request.getId());
}

@DeleteMapping("/user")
public void deleteUser(@RequestParam String name) {
    String readSql = "SELECT * FROM user WHERE name = ?";
    boolean isUserExist = jdbcTemplate.query(readSql (rs, rowNum) -> 0, name).isEmpty();
    
    if (isUserNotExist) {
        throw new IllegalArgumentException();
    }
    
    String sql = "DELETE FROM user WHERE name = ?";
    jdbcTemplate.update(sql, name);
}
  • ์œ„์™€ ๊ฐ™์ด API์—์„œ ๋ฐ์ดํ„ฐ ์กด์žฌ ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•ด ์˜ˆ์™ธ๋ฅผ ๋˜์ง€๋ฉด ๋ฉ๋‹ˆ๋‹ค.
String readSql = "SELECT * FROM user WHERE id = ?";
  • id๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์œ ์ €๊ฐ€ ์กด์žฌํ•˜๋Š”์ง€ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด SELECT ์ฟผ๋ฆฌ๋ฅผ ์ž‘์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค.
boolean isUserExist = jdbcTemplate.query(readSql, (rs, rowNum) -> 0 request.getId()).isEmpty();
  • SQL์„ ๋‚ ๋ ค DB์— ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
    • SELECT SQL์˜ ๊ฒฐ๊ณผ๊ฐ€ ์žˆ์œผ๋ฉด 0์œผ๋กœ ๋ณ€ํ™˜๋ฉ๋‹ˆ๋‹ค.
    • ์ตœ์ข…์ ์œผ๋กœ List๋กœ ๋ฐ˜ํ™˜๋ฉ๋‹ˆ๋‹ค.
      • ์ฆ‰, ํ•ด๋‹น id๋ฅผ ๊ฐ€์ง„ ์œ ์ €๊ฐ€ ์žˆ์œผ๋ฉด 0์ด ๋‹ด๊ธด List๊ฐ€ ๋‚˜์˜ค๊ณ , ์—†๋‹ค๋ฉด ๋นˆ List๊ฐ€ ๋‚˜์˜ค๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.
      • jdbcTemplate.query()์˜ ๊ฒฐ๊ณผ์ธ List๊ฐ€ ๋น„์–ด์žˆ๋‹ค๋ฉด, ์œ ์ €๊ฐ€ ์—†๋‹ค๋Š” ๋œป์ž…๋‹ˆ๋‹ค.
if (isUserNotExist) {
    throw new IllegalArgumentException();
}
  • ๋งŒ์•ฝ ์œ ์ €๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด IllegalArgumentException์„ ๋˜์ง‘๋‹ˆ๋‹ค.