Home Exception
Post
Cancel

Exception


최근 로그를 잘 남기는 것에 관심이 많아졌는데요, 이를 위해 자바의 예외(Exception)를 한 번 더 정리하고 싶어 글을 작성하게 되었습니다.





1. 자바의 예외


자바에서는 예외 처리를 위해 ExceptionError 두 가지 클래스를 제공합니다. 이들은 모두 Throwable의 하위 클래스로, 실행 중 발생할 수 있는 오류나 예외 상황을 나타냅니다. Exception은 주로 개발자가 처리할 수 있는 예외 를, Error는 시스템적 문제로 인해 복구가 어려운 심각한 오류 를 의미하는데, 이에 대해 살펴보겠습니다.

image





1-1. Exception

Exception은 Throwable의 하위 클래스로 애플리케이션에서 처리 가능한 예외 상황 을 나타냅니다. Exception에는 RuntimeException을 상속하지 않은 Checked Exception, RuntimeException을 상속하는 Unchecked Exception이 존재합니다.

The class Exception and its subclasses are a form of Throwable that indicates conditions that a reasonable application might want to catch. The class Exception and any subclasses that are not also subclasses of RuntimeException are checked exceptions.



Checked Exception은 컴파일 시점 에 체크되며, 반드시 개발자가 처리해야 하는 예외입니다. 예를 들어, 파일이 존재하지 않는 경우나 네트워크 연결 오류 등이 이에 해당합니다. 이는 try/catch 블록, throws 키워드를 이용한 예외 위임 등으로 처리할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Main {
    public static void main(String[] args) {
        try {
            final String content = readFile("hello-world.txt");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static String readFile(final String filename) throws IOException {
        final BufferedReader br = new BufferedReader(new FileReader(filename));
        final StringBuilder sb = new StringBuilder();
        String line;
        
        while ((line = br.readLine()) != null) {
            sb.append(line);
            sb.append(System.lineSeparator());
        }
        br.close();
        return sb.toString();
    }
}





Unchecked Exception은 주로 프로그래밍 오류 때문에 발생하며, 컴파일러가 예외 처리를 강제하지는 않습니다. 이는 RuntimeException 클래스를 상속하는 예외들로 대표적으로 NullPointerException이나 ArrayIndexOutOfBoundsException과 같은 예외들이 있습니다.

1
2
3
4
5
6
7
8
9
10
public class Main {
    public static void main(String[] args) {
        String str = null;
        try {
            int length = str.length();
        } catch (NullPointerException e) {
            e.printStackTrace();
        }
    }
}





1-2. Error

시스템 레벨에서 발생하는 심각한 문제를 나타냅니다. 이러한 문제들은 프로그램이 정상적으로 복구되는 것을 방해하며, 개발자가 직접 해결하기 어렵습니다. 그렇기 때문에 에러에 대한 대응보다는 그것들의 예방이 중요합니다. 특히, StackOverflowError와 OutOfMemoryError와 같은 에러가 대표적입니다.

An Error is a subclass of Throwable that indicates serious problems that a reasonable application should not try to catch. Most such errors are abnormal conditions. The ThreadDeath error, though a “normal” condition, is also a subclass of Error because most applications should not try to catch it.



Error와 그 하위 클래스들은 unchecked 예외로 간주됩니다. 따라서 개발자들이 이를 명시적으로 처리할 필요가 없습니다.

That is, Error and its subclasses are regarded as unchecked exceptions for the purposes of compile-time checking of exceptions.







2. @Transactional과 예외 처리


스프링의 @Transactional은 프록시 기반으로 동작하며, 트랜잭션의 롤백 여부는 예외가 발생했는지, 그리고 그 예외가 프록시 바깥으로 전파되었는지에 따라 결정됩니다. 일반적으로 @Transactional이 붙은 메서드 안에서 RuntimeException이나 Error가 발생하고 그 예외가 밖으로 던져지면, 프록시는 이를 감지해 트랜잭션을 rollback-only 상태로 마킹하고, 트랜잭션은 롤백됩니다.


반대로 예외를 try-catch로 잡아버리면 프록시는 예외가 발생한 사실을 인식하지 못하고, 트랜잭션은 그대로 커밋됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Slf4j
@Service
@RequiredArgsConstructor
public class OrderService {

    private final OrderRepository orderRepository;
    private final ExternalPaymentClient paymentClient;

    @Transactional
    public void processOrder(Long userId, OrderRequest request) {
        log.info("주문 처리 시작 - userId: {}, productId: {}, amount: {}", userId, request.getProductId(), request.getAmount());

        final Order order = new Order(userId, request.getProductId(), request.getAmount());
        orderRepository.save(order);
        log.debug("주문 저장 완료 - orderId: {}", order.getId());

        try {
            paymentClient.pay(request.getPaymentToken(), request.getAmount());
            log.info("결제 성공 - userId: {}, amount: {}", userId, request.getAmount());
        } catch (HttpClientErrorException | HttpServerErrorException e) {
            log.error("결제 실패 - 트랜잭션 커밋 예정 (예외 삼킴)", e);
            // 주의: 예외를 다시 던지지 않으면 트랜잭션은 커밋됨
        }

        ......
        
    }
}







2. 정리


Checked Exception은 RuntimeException과 Error를 제외한 Exception 하위 클래스를 의미하며, Unchecked Exception은 RuntimeException을 상속받는 Exception 하위 클래스를 의미합니다.



This post is licensed under CC BY 4.0 by the author.