728x90

Java에서 멀티스레드 환경을 개발하다 보면, 스레드마다 독립적인 데이터를 저장하고 관리해야 할 때가 있습니다.

이럴 때 유용하게 활용할 수 있는 클래스가 바로 ThreadLocal입니다.

 

이번 글에서는 ThreadLocal의 개념, 사용 방법, 그리고 실제 사례를 중심으로 살펴보겠습니다.

ThreadLocal이란?

ThreadLocal은 각 스레드마다 별도의 변수를 저장할 수 있도록 지원하는 Java 클래스입니다.

 

동일한 ThreadLocal 인스턴스를 사용하더라도, 각 스레드가 자신만의 독립적인 값을 가지며, 다른 스레드와 공유되지 않습니다.

즉, 하나의 변수를 스레드 간에 안전하게 관리할 수 있는 메커니즘을 제공합니다.

ThreadLocal의 특징

  1. 각 스레드마다 독립적인 데이터 저장 공간을 제공합니다.
  2. 스레드 간 데이터 공유를 방지하며, 멀티스레드 환경에서 데이터 충돌을 줄입니다.
  3. 특정 스레드에 한정된 데이터를 저장할 때 유용합니다.

ThreadLocal의 사용 방법

ThreadLocal은 주로 다음과 같은 메소드를 사용하여 데이터를 저장하고 접근합니다.

set()

현재 스레드의 ThreadLocal 변수에 값을 저장합니다.

ThreadLocal<String> threadLocal = new ThreadLocal<>();
threadLocal.set("Thread A's Data");

get()

현재 스레드의 ThreadLocal 변수에 저장된 값을 가져옵니다.

String data = threadLocal.get();
System.out.println(data);

remove()

현재 스레드의 ThreadLocal 변수에 저장된 값을 삭제합니다. 이는 메모리 누수를 방지하기 위해 권장되는 작업입니다.

threadLocal.remove();

initialValue()

ThreadLocal 변수를 초기화할 때 사용됩니다. 기본적으로 null을 반환하지만, 필요하면 오버라이딩하여 초기값을 설정할 수 있습니다.

ThreadLocal<Integer> threadLocal = new ThreadLocal<>() {
    @Override
    protected Integer initialValue() {
        return 0;
    }
};
System.out.println(threadLocal.get());

사용 시 주의사항

ThreadLocal은 사용 후 반드시 remove() 메소드를 호출하여 데이터를 정리해야 합니다.

이를 소홀히 하면, 스레드가 종료된 후에도 ThreadLocal 객체에 데이터가 남아 메모리 누수가 발생할 수 있습니다.

 

또한, 너무 많은 데이터를 ThreadLocal에 저장할 경우 OOM을 야기할 수 있으니 적절하게 사용하는 것을 권장합니다.

 

ThreadLocal은 부모 Thread가 자식 Thread로 데이터를 전달하지 않습니다.

따라서 데이터를 상속하기 위해서는 ThreadLocal를 상속한 InheritableThreadLocal를 사용하시면 됩니다.


사용해 보기

ThreadLocal 정의

@Getter
@Setter
public class CustomContext {
    private static final ThreadLocal<CustomContext> CONTEXT = ThreadLocal.withInitial(CustomContext::new);

    private Object data;

    public static CustomContext getContext() {
        return CONTEXT.get();
    }

    public static void clearContext() {
        CONTEXT.remove();
    }
}

JWT Token 유저 ID ThreadLocal에 저장 및 사용 후 정리

@Slf4j
@Component
@RequiredArgsConstructor
public class AuthenticationInterceptor implements HandlerInterceptor {

    private final JwtTokenProvider jwtTokenProvider;
    private final CustomContext customContext = CustomContext.getContext();;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
        if(!(handler instanceof HandlerMethod))
            return true;

        String token = request.getHeader("authorization");
        if(Strings.isEmpty(token)) {
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "JWT token is missing");
            return false;
        }

        token = token.substring(7);
        if(!jwtTokenProvider.validateToken(token)) {
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "JWT Token is invalid");
            return false;
        }
        String userId = jwtTokenProvider.getUserId(token);
        customContext.setData(userId);

        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        CustomContext.clearContext();
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }
}

ThreadLocal 데이터 사용

@Getter
@Setter
public class CreateQuoteRequest {
    transient private String userId;
    private long amount;
    private CurrencyCode targetCurrency;

    public CreateQuoteRequest() {
        CustomContext context = CustomContext.getContext();
        Assert.notNull(context);
        this.userId = (String) context.getData();
    }
}

후기

기존 ID를 넘겨줄 때 Token에서 뽑아 HttpServletRequest에 담거나 하여 사용을 하였지만,

이와 같이 ThreadLocal을 사용하게 된다면 본인 Thread 내에서 데이터를 쉽게 뽑아 사용할 수 있다는 점이 매력적으로 다가온 기술입니다.

 

물론 메모리에 올려두고 사용한다는 점에서 비효율적일 수 있지만, 코드를 생각했을 때 좀 더 보기 편하고 관리하기 편하다는 점이 부정할 수 없다 생각하는데요.

 

추후 좀 더 관리하기 좋은 방법을 찾게 된다면 포스팅하도록 하겠습니다. 

728x90

'Java' 카테고리의 다른 글

오차 없는 실수 연산 어떻게 해야 할까?  (0) 2025.01.10
[Java] Static  (3) 2024.12.26
[ Basic ] I/O Stream이란?  (0) 2024.12.23
[JPA] 비관적 락 낙관적 락  (0) 2024.06.29
JPA는 어떤 기술일까?  (0) 2024.04.12
728x90

컴퓨터 프로그래밍에서 실수 연산은 흔히 오차 문제를 동반합니다.

 

특히 금융, 과학 계산, 그리고 데이터 분석 등 정확한 수치가 중요한 분야에서는 이 문제가 더욱 부각되는 문제인데요.

이번 글에서는 Double의 한계, Float를 사용한 실수 연산의 특징, 그리고 BigDecimal을 활용한 오차 없는 실수 연산 방법을 살펴보겠습니다.


1. Double의 문제: 부동소수점 연산의 한계

Double은 대부분의 프로그래밍 언어에서 기본 실수형으로 사용됩니다.

 

이는 64비트 IEEE 754 표준을 따르며, 빠른 연산 속도와 넓은 범위의 숫자를 처리할 수 있는 장점이 있습니다.

하지만 정확도 면에서는 한계가 있습니다.

public class DoubleExample {
    public static void main(String[] args) {
        double a = 0.1;
        double b = 0.2;
        double c = a + b;

        System.out.println("0.1 + 0.2 = " + c);
    }
}

출력 결과.

0.1 + 0.2 = 0.30000000000000004

왜 이런 일이 발생할까요?

  • Double은 2진수 기반의 부동소수점 표현 방식을 사용합니다.
  • 10진수 0.1과 0.2를 정확히 표현할 수 없기 때문에, 이진수로 변환될 때 근삿값으로 저장됩니다.
  • 이로 인해 연산 결과에도 미세한 오차가 발생합니다.

2. Float 사용: 더 작은 범위, 더 큰 오차

Float는 Double보다 작은 32비트 부동소수점 자료형입니다.

메모리 사용량이 적고 연산 속도가 빠르지만, 표현할 수 있는 유효 숫자 범위가 좁습니다.

public class FloatExample {
    public static void main(String[] args) {
        float a = 0.1f;
        float b = 0.2f;
        float c = a + b;

        System.out.println("0.1f + 0.2f = " + c);
    }
}

출력 결과.

0.1f + 0.2f = 0.3

겉보기에는 정확해 보이지만, 실제로는 내부에서 근삿값을 저장하고 있어 더 작은 숫자 범위에서 더 큰 오차가 발생할 가능성이 높습니다.

Float의 주요 특징

  • 적합한 경우: 높은 정밀도가 요구되지 않는 경우 (예: 게임 개발, 그래픽 연산).
  • 부적합한 경우: 금융 계산이나 통계 연산 등 오차가 치명적인 경우.

3. BigDecimal 사용: 실수 연산의 정밀도 보장

BigDecimal은 Java에서 제공하는 클래스 중 하나로, 정확한 실수 연산을 보장하기 위해 사용됩니다.

이 클래스는 실수를 10진법 기반으로 처리하므로, 부동소수점의 근삿값 문제를 해결할 수 있습니다.

import java.math.BigDecimal;

public class BigDecimalExample {
    public static void main(String[] args) {
        BigDecimal a = new BigDecimal("0.1");
        BigDecimal b = new BigDecimal("0.2");
        BigDecimal c = a.add(b);

        System.out.println("0.1 + 0.2 = " + c);
    }
}

출력 결과.

0.1 + 0.2 = 0.3

중요 사항

  • new BigDecimal(double)로 생성할 경우 부동 소수점의 오차가 그대로 발생할 위험성이 존재합니다.
  • "scale()"메서드로 소수 자릿수를 설정하여 계산 결과를 정리할 수 있습니다.

그럼 뭘 사용해야 할까?

실수 연산은 문제 되는 상황을 인지하고 있지 않다면 쉽게 간과할 수 있는 중요한 이슈입니다.

 

특히 환율, 이율 등과 같이 금전적인 부분이 연결되어 있다면 이는 서비스의 신뢰도를 낮추게 되는 매우 크리티컬 한 문제가 될 수 있습니다.

 

그렇기 때문에 위에 주어진 Double, Float, BigDecimal들을 잘 활용하여 적재적소에 사용해야 하는 것은 중요한 기본이라 생각합니다.

728x90

'Java' 카테고리의 다른 글

[ThreadLocal] Thread영역에 데이터를 저장하고 싶어요  (1) 2025.01.27
[Java] Static  (3) 2024.12.26
[ Basic ] I/O Stream이란?  (0) 2024.12.23
[JPA] 비관적 락 낙관적 락  (0) 2024.06.29
JPA는 어떤 기술일까?  (0) 2024.04.12
728x90

1. Static이란??

Static 키워드를 선언하게 되면 사용할 때만 메모리에 할당하고, 사용 안 할 때는 제거되는 일반 선언들과 달리 프로그램이 종료될 때 까지 메모리에 할당되어 있는 것을 의미합니다.

 

그렇기 때문에 우리가 Static으로 선언한 것들은 따로 생성하지 않고 가져다 사용할 수 있는 것입니다.

 

그렇다면 우리가 흔히 사용하는 Static 변수와 Static 메소드들은 어떤 작동원리를 가지고, 어떤 차이점이 존재하는지 알아보도록 하겠습니다.


2. Static 변수

Static 변수는 클래스 수준에서 선언되며, 인스턴스와 관계없이 모든 객체가 동일한 메모리 공간을 공유합니다. Static 변수는 클래스가 메모리에 로드될 때 한 번만 초기화되며, 프로그램이 종료될 때까지 메모리에 유지됩니다.

Static 변수의 특징

1. 공유 메모리
Static 변수는 클래스 로더가 클래스를 로드할 때 메모리에 할당되며, 해당 클래스의 모든 인스턴스가 이 변수를 공유합니다. 따라서 인스턴스마다 별도의 값을 가지지 않고, 모든 인스턴스가 동일한 값을 참조합니다.

 

2. 클래스 이름으로 접근 가능
Static 변수는 객체를 생성하지 않고, 클래스 이름을 통해 직접 접근할 수 있습니다.

 

3. 메모리 효율성
여러 객체에서 공통으로 사용하는 데이터를 Static 변수로 선언하면, 각 객체가 별도의 메모리를 할당받지 않으므로 메모리 효율성을 높일 수 있습니다.

 

4. 초기화 시점
Static 변수는 프로그램 실행 시점에 클래스가 메모리에 로드될 때 초기화됩니다. 한 번만 초기화되며, 이후 변경된 값은 프로그램 종료 시점까지 유지됩니다.


Static 변수 사용 사례

1. 공유 데이터 관리
객체생성과 관계없이 데이터를 공유하고 싶다면 Static변수를 사용하여 관리할 수 있습니다.

 

2. 상수 선언

변하지 않는 상수 값을 정의할 때 Static 변수를 final 키워드와 함께 사용하여 메모리 낭비를 줄일 수 있습니다.

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable {
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
    static final int MAXIMUM_CAPACITY = 1 << 30;
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
}


3. 유틸리티 클래스에서 공용 변수 사용
특정 설정 값이나 공용 데이터를 유틸리티 클래스에 Static 변수로 선언해 다른 클래스에서 쉽게 접근하도록 합니다.

public final class StandardCharsets {
    private StandardCharsets() {
        throw new AssertionError("No java.nio.charset.StandardCharsets instances for you!");
    }
    public static final Charset US_ASCII = sun.nio.cs.US_ASCII.INSTANCE;
    public static final Charset ISO_8859_1 = sun.nio.cs.ISO_8859_1.INSTANCE;
    public static final Charset UTF_8 = sun.nio.cs.UTF_8.INSTANCE;
    public static final Charset UTF_16BE = new sun.nio.cs.UTF_16BE();
    public static final Charset UTF_16LE = new sun.nio.cs.UTF_16LE();
    public static final Charset UTF_16 = new sun.nio.cs.UTF_16();
}

3. Static 메소드

Static 메소드는 객체를 생성하지 않고도 호출할 수 있는 클래스 수준의 메소드입니다. Static 변수와는 다르게 메소드는 로직을 정의하며, Static 키워드로 선언된 메소드는 클래스의 모든 객체가 동일한 동작을 공유합니다.

Static 메소드의 특징

1. 객체 없이 호출 가능
Static 메소드는 클래스 이름으로 호출할 수 있습니다.

 

2. Static 변수와 연동 가능
Static 메소드는 같은 클래스에 선언된 Static 변수에 직접 접근할 수 있습니다.

 

3. 인스턴스 멤버 사용 불가
Static 메소드 내에서는 클래스의 인스턴스 변수나 인스턴스 메소드를 직접 사용할 수 없습니다. 이는 Static 메소드가 클래스 수준에서 동작하기 때문입니다. 인스턴스 멤버를 사용하려면 해당 메소드에서 객체를 생성하거나 참조를 전달받아야 합니다.

 

4. 상속과 재정의 제한
Static 메소드는 상속받은 클래스에서 오버라이드(재정의)할 수 없습니다. 하지만 동일한 이름으로 정의해 숨길 수는 있습니다.


Static 메소드 사용 사례

1. 유틸리티 메소드 제공
자주 사용되는 공용 로직들을 Static 메소드로 정의해 간편하게 호출할 수 있습니다.

 

2. 팩토리 메소드 구현
특정 객체를 생성하는 로직을 Static 메소드로 제공할 수 있습니다.

 

3. 프로그램의 진입점
Java 프로그램은 main 메소드를 Static으로 선언하여, 객체를 생성하지 않고 프로그램의 실행을 시작합니다.

public class Main {
    public static void main(String[] args) {
        System.out.println("Hello World");
    }
}

 

728x90
728x90

I/O 스트림이란?

I/O 스트림은 Java에서 데이터를 입력(Input)하거나 출력(Output)할 때 사용하는 추상화된 모델입니다.

  • InputStream: 데이터를 읽어오는 데 사용.
  • OutputStream: 데이터를 외부로 쓰는 데 사용.

특징

  • 데이터의 흐름을 Stream으로 간주.
  • Byte 단위 또는 Character 단위로 처리.
  • 데이터 소스: 파일, 네트워크 소켓, 메모리 등.

💡 "Java의 I/O는 Stream 기반이다. 데이터를 한 번에 처리하지 않고, 스트림으로 데이터를 흘려보내면서 효율적으로 작업한다."


InputStream과 OutputStream의 기본 구조

InputStream의 주요 메서드

 
int read() throws IOException  
int read(byte[] b, int off, int len) throws IOException  
void close() throws IOException

OutputStream의 주요 메서드

void write(int b) throws IOException  
void write(byte[] b, int off, int len) throws IOException  
void close() throws IOException
 

주요 I/O 클래스와 사용 사례

파일 읽기: FileInputStream

 
import java.io.FileInputStream;
import java.io.IOException;

public class FileInputExample {
    public static void main(String[] args) {
        try (FileInputStream fis = new FileInputStream("example.txt")) {
            int data;
            while ((data = fis.read()) != -1) {
                System.out.print((char) data); // 데이터를 문자로 변환해 출력
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

파일 쓰기: FileOutputStream

import java.io.FileOutputStream;
import java.io.IOException;

public class FileOutputExample {
    public static void main(String[] args) {
        try (FileOutputStream fos = new FileOutputStream("example.txt")) {
            String data = "Java I/O Stream 예제입니다.";
            fos.write(data.getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

🔑 TIP: try-with-resources를 사용하면 스트림을 자동으로 닫아 메모리 누수를 방지할 수 있습니다.


Buffered 스트림과 성능 향상

BufferedInputStream 사용 예

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;

public class BufferedInputExample {
    public static void main(String[] args) {
        try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream("example.txt"))) {
            int data;
            while ((data = bis.read()) != -1) {
                System.out.print((char) data);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

왜 Buffered 스트림을 사용할까?

  • 기본 스트림은 데이터를 1바이트씩 처리.
  • Buffered 스트림은 버퍼를 사용해 데이터 블록을 한꺼번에 처리 -> 성능 향상.
728x90
728x90

JPA를 사용하여 데이터베이스에 락을 거는 방법은 2가지방법을 뽑아 볼 수 있습니다.

 

테이블에 행하는 행위 ( 조회, 수정, 등록, 삭제 )를 막는 비관적 락과 데이터에 버전을 명시하고 해당 버전을 통해 데이터의 일관성을 보장하는 낙관적 락이 이 경우입니다.

 

비관적 락  - Pessimistic Lock

비관적락은 데이터를 조회하고 특정 작업을 할 때 테이블에 어떤 작업도 일어나면 안되는 상황에서 사용하기 적합하다고 볼 수 있습니다.

이는 데이터의 정확성과 일관성을 보장하는 방법이며, 이 방법은 테이블에 락을 거는 행위로 성능에 큰 영향을 끼칠 수 있습니다.

 

데이터베이스의 종류에 따라 락을 걸었으나 데이터가 조회되는 경우도 있으니 이는 어떤 데이터베이스를 사용하는지를 확인을 하고 잘 선택하기 바랍니다.

public interface LectureJpaRepository extends JpaRepository<LectureEntity, Long> {
    //
    @Lock(LockModeType.PESSIMISTIC_WRITE)
    @Query("select l from LectureEntity l where l.id = :id")
    Optional<LectureEntity> findByIdForUpdate(long id);
}

사용법은 위와 같이 조회할 때 @Lock 어노테이션을 통해 Lock을 시작하는 Start point를 설정해줍니다.

 

LockkModeType에는 아래와 같은 옵션들을 선택하여 적용할 수 있습니다.

LockModeType Action
READ Entity의 버전을 확인합니다. - jpa1.0 버전 호환
WRITE Entity가 변경이 되지 않더라도 버전을 자동으로 올려줍니다. - jpa1.0 버전 호환
OPTIMISTIC thread가 종료될 때 Entity의 버전을 확인합니다.
OPTIMISTIC_FORCE_INCREMENT Entity가 변경이 되지 않더라도 버전을 자동으로 올려줍니다.
PESSIMISTIC_READ 데이터베이스가 지원하면 비관적락을, 지원하지 않다면 명시적 락으로 통해 읽기는 가능하지만 CUD는 할 수 없어집니다.
PESSIMISTIC_WRITE 해당 쓰레드를 제외 나머지 모든 쓰레드는 lock이 해제될때까지 Block됩니다.
PESSIMISTIC_FORCE_INCREMENT 비관적으로 잠기고, Entity가 변경되지 않더라도 버전이 자동으로 오릅니다.
NONE 잠금을 걸지 않습니다.

 

public class LectureService {

    @Transactional
    public Lecture loadLecture(long lectureId) {
		// 조회 -  Lock시작
        // 작업들
        //종료 - Transaction이 종료될 때 Lock을 풉니다
    }

}

 

비관적 락의 경우 Transaction내에서 유지되며, 해당 영역 내에서 작업을 끝내야 데이터의 일관성을 유지할 수 있습니다.

너무 많은 처리 및 Transaction전파에 의해 Lock이 길어질 수 있어 주의가 필요합니다.

낙관적 락  - Optimistic Lock

Version데이터를 통해 데이터의 일관성을 보장하는 방법입니다.

 

비관적락과 달리 테이블을 잠그거나 하지는 않지만, 버전이 다를 경우 데이터 수정작업 등을 처리하지 않는 것으로 동시성을 처리하였습니다.

 

비관적락에 비해 사용하기 쉽다는 장점이 있으며, 테이블을 잠그지 않기 때문에 성능적인 측면에서 더욱 효율적으로 관리할 수 있는 방법입니다.

단점으로는 버전을 통해 데이터의 일관성을 유지하기 때문에 동시에 여러번의 요청이 들어올 경우 많은 데드락이 발생 할 수 있습니다.

 

아래와 같은 방법으로 Entity에  @Version어노테이션을 사용함으로써 간단하게 명시 할 수 있습니다.

@Entity
public class LectureEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    @Version
    private int version
}

 

버전의 타입으로는 int, Long, TimeStemp와 같은 자료를 사용할 수 있으며,  TimeStemp의 경우 데이터의 일관성을 보장하기에는 int와 Long타입에 비해 부적절하지만, 상황에 따라 사용하는 경우가 존재한다고 합니다.


데이터베이스에서 락을 거는 만큼 무분별한 락사용과 제대로 된 설계가 아닐 경우 큰 성능적인 이슈를 야기할 수 있습니다.

 

이를 염두하고 적절한 락을 사용하는 것을 권장합니다.

또한 이 블로그글은 Hibernate구현체, Mysql기준으로 작성된 만큼 다른 데이터베이스, 또한 다른 기능들과 같이 사용할 경우 위에 소개한 상황과 다른 상황들이 발생할 수 있으니 확인해보시기 바랍니다.

 

Hibernate공식문서: https://docs.jboss.org/hibernate/orm/5.4/userguide/html_single/Hibernate_User_Guide.html#locking

728x90

'Java' 카테고리의 다른 글

[ThreadLocal] Thread영역에 데이터를 저장하고 싶어요  (1) 2025.01.27
오차 없는 실수 연산 어떻게 해야 할까?  (0) 2025.01.10
[Java] Static  (3) 2024.12.26
[ Basic ] I/O Stream이란?  (0) 2024.12.23
JPA는 어떤 기술일까?  (0) 2024.04.12
728x90

JPA에 대하여

 

우리가 JPA를 알기 위해서는 사전 지식으로 ORM이 무엇인지를 알아야 합니다.

이에 이 글은 ORM부터 알아본 후 JPA에 대해 다뤄보도록 하겠습니다.

ORM이란?

ORM( Object Ralational Mapping )은 단어를 풀어 해석하면 '객체 관계형 연결'이 됩니다.

이 기술은 애플리케이션과 데이터베이스 연결 시 기존에는 SQL언어를 애플리케이션 서버에서 직접 작성하였지만, 이를 서버에서는 객체로 정의하여 행위에 대한 Action을 하면  정의된 객체를 해석하여 행위에 필요한 SQL문을 작성하여 데이터베이스로 전달하는 말 그대로의 Mapping역할을 합니다.

 

이러한 ORM은 기존 Mybatis와 같은 기술을 사용하던 것을 '객체 지향'적으로 사용하기 위해 나온 기술이라고 봐도 무방 할 것이라 생각하는데, 어떤 탄생 배경이 있는지 알아보도록 하겠습니다.

ORM 탄생 배경

우리가 개발할 때 객체 지향 언어를 사용하여 개발을 하고, 관계형 데이터베이스를 사용하게 되면 프로그램 내에서 데이터베이스와 연결하여 SQL문을 직접 작성을 해줘 데이터를 얻거나 작업을 할 수 있습니다.

 

이때 우리는 객체 지향 언어의 데이터 표현 방법관계형 데이터베이스의 데이터 표현 방법의 차이를 확인해 볼 필요가 있습니다.

 

객체 지향 언어은 데이터를 객체에 상호 연결된 그래프로 표현을 하고, 관계형 데이터베이스는 데이터를 표형식으로 표현을 하게 됩니다.

이렇게 표현 방식이 다른 두 개념을 한 프로그램에서 사용을 하려면 표형식 표현방법을 객체에 상호 연결된 그래프 방식으로 변경하는 작업을 하게 되는데, 이는 개발자에게 번거로움과 오류 발생의 원인을 제공하게 됩니다.

 

이리하여 관계형 데이터베이스와 연결하여 SQL문을 작성하는 부분을 추상화시켜버리게 되는데 이것이 ORM입니다.

ORM의 장단점

위 설명만 보면 ORM을 만능으로 볼 수도 있습니다.

개발자의 번거로움을 줄여주고!! 오류 발생의 원인을 없애 주다니!! 하고 말입니다.

하지만 대부분의 기술이 그렇듯 장단점이 존재합니다.

장점

  • 사용하기 편하다.  - 강점 -
  • 직접적인 SQL문 작성이 없는 만큼 신경써야하는 부분이 줄어든다.
  • 객체를 정의하여 사용하고 얼마든지 수정 및 확장이 쉬워 유지보수가 편하다.

단점

  • ORM만 사용하여서는 복잡한 작업( 조회 혹은 수정 등 )을 대응하기 힘들다.
  • 객체 지향적이라 객체로 관리하게 되었다고 설계를 신경써서 하지 않으면 어떤 프로그램보다 복잡하고 힘든 프로그램이 만들어진다.

데이터 베이스를 객체 지향적으로 접근할 수 있는 만큼 설계의 중요성은 이루 말할 수 없을 정도이며,

사용하다 보면 그냥 SQL사용해서 개발하고 싶다라는 생각이 들 수도 있습니다.

다만 적절히 비즈니스 로직을 나눠 분석하여 사용할 경우보다 쉽게 개발할 수 있는 기술인 것에는 틀림없다 생각합니다.


JPA이란?

JPA(Java Persistent API)는 말그대로 JAVA Application에서 '객체와 관계형 데이터베이스 간의 매핑을 위한 API' 인터페이스의 모음입니다.

이 모음에는 3가지 구현체가 존재하는데, Hibernate, EclipseLink, DataNucleus가 있습니다.

Hibernate

하이버네이트는 JDBC API를 사용하는 JPA인터페이스 구현체입니다.

이는 무슨 뜻이냐 우리가 객체로 데이터를 CRUD 하게 되면 JPA 즉 하이버네이트 내부에서는 해당 객체를 분석하여 데이터베이스로 SQL문을 보내주게 되는데 이때 사용하는 API가 JDBC API라는 것입니다.

EclipseLink

EclipseLink는 JPA인터페이스 구현체입니다.

또한 JAXB, SDO를 구현한 포괄적인 오픈소스 프레임워크로, 다양한 기능과 확장성을 강력하게 제공하고 있습니다.

DataNucleus

DataNucleus는 JPA 인터페이스 구현체입니다.

EclipseLink와 마찬가지로 다른 데이터베이스들 또한 지원하고 있습니다.

Hibernate를 왜 많이 사용하는가

글을 작성하면서 의문이 들었던 점이 왜 Hibernate는 많이 사용하거나 본 거 같은데, EclipseLink와 DataNucleus는 다루는 글을 많이 못 본 것 같다는 생각을 하였습니다. 그리고 설명들을 보면 Hibernate는 JPA 인터페이스를 구현한 가장 대표적인 오픈소스라고 하는데 EclipseLink와 DataNucleus 또한 강력한 기능과 확장 가능성을 지원해 준 것을 확인할 수 있었습니다.

 

우선 찾아보며 생각한 것은 Hibernate의 강력한 커뮤니티와 그로 인해 나온 많은 선례, 또한 만들어진 지 오래된 만큼 안정성을 보장하기 때문이라 생각합니다.

JPA주요 특성

ORM

엔티티 클래스와 데이터베이스 테이블 간의 매핑을 지원합니다.

영속성 컨텍스트

데이터를 영속성 컨텍스트에 저장하고 지속적으로 추적하게 되는데, 이 데이터가 수정될 경우 트랜젝션이 걸려있지 않으면 바로 반영이 됩니다. 이게 무슨 의미인가 만약 데이터를 조회하여 엔티티로 받아서 바로 엔티티 속성을 수정한다면 실제 SQL문이 바로 날아가 변경된다는 뜻입니다.

 

즉 commit을 안 했는데 commit이 되는 현상으로 이러한 상황을 방지하기 위해서는 Transaction처리를 해주거나, 엔티티를 사용하지 말고 데이터를 수정할 때 사용하는 객체를 따로 만들어 해당 객체로 데이터 값들만 복사하여 사용하는 것이 안전한 사용법인 것 같습니다.

캐싱

영속성 컨텍스트는 한번 조회된 데이터들은 가지고있는데, 만약 조회되는 데이터가 영속성 컨텍스트에 저장되어있는 데이터라면 데이터베이스를 조회하지 않고 해당 데이터를 반환해줍니다. 이는 1차 캐시의 개념이며, 만약 1차 캐시에 없을 경우 2차 캐시를 조회하게되고, 거기에도 없다면 데이터베이스를 조회하게 됩니다.

 

이때 말하는 1차 캐시는 Hibernate에서 제공하는 영속성 컨텍스트 캐시이고, 2차 캐시는 Session Factory 캐시입니다.


Reference

 

Understanding EclipseLink

This chapter describes how to set up your JPA applications to work with a non-relational data source. There are many types of non-relational data sources. These include document databases, key-value stores, and various other non-standard databases, such as

eclipse.dev

 

 

JPA Getting Started Guide (v5.2)

Developing applications is, in general, a complicated task, involving many components. Developing all of these components can be very time consuming. The Java Persistence API (JPA) was designed to alleviate some of this time spent, providing an API to allo

www.datanucleus.org

 

 

What is Object/Relational Mapping? - Hibernate ORM

Idiomatic persistence for Java and relational databases.

hibernate.org

 

캐싱 참조자료: https://www.baeldung.com/hibernate-second-level-cache

 

728x90

+ Recent posts