๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
BackEnd๐ŸŒฑ/Spring

ํŠธ๋žœ์žญ์…˜(Transaction)์˜ ์˜ˆ์™ธ(Exception)์— ๋”ฐ๋ฅธ ๋กค๋ฐฑ ์ฒ˜๋ฆฌ

by dkswnkk 2023. 2. 5.

์„œ๋ก 

์ด์ „์— Java์˜ Checked Exception๊ณผ UnChecked Exception์— ๋Œ€ํ•ด ์ •๋ฆฌํ•œ ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์‹œ ์š”์•ฝํ•˜๋ฉด RuntimeException์„ ์ƒ์†ํ•˜์ง€ ์•Š๋Š” ํด๋ž˜์Šค๋Š” Checked Exception, ์ƒ์†ํ•œ ํด๋ž˜์Šค๋Š” Unchecked Exception์ด๋ฉฐ, Checked Exception์€ try-catch์„ ํ†ตํ•ด ์˜ˆ์™ธ๋ฅผ ๊ผญ ์ฒ˜๋ฆฌํ•ด ์ฃผ์–ด์•ผ ์ปดํŒŒ์ผ์—์„œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์ด๋ฒˆ ํฌ์ŠคํŒ…์—์„œ๋Š” ์ด๋Ÿฌํ•œ ๊ฐ๊ฐ์˜ ์˜ˆ์™ธ๋“ค์— ๋Œ€ํ•˜์—ฌ Transaction์—์„œ ๋กค๋ฐฑ์ด ์–ด๋–ป๊ฒŒ ๋ฐ˜์˜๋˜๋Š”์ง€ ํ•œ๋ฒˆ ์ •๋ฆฌํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

 

๊ณตํ†ต ์ฝ”๋“œ

@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private String password;

    public User(String name, String password) {
        this.name = name;
        this.password = password;
    }
}


@Getter
@NoArgsConstructor
public class UserRequestDto {
    private String email;
    private String password;
}


@RestController
@RequiredArgsConstructor
public class UserController {

    private final UserService userService;

    @PostMapping("/user")
    public void signUp(@RequestBody UserRequestDto userRequestDto) {
        userService.signUp(userRequestDto);
    }
}


public interface UserRepository extends JpaRepository<User, Long> {
}

๊ณตํ†ต์ ์ธ Entity, Dto, Controller ๋กœ์ง์€ ์œ„์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

 

Checked Exception์—์„œ์˜ Transaction ์ฒ˜๋ฆฌ

@Slf4j
@Service
@RequiredArgsConstructor
public class UserService {

    private final UserRepository userRepository;

    @Transactional
    public void signUp(UserRequestDto userRequestDto) {
        try {
            User user = new User(userRequestDto.getEmail(), userRequestDto.getPassword());
            userRepository.save(user);
            throw new IOException("Throw Force Exception");
        } catch (Exception e) {
            log.error("message: {}", e.getMessage());
        }
    }

}

๋จผ์ € Chekced Exception์˜ ํŠธ๋žœ์žญ์…˜ ์˜ˆ์ œ์ž…๋‹ˆ๋‹ค. ์œ„์˜ ๊ฒฝ์šฐ๋Š” ๋กค๋ฐฑ์ด ์ ์šฉ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

๋˜ํ•œ ๋‹น์—ฐํ•˜๊ฒŒ๋„ throw๊ฐ€ ๋กœ์ง ์œ„๋กœ ๊ฐ€๊ฒŒ ๋˜๋ฉด ์ปดํŒŒ์ผ ๋‹จ๊ณ„์—์„œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

@Slf4j
@Service
@RequiredArgsConstructor
public class UserService {

    private final UserRepository userRepository;

    @Transactional
    public void signUp(UserRequestDto userRequestDto) {
        try {
            User user = new User(userRequestDto.getEmail(), userRequestDto.getPassword());
            userRepository.save(user);
            throw new RuntimeException("Throw Force Exception");
        } catch (Exception e) {
            log.error("message: {}", e.getMessage());
        }
    }

}

๊ทธ๋Ÿผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์˜ˆ์™ธ์ฒ˜๋ฆฌ๋ฅผ ํ•˜์ง€ ์•Š์•„๋„ ๋˜๋Š” Unchecked Exception ํƒ€์ž…์ธ RuntimeException์„ try-catch๋ฅผ ์ ์šฉํ•ด ์˜ˆ์™ธ๋ฅผ ์ฒ˜๋ฆฌ๋ฅผ ํ•˜๋ฉด์„œ ๋˜์กŒ์„ ๋•Œ๋Š” ์–ด๋–จ๊นŒ์š”?

๊ฒฐ๊ณผ๋Š” ๋กค๋ฐฑ์ด ์ ์šฉ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. RuntimeException์„ ์˜ˆ์™ธ๋กœ ๋˜์กŒ์ง€๋งŒ ์‹ค์งˆ์ ์œผ๋กœ try-catch๋ฅผ ํ†ตํ•ด ์˜ˆ์™ธ๋ฅผ ์ฒ˜๋ฆฌ๋ฅผ ํ™•์ธํ–ˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ๋” ์ž์„ธํžˆ ์•Œ์•„๋ณด๋ฉด @Transaction์„ ํ†ตํ•œ ์„ ์–ธ์  ํŠธ๋žœ์žญ์…˜ ๊ด€๋ฆฌ ๋ฐฉ์‹์€ ๊ธฐ๋ณธ์ ์œผ๋กœ ํ”„๋ก์‹œ ๋ฐฉ์‹์˜ AOP๊ฐ€ ์ ์šฉ๋˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

TransactionStatus status = transactionManager.getTransaction(..);

try {
    target.logic();
    transactionManager.commit(status);
} catch (Exception e) {
    transactionManager.rollback(status);
    throw new IllegalStateException(e); 
}

์œ„ ์ฝ”๋“œ๋Š” ํŠธ๋žœ์žญ์…˜ ํ”„๋ก์‹œ์˜ ๊ฐ„๋‹จํ•œ ์˜ˆ์ œ ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค. ํŠธ๋žœ์žญ์…˜ ํ”„๋ก์‹œ๊ฐ€ ํŠธ๋žœ์žญ์…˜ ์ฒ˜๋ฆฌ ๋กœ์ง์„ ์ˆ˜ํ–‰ํ•  ๋•Œ ์œ„ ํด๋ž˜์Šค๋ฅผ ์ƒˆ๋กœ ๋งŒ๋“ค์–ด ๋กœ์ง์„ ์ˆ˜ํ–‰ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ try-catch๋ฅผ ํ†ตํ•ด catch์—์„œ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๋ฅผ ํ™•์ธํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ํ”„๋ก์‹œ๊นŒ์ง€ ์˜ˆ์™ธ๊ฐ€ ์ „๋‹ฌ๋˜์ง€ ์•Š์•„ ํ”„๋ก์‹œ ๊ฐ์ฒด๊ฐ€ RuntimeException์„ ์ธ์ง€ํ•˜์ง€ ๋ชปํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค.

 

UnChecked Exception์—์„œ์˜ Transaction ์ฒ˜๋ฆฌ

@Slf4j
@Service
@RequiredArgsConstructor
public class UserService {

    private final UserRepository userRepository;

    @Transactional
    public void signUp(UserRequestDto userRequestDto) {
        User user = new User(userRequestDto.getEmail(), userRequestDto.getPassword());
        userRepository.save(user);
        throw new RuntimeException("Throw Force Exception");
    }

}

์—๋Ÿฌ ๋ฉ”์„ธ์ง€

๋‹ค์Œ๊ณผ ๊ฐ™์ด RuntimeException์„ try-catch๋ฅผ ํ†ตํ•ด ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ํ•˜์ง€ ์•Š์•˜์„ ์‹œ์—๋Š” ๋กค๋ฐฑ์ด ์ ์šฉ๋˜๋ฉฐ ๋ณ€๊ฒฝ์‚ฌํ•ญ์ด ์ €์žฅ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

 

์™œ Checked Exception์€ ๋กค๋ฐฑ๋˜์ง€ ์•Š์„๊นŒ?

๊ทธ๋ ‡๋‹ค๋ฉด ์™œ Checked Exception์€ ๋กค๋ฐฑ์ด ๋˜์ง€ ์•Š์„๊นŒ์š”?

์ด์œ ๋Š” Spring์€ @Transaction ์–ด๋…ธํ…Œ์ด์…˜์— ๋Œ€ํ•ด Runtime Exception๊ณผ Error์— ๋Œ€ํ•ด์„œ๋งŒ default๋กœ ๋กค๋ฐฑ์ด ์ ์šฉ๋˜๋„๋ก ์„ค์ •๋˜์–ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ์ด์— ๋Œ€ํ•ด ์„ค๋ช…ํ•˜๋ฉด ์•„๋ž˜์˜ ์ฝ”๋“œ๋Š” ์„œ๋กœ ๋™์ผํ•ฉ๋‹ˆ๋‹ค.

@Transactional
@Transactional(rollbackFor = {RuntimeException.class, Error.class})

์ฆ‰, ๊ธฐ๋ณธ์ ์œผ๋กœ UnChecked Exception(RuntimeException)์—์„œ๋งŒ ๋กค๋ฐฑ์ด ๋˜๋ฉฐ, Checked Exception(IOException, SQLException ๋“ฑ)์—์„œ๋Š” ๋กค๋ฐฑ์ด ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜๋“  AOP์— ์„ค์ •์„ ํ•˜๋“  spring์€ Unchecked Exception(RuntimeException)๊ณผ Error ๋ฐœ์ƒ ์‹œ ๋กค๋ฐฑ ์ ์šฉ์„ ๊ธฐ๋ณธ์œผ๋กœ ํ•˜๋„๋ก ์„ค์ •๋ฉ๋‹ˆ๋‹ค.

[๋กค๋ฐฑ ์ ์šฉ]
@Transactional(rollbackFor = {RuntimeException.class, Exception.class})
[๋กค๋ฐฑ ์ œ์™ธ]
@Transactional(noRollbackFor = {RuntimeException.class})

์ปค์Šคํ…€ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์œ„์™€ ๊ฐ™์ด ์ง์ ‘ ๋กค๋ฐฑ์„ ์ ์šฉํ•˜๊ฑฐ๋‚˜ ์ œ์™ธํ•  ์˜ˆ์™ธ๋ฅผ ์ง์ ‘ ๋ช…์‹œํ•ด์ฃผ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ ์ค‘์š”ํ•œ ์ ์€ ์œ„์—์„œ ์ž ๊น ์„ค๋ช…ํ–ˆ์ง€๋งŒ try-catch๋ฅผ ํ†ตํ•ด ์˜ˆ์™ธ์ฒ˜๋ฆฌ๋ฅผ ํ–ˆ์„ ๊ฒฝ์šฐ์—๋Š” rollbackFor์„ ์„ค์ •ํ•˜๋”๋ผ๋„ ์ ์šฉ์ด ๋˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— catch๋‚ด์—์„œ ๊ฐ•์ œ๋กœ throw๋ฅผ ํ•ด์ฃผ์–ด์•ผ ๋กค๋ฐฑ์ด ์ ์šฉ๋ฉ๋‹ˆ๋‹ค.

 

์ •๋ฆฌ

๊ฐœ๋ฐœ์ž๊ฐ€ ์ปดํŒŒ์ผ ์‹œ ์˜ˆ์ธก/์ฒ˜๋ฆฌ ๊ฐ€๋Šฅํ•œ ์˜ˆ์™ธ์˜ ์ฒ˜๋ฆฌ๋ฅผ ๊ฐœ๋ฐœ์ž์—๊ฒŒ ๊ฐ•์ œํ•˜๊ธฐ ์œ„ํ•ด Checked Exception์€ ์Šคํ”„๋ง์—์„œ ์ œ๊ณตํ•˜๋Š” ํŠธ๋žœ์žญ์…˜์—์„œ์˜ ๋กค๋ฐฑ์˜ ๊ธฐ๋ณธ ์ •์ฑ…์— ํฌํ•จ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์ด๋ฅผ ์ปค์Šคํ…€ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” Transaction ์–ด๋…ธํ…Œ์ด์…˜์—์„œ rollbackFor ํ˜น์€ noRollbackFor์„ ํ†ตํ•ด ์ง์ ‘ ์„ธํŒ…ํ•ด ์ฃผ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

  • try-catch๋ฅผ ํ†ตํ•ด ์˜ˆ์™ธ๋ฅผ ํ™•์ธํ•˜๋ฉด ๋กค๋ฐฑ ์ ์šฉ X
  • Unchecked Exception์€ default ์„ธํŒ…์œผ๋กœ ๋กค๋ฐฑ ์ ์šฉ O
  • Unchekced Exception๋„ try-catch๋ฅผ ํ†ตํ•ด ์˜ˆ์™ธ ํ™•์ธ ์‹œ ๋กค๋ฐฑ ์ ์šฉ X
  • @Transaction์—์„œ rollbackFor, noRollbackFor์„ ํ†ตํ•ด ์ปค์Šคํ…€ ๊ฐ€๋Šฅ

 

 

์ˆ˜์ •

์ตœ๊ทผ์— ๋” ์ž์„ธํ•˜๊ฒŒ ๋‹ค๋ฃฌ 'Spring ํŠธ๋žœ์žญ์…˜์€ ์–ธ์ œ ์–ด๋–ป๊ฒŒ ๋กค๋ฐฑ ๋ ๊นŒ?' ๊ธ€์„ ์ƒˆ๋กœ ๋ฐœํ–‰ํ–ˆ์œผ๋‹ˆ ๋” ์ž์„ธํ•˜๊ฒŒ ์•Œ๊ณ  ์‹ถ์œผ์‹  ๋ถ„๋“ค์€ ํ•ด๋‹น ์•„ํ‹ฐํด์„ ์œ„์ฃผ๋กœ ๋ด์ฃผ์‹œ๋ฉด ๊ฐ์‚ฌํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

๋Œ“๊ธ€