Docker와 가상머신(VM)의 차이

 

출처:  https://iancoding.tistory.com/184

 

아래 비교 표에 대한 자세한 설명은 아래에 작성하였습니다.

 

Docker 가상머신(VM)
Docker는 컨테이너화 된 애플리케이션을 실행합니다. 컨테이너는 호스트 OS의 커널을 공유하며 각 컨테이너는 시스템의 나머지 부분으로부터 격리된 환경에서 실행됩니다. VM은 물리적 하드웨어 위에 하이퍼바이저를 사용하여 별도의 전체 운영체제를 실행합니다. 완전히 독립된 가상의 컴퓨터를 의미합니다.
호스트 OS 커널을 공유하기 때문에 추가적인 운영체제를 로드할 필요가 없어 더 적은 리소스를 사용합니다. 각 VM은 전체 운영체제와 그 운영체제를 실행하는 데 필요한 리소스를 포함하므로 일반적으로 더 많은 리소스를 사용합니다.
컨테이너는 빠르게 시작될 수 있습니다. 전체 OS를 부팅해야 하기 때문에 시간이 좀 더 걸립니다.
컨테이너는 호스트 OS의 커널을 공유하기 때문에 VM에 비해 낮은 수준의 격리를 제공합니다. 각 VM은 독립적인 OS를 가지기 때문에 높은 수준의 격리를 제공합니다.
OS를 포함하지 않기 때문에 VM에 비해 이미지 크기가 일반적으로 작습니다. 전체 OS를 포함하기 때문에 디스크 이미지 크기는 대체로 큽니다.
Docker CLI, Docker Compose 등 다양한 도구와 플랫폼을 제공하여 컨테이너화된 애플리케이션의 배포 및 관리를 쉽게 합니다. VMware, VirtualBox, KVM 등 다양한 하이퍼바이저 및 관리 도구가 있습니다.

 

 

표에 대한 자세한 설명(표가 바로 이해되셨다면 넘어가셔도 됩니다)

 

컨테이너는 호스트 OS의 커널을 공유하며 각 컨테이너는 시스템의 나머지 부분으로부터 격리된 환경에서 실행됩니다

컨테이너와 호스트 OS의 관계를 이해하려면 먼저 컨테이너 기술의 핵심 요소와 원칙을 파악해야 합니다.

 

  1. 호스트 OS의 커널 공유

커널은 운영체제의 핵심 부분으로 하드웨어와 소프트웨어 간의 통신을 관리합니다.
이는 프로세스 관리, 메모리 관리, 디바이스 드라이버의 인터페이스 역할 등 다양한 핵심 기능을 포함합니다.

가상 머신(VM)
  • VM은 하이퍼바이저 위에 동작하며, 각 VM은 자체 운영체제(커널 포함)를 가집니다.
  • VM 내부의 애플리케이션은 해당 VM의 커널을 통해 하드웨어 리소스에 액세스합니다. 이 커널은 호스트 시스템의 실제 커널과 독립적입니다.
컨테이너
  • 컨테이너는 호스트 OS 위에서 동작하며, 호스트의 커널을 직접 사용합니다.
  • 컨테이너 내부의 애플리케이션은 호스트 OS의 커널을 통해 하드웨어 리소스에 액세스합니다.

"컨테이너는 호스트 OS의 커널을 직접 사용한다"는 것은, 컨테이너가 호스트 시스템의 실제 커널을 공유하고 이를 바탕으로 동작한다는 것을 의미합니다.

 

  1. 컨테이너의 격리

컨테이너는 '네임스페이스'라는 커널 기능을 사용하여 시스템의 다른 부분으로부터 격리됩니다.
네임스페이스는 프로세스, 네트워크, 사용자, 파일 시스템 등 다양한 리소스에 대한 격리를 제공합니다.

예를 들어, 네트워크 네임스페이스를 사용하면 각 컨테이너는 자체 IP 주소, 포트 번호, 라우트 테이블 등을 가질 수 있습니다.
PID(프로세스 ID) 네임스페이스를 사용하면 컨테이너 내부의 프로세스는 컨테이너 외부에서는 보이지 않습니다.

 

  1. 컨테이너의 이미지 및 파일 시스템

컨테이너는 '이미지'를 기반으로 실행됩니다.
이미지는 응용 프로그램, 라이브러리, 종속성 및 실행에 필요한 다른 모든 파일을 포함합니다.

'오버레이 파일 시스템'을 사용하여 이미지는 여러 계층으로 구성될 수 있습니다.
이를 통해 여러 컨테이너가 동일한 이미지 계층을 공유하면서 독립적인 변경을 수행할 수 있습니다.

VM은 물리적 하드웨어 위에 하이퍼바이저를 사용하여 별도의 전체 운영체제를 실행합니다. 이것은 완전히 독립된 가상의 컴퓨터를 의미합니다.

 

  1. 물리적 하드웨어
  • 컴퓨터의 실제 구성 요소를 의미합니다.
  • 예를 들어, CPU, RAM, 저장 장치(SSD, HDD), 네트워크 카드 등입니다.

 

  1. 하이퍼바이저
  • 하이퍼바이저는 가상화를 가능하게 하는 소프트웨어 또는 펌웨어 컴포넌트입니다.
  • 이를 통해 단일 물리적 하드웨어 위에서 여러 운영체제를 동시에 실행할 수 있습니다.

 

  1. 별도의 전체 운영 체제
  • VM은 자체적인, 독립된 운영체제를 실행합니다. 이 운영체제는 "게스트 운영체제"라고도 불립니다.
  • 게스트 운영체제는 물리적 하드웨어가 아닌, 하이퍼바이저에 의해 제공되는 가상의 하드웨어 위에서 동작합니다. 가상의 CPU, RAM, 디스크 등의 자원을 사용합니다.
  • 각 VM은 다른 VM과 완전히 독립적입니다. 따라서 한 VM에서 발생하는 문제가 다른 VM에 영향을 미치지 않습니다.

 

요약하면, VM 기술은 하이퍼바이저를 사용하여 물리적 하드웨어를 추상화하고, 이 추상화된 환경 위에서 여러 개의 독립적인 운영체제(게스트 OS)를 동시에 실행할 수 있게 해줍니다.
각 운영체제는 자신만의 가상의 자원과 환경을 가지며, 실제 물리적 자원을 하이퍼바이저를 통해 공유합니다.

 

컨테이너는 거의 즉시 시작될 수 있습니다.

  1. 가벼운 구조
  • 컨테이너는 전체 운영체제를 포함하지 않습니다. 대신 호스트 OS의 커널을 공유합니다. 따라서 컨테이너 이미지는 애플리케이션과 그 종속성만을 ㅗ함하게 되므로 사이즈가 작습니다.

 

  1. 직접적인 커널 액세스
  • 컨테이너는 호스트의 커널을 직접 사용하기 때문에 추가적인 추상화 계층이 없습니다. 이로 인해 컨테이너는 자원에 대한 접근이 빠르고 효율적입니다.

 

  1. 최소한의 부팅 프로세스
  • 전통적인 운영체제나 가상 머신은 시작할 때 많은 초기화 과정과 프로세스를 거쳐야 합니다.
  • 반면, 컨테이너는 애플리케이션 실행을 위한 최소한의 프로세스만 시작하므로 시작 시간이 크게 단축됩니다.

 

  1. 이미지 기반의 구조
  • 컨테이너는 미리 준비된 이미지를 바탕으로 실행됩니다.
  • 이 이미지에는 실행에 필요한 모든 파일과 설정이 포함되어 있기 때문에 추가적인 설정 또는 초기화 과정이 필요 없습니다.
반응형

'개발 > Docker' 카테고리의 다른 글

Docker File, Docker Compose 정의 및 사용 방법  (1) 2023.12.28

프로토콜 버퍼(Protocol Buffers, Protobuf)란?

  • Google에서 개발한 데이터 직렬화 도구입니다.
  • Json이나 XML 같은 다른 직렬화 형식에 비해 작고, 빠르며, 효율적입니다.
  • Protobuf는 주로 원격 프로시저 호출(RPC)에서 사용되며, 특히 gRPC에서 중요한 역할을 합니다.

출처: [https://narup.tistory.com/234](https://narup.tistory.com/234)

위 이미지는 첫번째 json 형태를 직렬화 Protobuf를 이용하여 직렬화 되는 과정을 나타내는 이미지 입니다.

프로토콜 버퍼(Protocol Buffers, Protobuf) 특징

1. 언어 중립적

  • 프로토콜 버퍼 정의는 특정 언어와 독립적입니다. 생성된 소스 코드는 다양한 프로그래밍 언어로 제공됩니다.(java, c++, python 등..)

2. 이진 포맷(데이터를 0, 1의 연속으로 표현하는 방식)

  • 데이터는 이진 포맷으로 직렬화되므로 전송 및 저장이 효율적입니다.

3. 확장 가능(필드 번호 이용)

  • 데이터 구조를 변경하더라도 이전 버전의 구조와 호환성을 유지할 수 있습니다.
  • 확장성과 호환성은 특히 분산 시스템, 마이크로서비스 아키텍처, 또는 단순히 시간이 지나면서 시스템의 요구 사항이 변경되는 경우에 중요한 특성입니다.
  • 프로토콜 버퍼(Protobuf)와 같은 이진 직렬화 포맷들은 확장성을 명확하게 지원하도록 설계되었습니다.
  • 이를 통해 개발자들은 시스템이 성장하고 변화해도 서로 다른 버전의 애플리케이션 간에 데이터를 안전하게 교환할 수 있습니다.
필드 번호란?
  • 프로토콜 버퍼에서는 각 필드에 고유한 번호가 할당됩니다.
  • 필드의 식별자로 사용되며, 각 번호는 직렬화와 역직렬화 과정에서 중요한 역할을 합니다.
  • 일단 번호가 할당되면 번호를 변경해서는 안됩니다.
필드 번호 역할 및 사용 방식

1) 식별자로서의 역할

필드 번호는 해당 필드의 고유 식별자로 동작합니다.
직렬화 시, 필드 이름이 아닌 필드 번호가 사용되어 데이터 스트림에 기록됩니다.
이렇게 함으로써, 데이터 전송 시의 크기가 줄어들게 됩니다.

 

2) 직렬화 및 역직렬화

데이터를 이진 형태로 직렬화 할 때, 필드 번호가 이진 스트림에 함께 기록됩니다.
반대로 데이터를 역직렬화 할 때, 이진 스트림에 기록된 필드 번호를 사용하여 해당 필드의 데이터를 올바르게 복원합니다.

 

3) 확장성 및 호환성

데이터 구조가 시간이 지나면서 변경되더라도 필드 번호는 일정하게 유지됩니다.
즉, 새로운 필드를 추가하거나 기존의 필드를 제거해도 해당 필드 번호는 바뀌지 않습니다.
이렇게 하면, 서로 다른 버전의 프로토콜 버퍼 구조 간에도 데이터의 호환성을 유지할 수 있습니다.

예를 들어, 초기 버전의 메시지 구조가 다음과 같다고 가정합니다.

 

message Person{
    string name = 1;
    int32 age = 2;
}

시간이 지나면서 'age' 필드가 더 이상 필요하지 않을 때, 그 필드를 제거할 수 있지만 필드 번호 2는 재사용해서는 안됩니다.

message Person{
    string name = 1;
    reserved 2;
}

또는 새로운 필드를 추가할 수도 있습니다.

message Person{
    string name = 1;
    int32 age = 2;
    string address = 3;
}

기존 'age'에 할당된 필드 번호 2는 변경되지 않았으므로, 오래된 버전과 새로운 버전모두 호환됩니다.

 

4) 필드 순서의 중요성

필드 번호는 필드의 순서나 위치와는 무관하게 동작합니다.
따라서 메시지 정의에서 필드의 순서가 바뀌더라도 필드 번호는 일정하게 유지되어야 합니다.

4. 정의된 스키마

'.proto' 파일에서 데이터 구조를 정의하게 됩니다.
이 파일은 데이터의 구조와 타입 정보를 포함하며, 이를 바탕으로 코드를 생성할 수 있습니다.

.proto 파일
  • 프로토콜 버퍼(Protobuf) 에서 데이터 구조를 정의하는 곳입니다.
  • 이 파일을 이용해서 특정 언어의 코드를 생성할 수 있습니다.
  • 데이터 교환의 규약 또는 계약처럼 동작하며, 다양한 시스템이나 언어 간의 통신에 있어서 일관된 데이터 구조를 보장합니다.

1) 메시지 정의

 

메시지는 여러 필드를 포함하는 구조체와 같습니다.
각 필드에는 데이터 타입, 필드 이름, 고유한 번호가 지정됩니다.

message Person{
    string name = 1;
    int32 age = 2;
}

 

2) 필드 타입

 

a. 정수 타입

  • int32, int64 : 순서대로 32비트, 64비트의 일반적인 정수 타입입니다.

b. 실수 타입

  • float, dobule : 순서대로 32비트, 64 비트의 부동소수점 타입입니다.

c. 문자열 및 바이트

  • string : UTF-8로 인코딩된 문자열 타입입니다.
  • bytes : 임의의 바이트 데이터를 포함하는 타입입니다.

d. bool(참, 거짓)

  • bool : 참 또는 거짓 값을 나타내는 타입입니다.

e. 열거형

  • enum : 명명된 값의 집합을 나타내는 타입입니다.

f. 필드 타입

  • 단일 필드
    기본적인 데이터 타입이나 복합 메시지 타입을 직접 사용하여 필드를 정의할 수 있습니다.
message Sample{
    int32 id = 1;
    string name = 2;
}
  • 반복 필드 (Repeated Fields)
    특정 필드 타입의 여러 값을 배열 또는 리스트 형태로 포함시킬 수 있습니다.
message Sample{
    repeated string tags = 1;
}
  • 옵셔널 필드
    Protobuf v2 에서는 optional 키워드를 사용하여 필드가 선택적으로 포함될 수 있음을 나타내야 했지만 Protobuf v3에서는 모든 필드는 기본적으로 선택적(옵셔널) 입니다.
  • 임베디드 메시지

다른 메시지 타입을 필드 타입으로 사용하여 중첩된 복합 구조를 만들 수 있습니다.

message Address{
    string city = 1;
    string state = 2;
}

message Person{
    string name = 1;
    Address address = 2;
}
  • Oneof

여러 필드 중 하나만 값을 가질 수 있을 때 사용하는 구조입니다.

message ContactInfo{
    oneof method{
        string phone_number = 1;
        string email = 2;
        string address = 3;
    }
}

위의 예에서 ContactInfo 메시지는 method라는 oneof 필드를 가지며, 이 필드는 phone_number, email, address 중 하나만 값을 가질 수 있습니다.

 

oneof를 사용하면 어떤 필드 중에서 하나만 값이 있어야 함을 명확하게 나타낼 수 있습니다.
이는 데이터의 무결성을 보장합니다.

 

oneof 내의 필드들은 공유 메모리를 사용하기 때문에 메모리 사용량이 절약됩니다.
즉, 한번에 하나의 필드만 값이 할당되므로, 여러 필드에 동시에 메모리를 할당할 필요가 없습니다.

 

만약 프로그램이 실수로 두 개 이상의 필드에 값을 할당하려고 하면, 마지막으로 할당된 필드의 값만 유지되며, 그 전의 필드 값들은 모두 삭제됩니다.

이를 통해 오류를 방지할 수 있습니다.

 

3) 서비스 정의

.proto 파일에서는 RPC(Remote Procedure Call) 서비스를 정의할 수도 있습니다.
이를 통해 클라이언트와 서버 간의 통신 규약을 명확하게 지정할 수 있습니다.

service UserService{
    rpc CreateUser (UserRequest) returns (UserResponse);
}

message UserRequest{
    string username = 1;
}

message UserResponse{
    int32 user_id = 1;
}

 

4) 코드 생성

.proto 파일을 정의한 후, 프로토콜 버퍼 컴파일러(protoc)를 사용하여 특정 언어로 코드를 생성할 수 있습니다.
예를 들어, Java 용 코드를 생성하려면

protoc --java_out=./ yourfile.proto

이렇게 생성된 코드는 프로토콜 버퍼 메시지의 직렬화, 역직렬화, 데이터 접근 등의 기능을 제공합니다.

 

5) 확장 및 임포트

.proto 파일 내에서 다른 .proto 파일을 임포트하여 정의된 메시지나 타입을 사용하거나 확장할 수 있습니다.

import "other_definitions.proto";

message ExtendedPerson {
    Person base = 1;
    string address = 2;
}
반응형

'개발 > MSA' 카테고리의 다른 글

ConnectionPool 이란? - 설정 방법  (0) 2023.09.18

ConnectionPool 이란?

연결을 재사용 가능한 형태로 미리 생성해두고, 필요할 때 애플리케이션에서 제공하는 매커니즘입니다.
ConnectionPool을 사용하는 주요 이유는 연결을 맺고 해제하는 과정이 상대적으로 많은 자원과 시간을 소모하기 때문입니다.

Connection Pool 동작 과정

  1. 애플리케이션이 시작될 때, Connection Pool은 지정된 수의 연결을 미리 생성합니다.
  2. 애플리케이션은 연결 작업이 필요할 때, Connection Pool로부터 연결을 요청합니다.
  3. Connection Pool은 사용 가능한 연결을 애플리케이션에 제공합니다.
  4. 애플리케이션은 작업이 끝나면 연결을 닫지 않고, 다시 Connection Pool에 반환합니다. 이 연결은 후속 요청에서 재사용됩니다.

Connection Pool의 장단점

장점

  1. 응답 시간 감소

사용자가 웹 애플리케이션에 로그인 요청을 보낼 때마다 새로운 연결을 생성하는 대신, 미리 생선된 연결을 사용하여 즉시 응답을 처리할 수 있습니다.
연결 설정 시간이 제거되므로 전체 응답 시간이 빨라집니다.

  1. 리소스 최적화

연결 풀을 사용하면 동시에 열려있는 연결 수를 제한할 수 있습니다.
이렇게 하면 서버에 과도한 부하가 걸리는 것을 방지하면서 리소스를 효율적으로 활용할 수 있습니다.

  1. 시스템 안정성 향상

특정 서비스에 대한 급증한 요청으로 서비스가 다운되는 경우, 연결 풀의 크기 제한을 사용하여 특정 임계값을 넘지 않게 할 수 있습니다.
이는 서비스 거부 공격(Denial-of-Service attack)과 같은 상황에서도 서비스의 안정성을 유지하는데 도움이 됩니다.

단점

  1. 초기 구성 복잡성

연결 풀의 최소/최대 크기, 유휴 시간, 연결 유효성 검사 등 다양한 설정 값을 조절하고 최적화해야 합니다.
설정이 적절하지 않으면 성능 문제나 안정성 문제가 발생할 수 있습니다.

  1. 잠재적 리소스 낭비

연결 풀의 크기가 너무 크게 설정되면, 실제로 사용되지 않는 많은 연결이 유휴 상태로 남아 있게 되어 리소스가 낭비될 수 있습니다.

  1. 커넥션 누수 위험

애플리케이션 코드에서 연결을 올바르게 반환하지 않으면, 해당 연결이 풀에서 계속 점유되게 됩니다.
이러한 상황이 반복되면 결국 풀에 사용 가능한 연결이 없게 되어 새로운 요청을 처리할 수 없게 됩니다.
이를 커넥션 누수(connection leak)라고 합니다.

  1. 불완전한 연결 상태

연결이 오래동안 유휴 상태로 있거나 네트워크 문제 등으로 인해 연결이 끊어진 상태일 수 있습니다.
이 경우, 애플리케이션이 해당 연결을 사용하려고 하면 오류가 발생할 수 있습니다.
이를 방지하기 위해 연결 유효성 검사 등의 추가 작업이 필요합니다.

Connection Pool 이 주로 사용되는 4가지

  1. DB
  • DB 연결은 생성과 해제에 비용이 많이 들기 때문에, 연결 풀링을 통해 성능을 향상시킬 수 있습니다.
  1. HTTP 연결
  • 웹 서버 또는 웹 서비스에 대한 HTTP(S) 연결을 풀링하여, 동일한 목적지에 대한 반복적인 요청의 성능을 향상시킬 수 있습니다.
  1. 네트워크 소켓 연결
  • 원격 서비스나 미들웨어(ex: message broker) 와의 통신을 위한 일반적인 TCP 또는 UDP 소켓 연결을 풀링할 수 있습니다.
  1. LDAP 연결
  • LDAP 서버와의 통신을 위한 연결을 풀링하여, 사용자 인증 및 권한 부여 쿼리의 성능을 향상시킬 수 있습니다.

Connection Pool 의 주요 설정 값

  1. maxPoolSize (최대 풀 크기)
  • 연결 풀에 보유할 수 있는 최대 연결 수 입니다.
  • 너무 큰 값은 시스템의 자원을 낭비할 수 있으므로, 실제 시스템의 부하에 따라 조절이 필요합니다.
  1. minPoolSize (최소 풀 크기)
  • 연결 풀이 유지해야 하는 최소한의 연결 수 입니다.
  • 시스템이 비활성 상태일 때도 일정 수의 연결을 유지하게 됩니다.
  1. initialPoolSize (초기 풀 크기)
  • 연결 풀이 처음 생성될 때 만들어져야 하는 연결의 수 입니다.
  • minPoolSize 이상, maxPoolSize 이하의 값이 되어야 합니다.
  1. connectionTimeout (연결 시간 초과)
  • 연결을 얻기 위해 대기할 최대 시간입니다.
  • 지정된 시간 안에 연결을 얻지 못하면 오류가 발생합니다.
  1. idleTimeout (유휴 연결 시간 초과)
  • 연결이 유휴 상태로 있을 수 있는 최대 시간입니다. 이 시간을 초과하면 연결은 닫힙니다.
  • 유휴 상태(Idle State)는 어떤 자원이 활성화되어 있찌만 현재 사용되고 있지 않은 상태(대기 중, 사용되지 않는 상태) 입니다.
  1. maxLifetime (최대 연결 수명)
  • Pool 에 있을 수 있는 연결의 최대 수명입니다.
  • 이 시간을 초과하면 연결은 닫힙니다.
  1. validationTimeout (검증 시간 초과)
  • 연결 검증에 대한 최대 대기 시간입니다.
  1. connectionTestQuery (연결 테스트 쿼리)
  • 연결의 유효성을 확인하기 위한 쿼리입니다.
  • 일반적으로 간단한 쿼리 (ex: 'SELECT 1')가 사용됩니다.

ConnectionPool 의 적절한 설정값 구하기

사실상 ConnectionPool 의 설정값을 "이 값으로 하면 최적이다"라고 하는 값은 없습니다.
그러므로 다음과 같은 절차를 따라 적절한 설정값을 구할 수 있습니다.

  1. 시스템 분석
  • 시스템의 특성, 예상 사용자 수, 트래픽 패턴, 데이터베이스와의 통신 패턴 등을 분석합니다.
  1. 기본값 시작
  • 많은 Connection Pool 라이브러리는 합리적인 기본값을 제공합니다. 이를 시작점으로 사용하고, 이후에 조절할 수 있습니다.
  1. 모니터링
  • 초기 설정을 한 후에는 Connection Pool 의 사용 상태를 모니터링 합니다.
  • 풀에서 사용 가능한 연결의 수, 대기 중인 연결 요청의 수, 유휴 연결의 수 등을 확인할 수 있습니다.
  1. 부하 테스트
  • 시스템에 예상 트래픽 이상의 부하를 가하면서 Connection Pool의 동작을 관찰합니다.
  • 이를 통해 최대 연결 수, 연결 시간 초과 등의 설정값을 조절할 수 있습니다.
  1. 조절 및 반복
  • 모니터링 및 부하 테스트의 결과를 바탕으로 설정값을 조절하고, 다시 테스트와 모니터링을 반복합니다.
  1. 유휴 연결 관리
  • 연결이 유휴 상태로 오래 유지되지 않도록 설정합니다.
  • 일정 시간 이상 사용되지 않는 연결은 자동으로 닫히도록 'idleTimeout' 설정을 조절할 수 있습니다.
  1. 문서화
  • 설정값과 그 이유, 테스트 결과 등을 문서화하여 나중에 참조하거나, 다른 팀원과 공유할 수 있도록 합니다.
  1. 정기적인 리뷰
  • 시스템의 환경이나 요구 사항이 변경될 수 있으므로, 주기적으로 Connection Pool 설정을 리뷰하고 필요에 따라 조절합니다.
반응형

'개발 > MSA' 카테고리의 다른 글

프로토콜 버퍼(Protocol Buffers, Protobuf)란?  (0) 2023.09.19

JQuery와 Java Spring을 이용한 프로젝트에서 JQuery 쪽의 ajax에서 Get Method를 이용한 통신을 Post Method를 이용한 통신으로 변경하였을 때 java에서 설정한 Parameter의 값이 넘어오지 않는 현상 발견!

기존 Get을 이용한 ajax 통신

아래와 같은 코드를 이용하였을 때는 java쪽에 parameter가 정상적으로 처리가 되었습니다.

  • JQuery 코드

let param = $('#inquiry-form').serialize();

$.ajax({
type : "GET",
url : urlPrefix+'/inquiry/sendInquiry',
data : param,
dataType : 'json',
contentType : "application/json; charset=utf-8",
success : function(data){
$('.flex-input-box').removeClass('error')
if(data["code"] == "200"){
window.location.href = urlPrefix+'/inquiry/complete'
}else{
alert(data["message"]);
}
}
})


- java 코드

@RestController
@RequestMapping("${signplus.url-prefix.front}/inquiry")
public class FrontInquiryController {

@Autowired
HttpApiConnectionUtill httpApiConnectionUtill;

@GetMapping()
public String view() {
    return "front/inquiry/inquiry";
}

@GetMapping("/complete")
public String complete() {
    return "front/inquiry/complete";
}

@GetMapping("/sendInquiry")
public ResponseEntity<JSONObject> sendInquiry(@RequestParam Map<String,Object> paramMap) throws URISyntaxException {

    JSONObject jsonObject = httpApiConnectionUtill.sendApiCallClientCredentials(ApiUrlConstants.util+"/sendInquiry", HttpMethod.POST, paramMap);

    JSONObject result = new JSONObject();
    result.put("code", jsonObject.get("code"));
    result.put("message", jsonObject.get("message"));

    return ResponseEntity.status(HttpStatus.OK).body(result);
}

}


### Post를 이용한 ajax 통신으로 변경

처음에 ajax 내의 method를 get -> post, java쪽 @GetMapping -> @PostMapping으로만 변경하면 동작 할 줄 알고 변경하였지만 parameter의 값이 넘어오지 않는 현상이 발견되었습니다.
이부분은 parameter가 전달되지 않은 채로 성공으로 처리되고 있어 발견하기 쉽지 않았습니다.

원인으로는 Get 메소드와 ajax에서 data로 넘겨주는 form 태그의 serialize()의 내용을 제대로 이해하지 못해 나타난 오류였습니다.

@GetMapping에서는 웹 브라우저나 다른 클라이언트가 URL의 쿼리 파라미터를 통해 데이터를 전송합니다.
예를 들면, https://example.com/inquiry?param1=value1&param2=value2와 같은 형태입니다.
@RequestParam은 이러한 쿼리 파라미터를 쉽게 받아올 수 있도록 도와줍니다.
이전 코드에서 ajax의 contentType을 json 형식으로 넘긴다고 정의하였었지만 실제로는 Get 메소드의 특징에 의해 쿼리스트링으로 전달되고 있었던겁니다.

그러나, @PostMapping을 사용할 때는 요청 본문(body)에 데이터가 포함되어 전송됩니다.
@PostMapping을 이용하여 ajax를 통해 본문으로 json 형태로 넘긴다고(contentType) 명시하였지만 실제로는 serialize()를 이용하여 URL-encoded로 보내려고 했으니 해당 부분이 json이 아니어서 @RequestParam 으로 parameter를 받으려고 했지만 값이 제대로 넘어가고 있지 않았던 겁니다.

해결책으로는 2가지가 있습니다.

1. ajax를 이용하여 data를 보낼 때, json 형식으로 변경, java에서 @RequestBody를 이용하여 parameter 받아오기
2. ajax를 이용하여 기존과 동일하게 URL-encoded로 전송, 단, 위의 코드에서 contentType으로 json으로 보낸다는 부분 제거 또는 URL-encoded 명시적으로 설정


#### 수정한 코드

- JQuery

let param = $('#inquiry-form').serialize();

$.ajax({
type : "POST",
url : urlPrefix+'/inquiry/sendInquiry',
data : param,
dataType : 'json',
success : function(data){
$('.flex-input-box').removeClass('error')
if(data["code"] == "200"){
window.location.href = urlPrefix+'/inquiry/complete'
}else{
alert(data["message"]);
}
}
})


- java

@RestController
@RequestMapping("${signplus.url-prefix.front}/inquiry")
public class FrontInquiryController {

@Autowired
HttpApiConnectionUtill httpApiConnectionUtill;


@PostMapping("/sendInquiry")
public ResponseEntity<JSONObject> sendInquiry(@RequestParam Map<String,Object> paramMap) throws URISyntaxException {

    JSONObject jsonObject = httpApiConnectionUtill.sendApiCallClientCredentials(ApiUrlConstants.util+"/sendInquiry", HttpMethod.POST, paramMap);

    JSONObject result = new JSONObject();
    result.put("code", jsonObject.get("code"));
    result.put("message", jsonObject.get("message"));

    return ResponseEntity.status(HttpStatus.OK).body(result);
}

}
```

반응형
반응형

원인

Member와 Message가 연관관계를 맺고 있고 1:N 관계를 가지고 있다.
Message 엔티티에서 Member Fetch 전략을 Lazy로 설정해준 상태이다.

오류 예시

// 메시지 제목, 내용, 작성자 이름, 받는 사람 이름 등의 정보가 들어있는 MessageResponseDTO로 변환하여 리소스를 반환하는 API

@GetMapping("/message/{message_id}")
public Result readMessage(@PathVariable Long message_id){
    Message msg = messageService.findB yId(message_id);
    MessageResponseDto messageResponseDto = MessageResponseDto.covertMessageDto(msg);
    return responseService.getSingleResult(messageResponseDto);
}

오류발생

  • Message를 단건조회하면 Message와 Lazy Loding으로 연관된 Member는 바로 초기화 되지 않고 필요할 때 정보가 채워지는 프록시 객체로 채워진다.

Message = Message 필드 + Member Proxy 객체

  • Member의 값을 써서 DTO를 채워야하는데 Member의 값이 초기화 되지 않은 상태라서 DTO를 만들 수 없음.

Lazy Loding방식이니 변환하면서 데이터를 사용할 때 쿼리를 날려 Proxy 객체를 채우지 못함.
-> Service에서 트랜잭션이 일어나도록 설정을 하였음. JPA 영속성 컨텍스트는 보통 트랜잭션과 생명주기를 같이한다. 그 말은 Service -> Controller로 나오면서 영속성 상태가 끝난다는 뜻이다. 더이상 영속성 컨텍스트에서 관리하지 않고 Member에 필요한 값이 있을때 쿼리를 날려 Proxy 객체를 채우지 않는다는 뜻이다.

해결방법

  1. Message -> DTO 변환을 컨트롤러 단에서 서비스 단으로 변경
@GetMapping("/message/{message_id}")
public Result readMessage(@PathVariable Long message_id){
    MessageResponseDto messageResponseDto = messageService.findByMessageId(message_id);
    return responseService.getSingleResult(messageResponseDto);
}
  1. Message에 있는 Member를 즉시로딩(Eager)로 변경한다.
  • 추천하지 않음.
반응형

'개발 > JPA' 카테고리의 다른 글

QueryDsl이란? 왜 사용하는가?  (0) 2023.12.15
JPA-OSIV(Open Session In View)  (0) 2023.10.14
[JPA] GeneratedValue  (0) 2023.09.10
[JPA] Auditing - 공통 도메인 작업  (0) 2023.09.10
[JPA] @MappedSuperclass  (0) 2023.09.10

주의점

Hibernate 에서는 여러개의 데이터를 한번에 Insert, Update 하게 해주는 기능인 Batch 기능을 지원하고 있다.

jpa:
    hibernate:
      ddl-auto: create
    properties:
      hibernate:
        generate_statistics: true
        dialect: org.hibernate.dialect.H2Dialect
        show_sql: true
        format_sql: true
        order_inserts: true
        order_updates: true
        jdbc:
          batch_size: 1000

Spring Data JPA의 saveAll 함수를 사용하면 여러개의 Insert 또는 Update 할 수 있다.

하지만, @GeneratedValue 키 값 생성 전략을 Identity나 Auto로 정하는 경우 Hibernate에서 Batch Insert 기능을 비활성화 시켜놓고 Insert 작업을 수행하게 된다.

따라서 saveAll 같은 함수를 사용해도 데이터 개수만큼 Insert 또는 Update 쿼리가 나간다.

반응형

'개발 > JPA' 카테고리의 다른 글

JPA-OSIV(Open Session In View)  (0) 2023.10.14
[JPA] could not initialize proxy - no Session  (0) 2023.09.10
[JPA] Auditing - 공통 도메인 작업  (0) 2023.09.10
[JPA] @MappedSuperclass  (0) 2023.09.10
[Spring] NativeQuery  (0) 2023.09.10
반응형

JPA Auditing이란?

Java에서 ORM 기술인 JPA를 사용하여 도메인을 관계형 데이터베이스 테이블에 매핑할 때 공통적으로 도메인들이 가지고 있는 필드나 컬럼들이 존재합니다. 대표적으로 생성일자, 수정일자, 식별자 같은 필드 및 컬럼이 있습니다.
도메인마다 공통으로 존재한다는 의미는 결국 코드가 중복된다는 말과 일맥상통합니다.
데이터베이스에서 누가, 언제하였는지 기록을 잘 남겨놓아야 합니다. 그렇기 때문에 생성일, 수정일 컬럼은 대단히 중요한 데이터입니다.
그래서 JPA에서는 Audit 이라는 기능을 제공하고 있습니다. Audit은 감시하다, 감사하다라는 뜻으로 Spring Data JPA에서 시간에 대해서 자동으로 값을 넣어주는 기능입니다. 도메인을 영속성 컨텍스트에 저장하거나 조회를 수행한 후에 update를 하는 경우 매번 시간 데이터를 입력하여 주어야 하는데, audit을 이용하면 자동으로 시간을 매핑하여 데이터베이스의 테이블에 넣어주게 됩니다.

Auditing 활성화 하기

  • 가장 먼저 SpringBootApplication에 @EnableJpaAuditing 어노테이션을 추가해줍니다.
@EnableJpaAuditing
@SpringBootApplication
public class TestApplication{
    public static void main(String[] argAS){
        SpringApplication.run(TestApplication.class, args);
    }
}

BaseEntity 생성하기

  • Auditing이 필요한 Entity에서 상속받을 BaseEntity를 생성합니다.
@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseEntity{
    @CreatedDate
    @Column(updatable = false)
    private LocalDateTime createdDate;

    @LastModifiedDate
    private LocalDateTime updatedDate;

    @CreatedBy
    @Column(updatable = false)
    private String createdBy;

    @LastModifiedBy
    private String modifiedBy;
}

@MappedSuperclass (javax.persistence)

  • Entity에서 Table에 대한 공통 매핑 정보가 필요할 때 부모 클래스에 정의하고 상속받아 해당 필드를 사용하여 중복을 제거

@EntityListenrs (javax.persistence)

  • Entity를 DB에 적용하기 이전, 이후에 커스텀 콜백을 요청할 수 있는 어노테이션

Class AuditingEntityListner (org.springframework.data.jpa)

  • Entity 영속성 및 업데이트에 대한 Auditing 정보를 캡처하는 JPA Entity Listener

@CreatedDate (org.springframework.data)

  • 데이터 생성 날짜 자동 저장 어노테이션

@LastModifiedDate (org.springframework.data)

  • 데이터 수정 날짜 자동 저장 어노테이션

@CreatedBy (org.springframework.data)

  • 데이터 생성자 자동 저장 어노테이션

@LastModifiedBy (org.springframework.data)

  • 데이터 수정자 자동 저장 어노테이션

Entity에 적용하기

@Getter
@Entity
@NoArgsConstructor(access = PROTECTED)
public class class Users extends BaseEntity{
    @Id
    @GeneratedValue
    @Column(name = "user_id")
    private Long id;

    private String name;
}

@CreatedBy, @ModifiedBy 사용하기

org.springframework.data.domain.AuditorAware를 스프링 빈으로 등록해야 합니다.

public interface AuditorAware<T> {
    /**
    * Returns the current auditor of the application.
    *
    * @return the current auditor.
    */
    Optional<T> getCurrentAuditor();
}

AuditorAware 인터페이스는 Optional를 반환하는 method가 하나 있기 때문에 아래 코드처럼 람다로 AuditorAware를 구현한 객체를 반환할 수 있습니다.

@Bean
public AuditorAware<String> auditorProvider(){
 // 람다를 이용
  return () -> Optional.of(UUID.randomUUID().toString());

  // 익명 클래스를 이용
  return new AuditorAware<String>(){
      @Override
      public Optional<String> getCurrentAuditor(){
          return Optional.of(UUID.randomUUID().toString());
      }
  }
}
반응형

'개발 > JPA' 카테고리의 다른 글

[JPA] could not initialize proxy - no Session  (0) 2023.09.10
[JPA] GeneratedValue  (0) 2023.09.10
[JPA] @MappedSuperclass  (0) 2023.09.10
[Spring] NativeQuery  (0) 2023.09.10
[JPA] Cascade  (1) 2023.09.10
반응형

@MappedSuperclass

  • 객체의 입장에서 공통 매핑 정보가 필요할 때 사용한다.
  • id, name, created_at, updated_at은 객체의 입장에서 볼 때 계속 나온다.
  • 이렇게 공통 매핑 정보가 필요할 때 부모 클래스에 선언하고 속성만 받아서 사용하고 싶을 때 @MappedSuperclass를 사용한다.테이블 구조
-- Member 테이블
create table member(
    id bigint,
    created_at date,
    updated_at date
);
-- Team 테이블
create table team(
    team_name varchar(128),
    created_at date,
    updated_at date
);

코드로 이해하기

  • 생성시간, 수정시간을 모든 엔티티에 공통으로 가져가야 하는 상황
  • BaseEntity.java
    • 매핑정보만 상속받는 Superclass라는 의미의 @MappedSuperclass 어노테이션 선언
@Getter
@Setter
@MappedSuperclass
public abstact class BaseEntity{
    private LocalDate createdAt;
    private LocalDate update_at;
}
@Entity
public class Member extends BaseEntity{
    @Id
    private Long id;
}
@Entity
public class Team extends BaseEntity{
    @Id
    private String teamName;
}

정리

  • 상속관계 매핑이 아니다.
  • @MappedSuperclass가 선언되어 있는 클래스는 엔티티가 아니다. 당연히 테이블과 매핑도 안된다.
  • 단순히 부모 클래스를 상속받는 자식 클래스에 매핑 정보만 제공한다.
  • 부모 타입으로 조회, 검색이 불가능하다.
  • 직접 생성해서 사용할 일이 없으므로 추상 클래스로 만드는 것을 권장한다.
  • 주로 등록일, 수정일 같은 전체 엔티티에서 공통으로 적용하는 정보를 모을 때 사용한다.
  • JPA에서 @Entity 클래스는 @Entity나 @MappedSuperclass로 지정한 클래스만 상속할 수 있다.
반응형

'개발 > JPA' 카테고리의 다른 글

[JPA] GeneratedValue  (0) 2023.09.10
[JPA] Auditing - 공통 도메인 작업  (0) 2023.09.10
[Spring] NativeQuery  (0) 2023.09.10
[JPA] Cascade  (1) 2023.09.10
[JPA] @OneToMany orphanRemoval  (0) 2023.09.10
반응형

@Query

  • JPA에 정의된 키워드를 조합하면 특정조건에 해당하는 데이터를 원하는 형태대로 가지고 올 수 있습니다. 하지만 데이터베이스에 종속적인 문법을 사용해야 할 때나 Entity 간의 명시적으로 들어나지 않는 관계간의 조인, 데이타 조회 속도 향상등의 목적으로 직접 쿼리를 작성할 수 있는 방법을 제공하고 있습니다.
  • @Query 속성중에 nativeQuery 속성을 true로 설정하지 않았따면 기본적으로 JPQL 문법으로 동작이 됩니다.
  • JPQL 문법은 JPA 에서 사용되는 언어이며 쿼리 구문과 유사하나 Table이 아닌 Entity를 기준으로 데이터를 조회한다는 것이 다릅니다.

NativeQuery란?

  • JPA는 SQL이 지원하는 대부분의 문법과 SQL 함수들을 지원하지만 특정 데이터베이스에 종속적인 기능은 잘 지원하지 않는다. 하지만 때로는 특정 데이터베이스에 종속적인 기능이 필요할 수도 있다. 다양한 이유로 JPQL을 사용할 수 없을 때, JPA는 SQL을 직접 사용할 수 있는 기능을 제공하는데 이것을 네이티브 SQL(네이티브쿼리)라 한다.
  • 즉, 사용자가 직접 데이터베이스에 날리는 쿼리를 작성하는 것이다.
  • NativeQuery는 엔티티를 조회할 수 있고 JPA가 지원하는 영속성 컨텍스트의 기능을 그대로 사용할 수 있다.
반응형

'개발 > JPA' 카테고리의 다른 글

[JPA] Auditing - 공통 도메인 작업  (0) 2023.09.10
[JPA] @MappedSuperclass  (0) 2023.09.10
[JPA] Cascade  (1) 2023.09.10
[JPA] @OneToMany orphanRemoval  (0) 2023.09.10
[JPA] @OneToMany 단방향 매핑의 단점  (0) 2023.09.10
반응형

Cascade란?

  • cascade 옵션이란 @OneToMany나 @ManyToOne에 옵션으로 줄 수 있는 값이다.
  • Entity의 상태 변화를 전파시키는 옵션이다.
  • 만약 Entity의 상태 변화가 있으면 연관되어 있는(ex. @OneToMany, @ManyToOne) Entity에도 상태 변화를 전이시키는 옵션이다.
  • 기본적으로는 아무 것도 전이시키지 않는다.

Entity의 상태

  1. Transient
  • 객체를 생성하고 값을 주어도 JPA나 hibernate가 그 객체에 관해 아무것도 모르는 상태. 즉, 데이터베이스와 매핑된 것이 아무것도 없다.
  1. Persistent
  • 저장을 하고나서 JPA가 아는 상태(관리하는 상태)가 된다. 그러나 .save()를 했다고 해서 바로 DB에 데이터가 들어가는 것이 아니다. JPA가 persistent 상태로 관리하고 있다가 후에 데이터를 저장한다.(1차 캐시, Dirty Checking(변경사항 감지), Write Behind(최대한 늦게, 필요한 시점에 DB에 적용)등의 기능을 제공한다)
  1. Detached
  • JPA가 더이상 관리하지 않는 상태. JPA가 제공해주는 기능들을 사용하고 싶다면 다시 persistent 상태로 돌아가야한다.
  1. Removed
  • JPA가 관리하는 상태이긴 하지만 실제 commit이 일어날 때 삭제가 일어난다.

사용 예제

ex) Post(1) : Comment(N) 관계

게시글이 존재하고 해당 게시물에 달린 댓글들은 만약 이 게시글이 저장되거나 삭제되면 같이 저장되거나 삭제되어야 한다고 가정한다.

-> 이 상태로 실행하면 post만 저장해주었기 때문에 post 테이블에만 데이터가 저장된다.

여기서 Post 객체에 cascade 옵션을 주면 comment도 같이 저장이된다.

-> Post라는 인스턴스가 Transient에서 Persistent 상태로 넘어갈 때 child 객체(Comment)도 같이 Persisten 상태가 되면서 같이 저장이 되는 것이다.

cascade remove옵션도 주고나서 삭제하면 게시글을 지우면 댓글들도 같이 삭제된다.

일반적으로는 CascadeType.ALL 옵션을 줘서 사용한다.

반응형

'개발 > JPA' 카테고리의 다른 글

[JPA] @MappedSuperclass  (0) 2023.09.10
[Spring] NativeQuery  (0) 2023.09.10
[JPA] @OneToMany orphanRemoval  (0) 2023.09.10
[JPA] @OneToMany 단방향 매핑의 단점  (0) 2023.09.10
JPA 무한 재귀 해결방법  (0) 2023.09.10

+ Recent posts