no image
[Tip] Github README에 내가 작성한 블로그 최신 글 가져오기
나의 Github README에 블로그 최근 글 작성하기 1. 자신을 소개하는 README.md에 블로그 글 목록이 들어갈 자리에 다음 코드 넣기 2. README.md가 위치하는 Repository에 .github 폴더를 만들고, 그 안에다가 또 workflows 라는 폴더 만들기 3. blog-post-workflow.yml 라는 파일에 아래 코드 붙여넣기 name: Latest blog post workflow on: schedule: # Run workflow automatically - cron: '0 * * * *' # Runs every hour, on the hour workflow_dispatch: # Run workflow manually (without waiting for the c..
2024.03.10
no image
[Spring] 스프링 부트의 예외 처리 방식
스프링 부트의 예외 처리 방식 웹 서비스 애플리케이션에서는 외부에서 들어오는 요청에 담긴 데이터를 처리하는 경우가 많다. 그 과정에서 예외가 발생하면 예외를 복구해서 정상으로 처리하기보다는 요청을 보낸 클라이언트에 어떤 문제가 발생했는지 전달하는 경우가 많다. 예외가 발생했을 때 클라이언트에 오류 메시지를 전달하려면 각 레이어에서 발생한 예외를 엔드포인트 레벨인 컨트롤러로 전달해야 한다. 이렇게 전달받은 예외를 스프링 부트에서 처리하는 방식으로 크게 두 가지가 있다. (Rest)ControllerAdvice와 @ExceptionHandler를 통해 모든 컨트롤러의 예외를 처리 ExceptionHandler를 통해 특정 컨트롤러의 예외를 처리 @RestControllerAdvice @RestControll..
2024.03.08
no image
[Java] Java에서 예외 처리
예외와 에러 프로그래밍에서 예외(Exception)란 입력 값의 처리가 불가능하거나 참조된 값이 잘못된 경우 등 애플리케이션이 정상적으로 동작하지 못하는 상황을 의미합니다. 예외는 개발자가 직접 처리할 수 있으므로 미리 코드 설계를 통해 처리할 수 있습니다. 에러(Error)란 예외와 비슷한 의미지만 엄연히 다른 용어로, 에러는 주로 자바의 가상머신(JVM)에서 발생시킵니다. 따라서 예외와 달리 애플리케이션에서 처리할 수 있는 것이 없습니다. 대표적인 예로 메모리 부족(OutOfMemory), 스택 오버플로(StackOverFlow)가 있습니다. 이러한 에러는 발생 시점에 처리하는 것이 아니라 미리 애플리케이션의 코드를 살펴보면서 문제가 발생하지 않도록 예방해서 원천적으로 차단해야 합니다. 예외 클래스 ..
2024.03.08
no image
[Java] Compile Time과 Runtime의 차이
자바에서는 컴파일 타임과 런타임은 각각 프로그램의 실행의 두 단계를 나타냅니다. 컴파일 타임(Compile Time) 컴파일 타임은 사람이 작성한 소스 코드가 컴퓨터가 이해할 수 있는 기계어로 번역되는 단계입니다. 자바의 컴파일러는 이 단계에서 문법적 에러를 확인합니다. (e.g., missing semicolons, typos, incorrect syntax) 만약 어떤 문제도 발생하지 않으면, 자바 컴파일러(e.g., javac)는 bytecode를 생성합니다. bytecode는 기계어는 아니지만, JVM(Java Virtual Machine)에 의해 해석될 수 있습니다. 또한 아래와 같은 일들이 수행됩니다. 데이터 유형 호환성 확인 클래스 및 방법이 올바르게 선언되었는지 확인 런타임(Runtime)..
2024.03.08
[이분 탐색] 프로그래머스 Level 3 입국심사 Java 풀이
https://school.programmers.co.kr/learn/courses/30/lessons/43238 프로그래머스 코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요. programmers.co.kr 문제 풀이 times를 오름차순 정렬한다. 가장 큰 시간(times의 마지막 인덱스)에다가 n을 곱해서 가장 오래 걸리는 경우의 시간을 구한다. 즉, 모든 사람이 가장 오래 걸리는 시간의 입국심사대를 가는 상황이다. 시작 시간(start)과 가장 오래 걸리는 시간(end)을 반으로 나눈 값을 mid라는 변수에 저장한다. times 배열을 순회하며 mid 변수를 times 배열의 요소로 나누어서 c..
2024.03.07
no image
[DataStructure] 우선순위 큐(Priority Queue)와 힙(Heap)의 차이
Queue Java의 Queue 인터페이스는 FIFO(First In First Out) 방식의 순차적 자료 구조를 구현 즉, 먼저 추가된 요소가 먼저 제거됨. Queue 인터페이스는 LinkedList, PriorityQueue, ArrayBlockingQueue 등 다양한 클래스에서 구현 Queue 인터페이스는 큐에 요소를 추가, 제거, 검사하는 여러 메서드를 제공하며, 다음은 가장 일반적으로 사용되는 메서드들이다. add(element): 요소를 큐의 맨 뒤에 추가, 큐가 가득 차 있으면 예외를 발생 offer(element): 요소를 큐의 맨 뒤에 추가, 큐가 가득 차 있으면 false를 반환 remove(): 큐의 앞에서 요소를 제거하고 반환, 큐가 비어 있으면 예외를 발생 poll(): 큐의 ..
2024.03.07
no image
[DataStructure] Stack과 Queue
ADT(Abstract Data Type) vs DS(Data structure) 'ADT'는 추상 자료형을 의미하며 개념적으로 어떤 동작이 있는지만 정의하고, 구현에 대해서는 다루지 않는다. 'DS'는 자료구조로, 'ADT'에서 정의된 동작을 실제로 구현한 것 Stack과 Queue는 'ADT'를 실제로 구현한 자료 구조이다. 스택(Stack)과 큐(Queue) 스택은 'Last-In-First-Out' 형태로 데이터를 저장하는 구조 데이터를 삽입하는 'push', 데이터를 뽑아내고 삭제하는 'pop', 가장 마지막에 들어온 요소를 추출하는 'peek'과 같은 동작이 있다. Queue는 'First-In-First-Out' 형태로 데이터를 저장하는 구조이며, 데이터를 삽입하는 'enqueue', 추출하..
2024.03.07
no image
[Spring] 유효성 검사와 Hibernate Validator
유효성 검사(validation) 애플리케이션의 비즈니스 로직이 올바르게 동작하는지 검증하는 작업을 유효성 검사라고 한다. 유효성 검사의 예로는 여러 계층에서 들어오는 데이터에 대해 의도한 형식대로 값이 들어오는지 체크하는 과정이 있다. 특히 자바에서 가장 신경 써야 하는 것 중 하나로 NullPointException이 있다. Bean Validation 계층별로 진행하는 유효성 검사는 검증 로직이 각 클래스별로 분산돼 있어 관리하기가 어렵다. 그리고 검증 로직에 의외로 중복이 많아 여러 곳에 유사한 기능의 코드가 존재할 수 있다. 검증해야 할 값이 많다면 검증하는 코드가 길어진다. 이러한 문제로 코드가 복잡해지고 가독성이 떨어지는 문제가 있다. 이 같은 문제를 해결하기 위해 자바 진영에서는 2009년..
2024.03.07

나의 Github README에 블로그 최근 글 작성하기

적용샷

 

1. 자신을 소개하는 README.md에 블로그 글 목록이 들어갈 자리에 다음 코드 넣기

<!-- BLOG-POST-LIST:START -->
<!-- BLOG-POST-LIST:END -->

 

2. README.md가 위치하는 Repository에 .github 폴더를 만들고, 그 안에다가 또 workflows 라는 폴더 만들기

 

3. blog-post-workflow.yml 라는 파일에 아래 코드 붙여넣기

name: Latest blog post workflow
on:
  schedule: # Run workflow automatically
    - cron: '0 * * * *' # Runs every hour, on the hour
  workflow_dispatch: # Run workflow manually (without waiting for the cron to be called), through the GitHub Actions Workflow page directly
permissions:
  contents: write # To write the generated contents to the readme

jobs:
  update-readme-with-blog:
    name: Update this repo's README with latest blog posts
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      - name: Pull in dev.to posts
        uses: gautamkrishnar/blog-post-workflow@v1
        with:
          feed_list: "https://dev.to/feed/gautamkrishnar,https://www.gautamkrishnar.com/feed/"

 

4. 맨 아래  feed_list: 에 본인의 블로그 rss 주소 넣기. 티스토리의 경우, 본인의 블로그 맨 끝에 /rss를 붙이면 됩니다.
예를 들어, https://bezzang2.tistory.com/rss

 

단, 티스토리에 블로그 설정>관리>블로그 에 Rss 설정을 적절히 해주셔야 합니다.

 

5. Actions 탭에,  Latest blog post workflow에 들어가서 Re-run all jobs(또는 Run workflow) 클릭해서 새로고침 하면 끝

처음에만 새로고침해도 되고, 어차피 cron에 의해서 매 시간마다 새로고침이 됩니다.

 

자세한 내용은 공식 홈페이지 URL에서 확인하실 수 있습니다.

https://github.com/marketplace/actions/blog-post-workflow

 

Blog Post Workflow - GitHub Marketplace

Allows you to show your latest blog posts on your github profile or project readme

github.com

 

스프링 부트의 예외 처리 방식

  • 웹 서비스 애플리케이션에서는 외부에서 들어오는 요청에 담긴 데이터를 처리하는 경우가 많다.
  • 그 과정에서 예외가 발생하면 예외를 복구해서 정상으로 처리하기보다는 요청을 보낸 클라이언트에 어떤 문제가 발생했는지 전달하는 경우가 많다.

예외가 발생했을 때 클라이언트에 오류 메시지를 전달하려면 각 레이어에서 발생한 예외를 엔드포인트 레벨인 컨트롤러로 전달해야 한다. 이렇게 전달받은 예외를 스프링 부트에서 처리하는 방식으로 크게 두 가지가 있다.

  • (Rest)ControllerAdvice@ExceptionHandler를 통해 모든 컨트롤러의 예외를 처리
  • ExceptionHandler를 통해 특정 컨트롤러의 예외를 처리

@RestControllerAdvice

@RestController
@RequestMapping("/exception")
public class ExceptionController {

    private final Logger LOGGER = LoggerFactory.getLogger(ExceptionController.class);

    @GetMapping
    public void getRuntimeException() {
        throw new RuntimeException("getRuntimeException 메소드 호출");
    }

    @ExceptionHandler(value = RuntimeException.class)
    public ResponseEntity<Map<String, String>> handleException(RuntimeException e,
        HttpServletRequest request) {
        HttpHeaders responseHeaders = new HttpHeaders();
        responseHeaders.setContentType(MediaType.APPLICATION_JSON);
        HttpStatus httpStatus = HttpStatus.BAD_REQUEST;

        LOGGER.error("클래스 내 handleException 호출, {}, {}", request.getRequestURI(),
            e.getMessage());

        Map<String, String> map = new HashMap<>();
        map.put("error type", httpStatus.getReasonPhrase());
        map.put("code", "400");
        map.put("message", e.getMessage());

        return new ResponseEntity<>(map, responseHeaders, httpStatus);
    }

@ControllerAdvice@RestControllerAdvice@Controller@RestController에서 발생하는 예외를 한 곳에서 관리하고 처리할 수 있게 하는 기능을 수행한다.

@ControllerAdvice@RestControllerAdvice의 차이는 @RestControllerAdvice는 결괏값을 JSON 형태로 반환한다.

 

다음과 같이 별도 설정을 통해 예외를 관제하는 범위를 지정할 수도 있다.
@RestControllerAdvice(basePackages = "com.springboot.valid_exception")
범위를 설정하지 않으면 전역 범위에서 예외를 처리하게 된다.

 

@ExceptionHandler@Controller@RestController가 적용된 Bean에서 발생하는 예외를 찾아 처리하는 메서드를 정의할 때 사용한다.

 

어떤 예외 클래스를 처리할지는 value속성으로 등록한다. value는 배열 형식으로도 전달받을 수 있어 여러 예외 클래스를 등록할 수도 있다.

 

위 예제에서는 RuntimeException이 발생하면 처리하도록 코드가 작성되어 있다.
그리고 Map객체에 응답할 메시지를 구성하고 ResponseEntityHttpHeader, HttpStatus, Body 값을 담아 전달한다.

 

위 핸들러 메서드는 다음과 같은 응답을 출력합니다.

{
 "code": "400",
  "error type": "Bad Request",
  "message": "getRuntimeException 메소드 호출"
}

예외 타입 레벨에 따른 예외 처리 우선순위

만약 컨트롤러 또는 @ControllerAdvice 클래스 내에 동일하게 핸들러 메서드가 선언된 상태에서는 위 그림 처럼 더 구체적인 구현이 있는 예외 클래스가 우선순위를 갖게 된다.

다른 경우로는 다음과 같은 상황이 있다.

@ControllerAdvice의 글로벌 예외 처리와 @Controller 내의 컨트롤러 예외 처리에 동일한 타입의 예외 처리를 하게 되면 범위가 좁은 컨트롤러의 핸들러 메서드가 우선순위를 갖게 된다.

참고

예외와 에러

프로그래밍에서 예외(Exception)란 입력 값의 처리가 불가능하거나 참조된 값이 잘못된 경우 등 애플리케이션이 정상적으로 동작하지 못하는 상황을 의미합니다. 예외는 개발자가 직접 처리할 수 있으므로 미리 코드 설계를 통해 처리할 수 있습니다.

 

에러(Error)란 예외와 비슷한 의미지만 엄연히 다른 용어로, 에러는 주로 자바의 가상머신(JVM)에서 발생시킵니다.
따라서 예외와 달리 애플리케이션에서 처리할 수 있는 것이 없습니다.
대표적인 예로 메모리 부족(OutOfMemory), 스택 오버플로(StackOverFlow)가 있습니다. 이러한 에러는 발생 시점에 처리하는 것이 아니라 미리 애플리케이션의 코드를 살펴보면서 문제가 발생하지 않도록 예방해서 원천적으로 차단해야 합니다.

예외 클래스

예외 클래스의 상속 구조, 이미지 출처:&nbsp;https://www.tcpschool.com/lectures/img_java_exception_class_hierarchy.png

자바에서 모든 예외 클래스는 Throwable 클래스를 상속받습니다. Exception 클래스는 크게 Checked Exception, UncheckedException이 있습니다. 위에서 파란 점선 내부에 있는 Exception이 Checked Exception, 주황색 점선 내부에 있는 Exception이 UncheckedException 입니다.

Checked Exception과 Unchecked Exception

Checked Exception은 컴파일 단계에서 확인 가능한 예외 상황입니다. 이러한 예외는 IDE에서 캐치해서 반드시 예외 처리를 할 수 있게 표시해줍니다.
반면 Unchecked Exception은 런타임 단계에서 확인되는 예외 상황을 나타냅니다. 즉, 문법상 문제는 없지만 프로그램이 동작하는 도중 예기치 않은 상황이 생겨 발생하는 예외를 의미합니다.
RuntimeException을 상속받는 Exception 클래스는 Unchecked Exception이고 그렇지 않은 Exception 클래스는 Checked Exception입니다.

예외 처리 방법

예외가 발생했을 때 이를 처리하는 방법은 크게 세 가지가 있습니다.

  • 예외 복구
  • 예외 처리 회피
  • 예외 전환

예외 복구

예외 복구 방법은 예외 상황을 파악해서 문제를 해결하는 방법입니다. 대표적인 방법이 try/catch구문입니다. try 블록에는 예외가 발생할 수 있는 코드를 작성합니다. 그리고 catch블록을 거치면서 예외 유형과 매칭되는 블록을 찾아 예외 처리 동작을 수행합니다.

public class UserInputExample {

    public static void main(String[] args) {
        String userInput = "123abc"; // Simulate user input that might not be a number

        try {
            int age = Integer.parseInt(userInput); // Attempt conversion
            System.out.println("Your age is: " + age);
        } catch (NumberFormatException e) {
            System.err.println("Error: Invalid age format. Please enter a number.");
        }
    }
}

예외 처리 회피

이 방법은 예외가 발생한 시점에서 바로 처리하는 것이 아니라 예외가 발생한 메서드를 호출한 곳에서 에러 처리를 할 수 있게 전가하는 방식입니다. 이때 throw키워드를 사용해 어떤 예외가 발생했는지 호출부에 내용을 전달할 수 있습니다.

public class ValidateAge {

    public static void checkAge(int age) throws InvalidAgeException {
        if (age < 0) {
            throw new InvalidAgeException("Age cannot be negative!");
        }
        System.out.println("Your age is valid.");
    }

    public static void main(String[] args) {
        try {
            checkAge(-5); // This will throw an InvalidAgeException
        } catch (InvalidAgeException e) {
            System.err.println(e.getMessage());
        }
    }
}

class InvalidAgeException extends Exception {
    public InvalidAgeException(String message) {
        super(message); // Call superclass constructor to set the message
    }
}

예외 전환

이 방법은 앞의 두 방식을 적절하게 섞은 방식입니다. 예외가 발생했을 때 어떤 예외가 발생했느냐에 따라 호출부로 예외 내용을 전달하면서 좀 더 적합한 예외 타입으로 전달할 필요가 있습니다. 또는 애플리케이션에서 예외 처리를 좀 더 단순하게 하기 위해 래핑(wrapping)해야 하는 경우도 있습니다. 이런 경우에는 try/catch 방식을 사용하면서 catch 블록에서 throw키워드를 사용해 다른 예외 타입으로 전달하면 됩니다.

참고

  • 스프링 부트 핵심 가이드 "스프링 부트를 활용한 애플리케이션 개발 실무" , 장정우, 2022

이미지 출처: https://www.console.webcodein.com/wp-content/uploads/2019/11/maxresdefault.jpg

자바에서는 컴파일 타임과 런타임은 각각 프로그램의 실행의 두 단계를 나타냅니다.

컴파일 타임(Compile Time)

컴파일 타임은 사람이 작성한 소스 코드가 컴퓨터가 이해할 수 있는 기계어로 번역되는 단계입니다.
자바의 컴파일러는 이 단계에서 문법적 에러를 확인합니다. (e.g., missing semicolons, typos, incorrect syntax)
만약 어떤 문제도 발생하지 않으면, 자바 컴파일러(e.g., javac)는 bytecode를 생성합니다.

bytecode는 기계어는 아니지만, JVM(Java Virtual Machine)에 의해 해석될 수 있습니다.

 

또한 아래와 같은 일들이 수행됩니다.

  • 데이터 유형 호환성 확인
  • 클래스 및 방법이 올바르게 선언되었는지 확인

런타임(Runtime)

런타임은 컴파일된 코드(bytecode)가 JVM(Java Virtual Machine)에 의해 실행되는 단계입니다.

다음과 같은 순서로 실행됩니다.

  • JVM은 메모리로 bytecode를 로드합니다.
  • JVM은 bytecode를 해석하고 명령어를 순차적으로 실행합니다.
  • 실행하는 동안에 runtime errors를 발생시킬 수 있습니다. (e.g., OutOfInDex, division by zero)

이 단계에서는 다음과 같은 일이 발생합니다.

  • 객체를 메모리에 할당합니다.
  • 연산을 수행합니다.
  • 운영체제와 외부 리소스와 상호작용합니다.

요약

Feature Compile Time Runtime
Definition Source code is translated into machine code. Machine code is interpreted and executed.
What happens Syntax and code structure are checked; bytecode is generated. Bytecode instructions are interpreted and executed.
Tools involved Java compiler (e.g., javac) Java Virtual Machine (JVM)
Actions performed Checking for syntax errors, type compatibility, etc. Executing code, allocating memory, interacting with resources
Error handling Catches syntax errors Catches runtime errors (e.g., division by zero)

Key points to remember

  • 컴파일 타임에서는 프로그램이 실행되기 전 한 번만 발생합니다. Compile time happens once before the program is executed.
  • 런타임은 프로그램이 실행될 때마다 계속해서 발생합니다. (코드 한줄한줄의 명령어를 실행할 때라고 생각하면 됩니다.) Runtime happens every time the program is run.
  • 런타임 오류는 컴파일 시간 오류보다 진단 및 수정이 더 어려운 경우가 많습니다.

참고

  • Google Gemini

https://school.programmers.co.kr/learn/courses/30/lessons/43238

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

문제 풀이

  1. times를 오름차순 정렬한다.
  2. 가장 큰 시간(times의 마지막 인덱스)에다가 n을 곱해서 가장 오래 걸리는 경우의 시간을 구한다.
    즉, 모든 사람이 가장 오래 걸리는 시간의 입국심사대를 가는 상황이다.
  3. 시작 시간(start)과 가장 오래 걸리는 시간(end)을 반으로 나눈 값을 mid라는 변수에 저장한다.
  4. times 배열을 순회하며 mid 변수를 times 배열의 요소로 나누어서 completed라는 변수에 더한다.
    즉, mid 시간 동안에 입국 심사를 받을 수 있는 사람 수를 세는 과정이다.
  5. completed라는 변수의 크기가 n보다 작으면, 그 mid 시간동안 n명의 입국심사를 모두 할 수 없음을 의미한다.
  6. 따라서 mid의 값을 크게 하기 위해 start를 mid + 1로 설정한다
  7. 반대로 completed의 크기가 n보다 크면 그 mid 시간동안 n명의 입국심사를 모두 할 수 있다는 것인데,
    입국심사를 다 하고 시간이 남았을수도 있으므로 end를 mid - 1로 설정한다.
  8. start 값이 end 값보다 커지면 멈춘다.

코드

import java.util.*;


class Solution {
    public long solution(int n, int[] times) {
        long answer = 0;
        Arrays.sort(times);
        
        long start = 0;
        long end = (long)n * times[times.length - 1];
        while(start <= end) {
            long mid = (start + end) / 2;
            long completed = 0;
            for(int i = 0; i < times.length; i++) {
                completed += mid / times[i];
            }
            if (completed < n) {
                start = mid + 1;
            } else {
                end = mid - 1;
                answer = end;
            }   
        }
        
        return answer + 1;
    }
    
}

Queue

  • Java의 Queue 인터페이스는 FIFO(First In First Out) 방식의 순차적 자료 구조를 구현
  • 즉, 먼저 추가된 요소가 먼저 제거됨.
  • Queue 인터페이스는 LinkedList, PriorityQueue, ArrayBlockingQueue 등 다양한 클래스에서 구현

Queue 인터페이스는 큐에 요소를 추가, 제거, 검사하는 여러 메서드를 제공하며, 다음은 가장 일반적으로 사용되는 메서드들이다.

  • add(element): 요소를 큐의 맨 뒤에 추가, 큐가 가득 차 있으면 예외를 발생
  • offer(element): 요소를 큐의 맨 뒤에 추가, 큐가 가득 차 있으면 false를 반환
  • remove(): 큐의 앞에서 요소를 제거하고 반환, 큐가 비어 있으면 예외를 발생
  • poll(): 큐의 앞에서 요소를 제거하고 반환합니다, 큐가 비어 있으면 null을 반환
  • element(): 큐의 앞에 있는 요소를 제거하지 않고 반환, 큐가 비어 있으면 예외를 발생
  • peek(): 큐의 앞에 있는 요소를 제거하지 않고 반환, 큐가 비어 있으면 null을 반환

우선순위 큐(Priority Queue)

  • 우선순위 큐는 우선순위에 따라 아이템을 처리 하는 Queue 자료구조로, 주요 동작은 insert, delete, peek이 있다.
  • 우선순위 큐는 트리 구조 기반의 이진 트리로 구현된다.
  • 이진 트리(Binary tree)는 부모 노드가 최대 2개의 자녀 노드를 가지는 트리를 의미한다.
  • 트리 구조는 부모-자녀 계층 구조를 가진다.

힙(Heap)

이미지 출처:&nbsp;https://prepbytes-misc-images.s3.ap-south-1.amazonaws.com/assets/1674109793492-Difference%20Between%20Max%20Heap%20and%20Min%20Heap2.png

  • 힙(Heap)은 데이터 구조이며 우선순위 큐의 구현체다.
  • Max Heap은 부모 노드의 키 값이 자식 노드들의 키 값보다 크거나 같은 트리를 나타내고, Min Heap은 반대를 의미한다.
  • 따라서 힙은 데이터 구조이고, 우선순위 큐는 ADT(Abstract Data Type)으로 추상 데이터 유형이며, 이 둘은 다른 개념이다.

우선순위 큐와 힙의 사용 사례

프로세서 스케줄링

  • 운영체제에서 멀티태스킹을 통해 여러 프로세스가 CPU를 번갈아가며 실행한다.
  • 프로세스들은 Ready Queue에서 기다리며 이때 각 프로세스의 우선순위가 결정된다.
  • 프로세스가 CPU에서 실행되는 조건은 해당 프로세스의 작업이 끝날 때나 타임 슬라이스로 인해 우선순위가 높은 다음 프로세스가 실행될 때이다.

heap sort

  • 정렬을 할때도 사용할 수 있다.
  • 시간 복잡도는 Best-case & Average-case: O(n log n), Worst-case: O(n log n)이다.
  • 공간 복잡도는 O(1)이다. (in-place sorting, modifies the original array)

힙 메모리와 데이터 구조의 힙은 다르다.

  • 힙 메모리는 동적으로 할당된 객체들을 저장하는 RAM 메모리 영역을 의미한다.

참고

ADT(Abstract Data Type) vs DS(Data structure)

  • 'ADT'는 추상 자료형을 의미하며 개념적으로 어떤 동작이 있는지만 정의하고, 구현에 대해서는 다루지 않는다.
  • 'DS'는 자료구조로, 'ADT'에서 정의된 동작을 실제로 구현한 것
  • Stack과 Queue는 'ADT'를 실제로 구현한 자료 구조이다.

스택(Stack)과 큐(Queue)

이미지 출처: https://gohighbrow.com/wp-content/uploads/2018/07/Computer-science-fundamentals_6.1.png

  • 스택은 'Last-In-First-Out' 형태로 데이터를 저장하는 구조
  • 데이터를 삽입하는 'push', 데이터를 뽑아내고 삭제하는 'pop', 가장 마지막에 들어온 요소를 추출하는 'peek'과 같은 동작이 있다.
  • Queue는 'First-In-First-Out' 형태로 데이터를 저장하는 구조이며, 데이터를 삽입하는 'enqueue', 추출하는 'dequeue' 동작이 있다.
  • Queue라는 항상 'FIFO'를 의미하진 않는다. OS(Operating System)에서 멀티태스킹을 할 때 사용되는 '레디 큐(Ready Queue)'와 '오퍼스 트윈 폴스(Operating System Twins Pulse)' 등 다양한 대기열을 의미하는 것으로, 역할에 따라 상이한 용어가 사용된다.

스택(Stack)의 사용 사례

Stack Memory

Stack Memory라는 메모리 영역으로, 함수가 호출될 때 Stack Frame이 쌓이고, 종료될 때 Stack Frame이 사라지는 구조로 동작한다.
자바 개발 시 스택 오버플로(Stack Overflow) 같은 에러가 발생할 수 있는데, 이는 스택 메모리 공간이 다 차서 발생하는 에러다. 보통 재귀함수에서 탈출을 못할때 발생하는 경우가 많다.

큐(Queue)의 사용 사례

Producer/Consumer Architecture

프로듀서가 생산한 아이템이 차례대로 큐에 쌓이고 도착 순서에 따라 처리된다.

Heap memory

힙 메모리(Heap memory)는 주로 객체를 저장하는 메모리 영역인데, OutOfMemoryError는 힙 메모리가 다 채워진 상태에서 메모리를 더 필요로 할 때 발생한다.
따라서 Queue 사이즈를 고정하여 메모리 사용을 제한하는 것이 중요하다.
OutOfMemoryError가 발생할 경우 대응할 수 있는 4가지 방식이 있다.

  1. Exception 던지기
  2. 특별한 값(Null or false)을 반환
  3. 성공할 때까지 영원히 스레드 블락(block)
  4. 제한된 시간만 블락되고 그래도 안되면 포기

이러한 4가지 방식을 구현한 Queue 클래스는 Java에서 LinkedBlockingQueue가 있다.

참고

유효성 검사(validation)

  • 애플리케이션의 비즈니스 로직이 올바르게 동작하는지 검증하는 작업을 유효성 검사라고 한다.
  • 유효성 검사의 예로는 여러 계층에서 들어오는 데이터에 대해 의도한 형식대로 값이 들어오는지 체크하는 과정이 있다.
  • 특히 자바에서 가장 신경 써야 하는 것 중 하나로 NullPointException이 있다.

Bean Validation

  • 계층별로 진행하는 유효성 검사는 검증 로직이 각 클래스별로 분산돼 있어 관리하기가 어렵다.
  • 그리고 검증 로직에 의외로 중복이 많아 여러 곳에 유사한 기능의 코드가 존재할 수 있다.
  • 검증해야 할 값이 많다면 검증하는 코드가 길어진다.
  • 이러한 문제로 코드가 복잡해지고 가독성이 떨어지는 문제가 있다.
  • 이 같은 문제를 해결하기 위해 자바 진영에서는 2009년부터 Bean Validation 이라는 데이터 유효성 검사 프레임워크를 제공한다.
  • Bean Validation은 어노테이션을 통해 다양한 데이터를 검증하는 기능을 제공한다.
  • Bean Validation을 사용한다는 것은 유효성 검사를 위한 로직을 DTO같은 도메인 모델과 묶어서 각 계층에서 사용하면서 검증 자체를 도메인 모델에 얹는 방식으로 수행한다는 의미이다.
  • 또한 Bean Validation은 어노테이션을 사용한 검증 방식이기 때문에 코드의 간결함도 유지할 수 있다.

Hibernate Validator

  • Hibernate Validator는 Bean Validation 명세의 구현체이다.
  • 스프링 부트에서는 Hibernate Validator를 유효성 검사 표준으로 채택해서 사용하고 있다.
  • Hibernate Validator는 JSR-303명세의 구현체로서 도메인 모델에서 어노테이션을 통한 필드값 검증을 가능하게 도와준다.
  • JSR이란 Java Specification Request의 약자로, Java 애플리케이션에서 데이터를 검증하는 기준을 명시하고 있다.

스프링 부트의 유효성 검사

  • 유효성 검사는 각 계층으로 데이터가 넘어오는 시점에 해당 데이터에 대한 검사를 실시한다.
  • 보통 스프링 부트 프로젝트에서는 계층 간 데이터 전송에 DTO(Data Transfer Object)를 활용하고 있기 때문에 다음과 같은 구조로 유효성 검사를 수행하는 것이 일반적이다.

스프링 부트에서 유효성 검사 기능 사용하기

  • 원래 스프링 부트의 유효성 검사 기능은 spring-boot-starter-web에 포함돼 있었으나, 스프링 부트 2.3 버전 이후로 별도의 라이브러리로 제공하고 있다.
  • 따라서 다음과 같은 의존성을 추가해야 한다.

Maven

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

Gradle

dependencies {
  implementation 'org.hibernate.validator:hibernate-validator:'
}

유효성 검사를 위한 대표적인 어노테이션은 다음과 같다.

문자열 검증

  • @Null: null 값만 허용합니다.
  • @NotNull: null을 허용하지 않습니다. "", " "는 허용합니다.
  • @NotEmpty: null, ""을 허용하지 않습니다. " "는 허용합니다.
  • @NotBlank: null, "", " "을 허용하지 않습니다.

최댓값/최솟값 검증

  • BigDecimal, BigInteger, int, long 등 타입을 지원합니다.
  • @DecimalMax(value = "$numberString") : $numberString보다 작은 값을 허용합니다.
  • @DecimalMin(value = "$numberString") : $numberString보다 큰 값을 허용합니다.
  • @Min(value = $number) : $number 이상의 값을 허용합니다.
  • @Max(value = $number) : $number 이하의 값을 허용합니다.

값의 범위 검증

  • BigDecimal, BigInteger, int, long 등 타입을 지원합니다.
  • @Positive : 양수를 허용합니다.
  • @PositiveOrZero : 0 포함 양수를 허용합니다.
  • @Negative : 음수를 허용합니다.
  • @NegativeOrZero : 0 포함 음수를 허용합니다.

시간에 대한 검증

  • Date, LocalDate, LocalDateTime 등의 타입을 지원합니다.
  • @Future : 현재보다 미래 날짜를 허용합니다.
  • @FutureOrPresent : 현재 포함 미래 날짜를 허용합니다.
  • @Past : 현재보다 과거 날짜를 허용합니다.
  • @PastOrPresent : 현재 포함 과거 날짜를 허용합니다.

이메일 검증

  • @Email : 이메일 형식을 검사합니다. ""을 허용합니다.

자릿수 범위 검증

  • BigDecimal, BigInteger, int, long 등 타입을 지원합니다.
  • @Digits(integer = $number1, fraction = $number2) : $number1의 정수 자릿수와 $number2의 소수 자릿수를 허용합니다.

Boolean 검증

  • @AssertTrue : true인지 체크합니다. null 값은 체크하지 않습니다.
  • @AssertFalse : false인지 체크합니다. null 값은 체크하지 않습니다.

문자열 길이 검증

  • @Size(min = $number1, max = $number2) : $number1 이상 $number2 이하 범위 허용

정규식 검증

  • @Pattern(regexp = "$expression") : 정규식을 검사합니다. 정규식은 자바의 java.util.regex.Pattern 패키지 컨벤션을 따릅니다.

사용 예시

@RestController
@RequestMapping("/validation")
public class ValidationController {

    private final Logger LOGGER = LoggerFactory.getLogger(ValidationController.class);

    @PostMapping("/valid")
    public ResponseEntity<String> checkValidationByValid(
            @Valid @RequestBody ValidRequestDto validRequestDto) {
        LOGGER.info(validRequestDto.toString());
        return ResponseEntity.status(HttpStatus.OK).body(validRequestDto.toString());
    }

위 코드에서 checkValidationByValid 메서드는 인자로 ValidRequestDto를 받고 RequestBody로 받고 있다.

앞에 @Valid 어노테이션을 지정해야 해당 클래스에 대해 유효성 검사를 수행한다.
아래는 ValidRequestDto 클래스이다.

@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
@Builder
public class ValidRequestDto {

    @NotBlank
    private String name;

    @Email
    private String email;

    @Pattern(regexp = "01(?:0|1|[6-9])[.-]?(\\d{3}|\\d{4})[.-]?(\\d{4})$")
    private String phoneNumber;

    @Min(value = 20)
    @Max(value = 40)
    private int age;

    @Size(min = 0, max = 40)
    private String description;

    @Positive
    private int count;

    @AssertTrue
    private boolean booleanCheck;

}

위 DTO에서 만약 Validation이 어긋나는 필드가 포함된 요청이 들어온다면 400 Bad Request를 클라이언트에게 응답한다.

@Validated 활용하기

  • 컨트롤러 클래스에서 메서드에 인자 앞에 명시한 @Valid 어노테이션은 자바에서 지원하는 어노테이션이며, 스프링에서 지원하는 어노테이션은 @Validated라는 어노테이션이다.
  • 이 어노테이션은 @Valid의 기능을 포함하고 있기 때문에 @Validated로 변경할 수 있다.
  • @Valid와 다른 점은 @Validated는 유효성 검사를 그룹으로 묶어 대상을 특정할 수 있는 기능이 있다.

예를 들어, 두 인터페이스를 생성한다.

ValidationGroup1

public interface ValidationGroup1 {

}

ValidationGroup2

public interface ValidationGroup2 {

}

그리고 위에서 사용한 Dto 객체에서 agecount에 다음과 같이 수정한다.

    @Min(value = 20, groups = ValidationGroup1.class)
    @Max(value = 40, groups = ValidationGroup1.class)
    private int age;

    @Positive(groups = ValidationGroup2.class)
    private int count;

각 어노테이션에 groups 속성을 사용하여 그룹을 설정한다.

실제로 어느 구룹에 유효성 검사를 실시할지 결정하는 것은 @Validated 어노테이션에서 한다.

@RestController
@RequestMapping("/validation")
public class ValidationController {

    private final Logger LOGGER = LoggerFactory.getLogger(ValidationController.class);

    @PostMapping("/valid1")
    public ResponseEntity<String> checkValidation1(
            @Validated(ValidationGroup1.class) @RequestBody ValidRequestDto validRequestDto) {
        LOGGER.info(validRequestDto.toString());
        return ResponseEntity.status(HttpStatus.OK).body(validRequestDto.toString());
    }

    @PostMapping("/valid2")
    public ResponseEntity<String> checkValidation2(
            @Validated(ValidationGroup2.class) @RequestBody ValidRequestDto validRequestDto) {
        LOGGER.info(validRequestDto.toString());
        return ResponseEntity.status(HttpStatus.OK).body(validRequestDto.toString());
    }

위 컨트롤러 클래스의 checkValidation1 메서드는 ValidRequestDto에서 ValidationGroup1.class의 그룹으로 설정한 필드에 대해서만 유효성 검사를 실시한다.


마찬가지로 checkValidation2 메서드는 ValidRequestDto에서 ValidationGroup2.class의 그룹으로 설정한 필드에 대해서만 유효성 검사를 실시한다.


만약 그룹으로 지정하지 않고 @Validated으로만 사용한다면 그룹으로 지정하지 않은 필드에 대해서만 유효성 검사를 실시한다.

Custom Validation

  • 자바 또는 스프링의 유효성 검사 어노테이션에서 제공하지 않는 기능을 사용해야 할 경우, ConstrainValidator와 커스텀 어노테이션을 조합해서 별도의 유효성 검사 어노테이션을 생성할 수 있다.

전화번호 형식이 일치하는지 확인하는 간단한 유효성 검사 어노테이션을 생성

  • 먼저 ConstraintValidator 인터페이스를 구현하는 클래스를 생성한다.
  • 이 인터페이스는 inValid 메서드를 정의하고 있으며, 이 메서드를 구현해야한다.
    이 메서드에서 false가 리턴되면 MethodArgumentNotValidException 예외가 발생한다.
public class TelephoneValidator implements ConstraintValidator<Telephone, String> {

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if(value==null){
            return false;
        }
        return value.matches("01(?:0|1|[6-9])[.-]?(\\d{3}|\\d{4})[.-]?(\\d{4})$");
    }
}
  • 커스텀한 로직을 어노테이션으로 사용하기 위해 다음과 같이 어노테이션 인터페이스를 구현한다.
  • 참고로 어노테이션 인터페이스는 클래스의 메타데이터를 제공하기 위해 사용된다. 이는 컴파일러나 런타임 환경에서 추가적인 정보를 제공할 수 있다.
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = TelephoneValidator.class)
public @interface Telephone {
    String message() default "전화번호 형식이 일치하지 않습니다.";
    Class[] groups() default {};
    Class[] payload() default {};
}

@Target 어노테이션은 이 어노테이션이 어디서 선언할 수 있는지 정의하는 데 사용된다.
사용할 수 있는 ElementType은 대표적으로 다음과 같다.

  • ElementType.FIELD: 각 필드에 적용 가능합니다.
  • ElementType.METHOD: 메서드에서 사용하는 모든 파라미터를 검사하기 위해 사용됩니다.
  • ElementType.TYPE: 클래스 레벨에서 유효성을 검사하기 위해 사용되며, 같은 객체에서 다른 필드들의 관계를 확인하는 데 유용합니다.
  • ElementType.PARAMETER: 메서드의 특정 파라미터를 검사하기 위해 사용됩니다.

@Retention 어노테이션은 이 어노테이션이 실제로 적용되고 유지되는 범위를 의미한다.
@Retention의 적용 범위는 RetentionPolicy를 통해 지정하며, 지정 가능한 항목은 다음과 같다.

  • RetentionPolicy.RUNTIME: 컴파일 이후에도 JVM에 의해 계속 참조합니다. 리플렉션이나 로깅에 많이 사용되는 정책입니다.
  • RetentionPolicy.CLASS: 컴파일러가 클래스를 참조할 때까지 유지합니다.
  • RetentionPolicy.SOURCE: 컴파일 전까지만 유지됩니다. 컴파일 이후에는 사라집니다.

@Constraint 어노테이션은 앞에서ConstrainValidator를 구현한TelephoneValidator를 매핑한다.

또한message()메서드를 통해 유효성 검사를 위배했을 때 보여질 메세지를 구현한다.

groups()는 유효성 검사를 사용하는 그룹으로 설정하며,payload()는 사용자가 추가 정보를 위해 전달하는 값이다.

 

이와 관련된 내용은ConstraintHelper를 참조

 

이제 커스텀한 @Telephone 어노테이션을 사용할 수 있다.

@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
@Builder
public class ValidRequestDto {

    @NotBlank
    private String name;

    @Email
    private String email;

    @Telephone // custom validation annotation
    private String phoneNumber;

    @Min(value = 20)
    @Max(value = 40)
    private int age;

    @Size(min = 0, max = 40)
    private String description;

    @Positive
    private int count;

    @AssertTrue
    private boolean booleanCheck;

}

참고