Tech Interview - 자바, 스프링

wintee·2025년 6월 29일

※ 질문 출처: https://github.com/VSFe/Tech-Interview

1. JVM

JVM은 Java Virtual Machine의 줄임말로 자바 프로그램을 실행하기 위한 가상 실행 환경. 자바 소스코드를 컴파일하면 .class 파일(바이트코드)가 생성되는데, 이를 실행하는 것이 JVM.

자바는 JVM 위에서 실행되기 때문에 플랫폼 독립적이다. 플랫폼 독립적이지 않은 언어의 경우 다른 플랫폼에서의 실행 결과가 달라질 수 있다. 다만 성능은 다소 떨어진다.

JVM은 바이트코드 실행, 메모리 관리, 스레드 및 예외 관리를 수행하며 클래스 로더, 실행 엔진, 메모리 영역으로 구성되어 있음.

JVM은 자바 바이트코드만 있으면 실행할 수 있기 때문에 굳이 자바가 아니더라도 코틀린이나, Jython같이 다른 언어여도 실행시킬 수 있다. 반대로 자바 바이트코드를 JVM 위가 아닌 다른 환경에서 실행시키는 것은 일반적으로 어렵다.

JVM에서 우리의 코드를 실행하게 되면 OS 상에서는 JVM이라는 프로세스 1개만 있는 것으로 보이며, 우리의 프로그램은 JVM 내부의 스레드로 식별되게 됨. 만일 우리의 프로그램이 다른 스레드를 만들게 되면 이는 중첩된 구조가 아닌 여러 개의 스레드가 존재하는 것으로 식별된다.

메모리 영역

  • 메소드 영역
    • static 변수
    • 런타임 상수 풀
      • 상수, 메소드, 필드에 대한 레퍼런스를 저장
    • 메소드, 필드
    • 메소드 바이트 코드
    • 런타임 시 동적으로 할당한 객체를 저장
  • 스택
    • 메소드 호출 시 생성되는 지역변수, 매개변수, 연산 중 발생하는 임시 데이터 저장
  • PC 레지스터
    • 현재 실행 중인 JVM 주소를 지님
  • 네이티브 메소드 스택
    • 자바 외 언어로 작성된 네이티브 코드를 위한 메모리

2. final

자바에서 final 키워드는 어느 위치에 붙느냐에 따라 의미가 달라진다.

  • 변수에 사용
    • 해당 값은 상수라는 의미
    • 한 번 값을 할당하면 더 이상 변경 불가
    • 값 변경에 따른 실수를 방지
  • 메소드에 사용
    • 해당 메소드는 서브 클래스에서 오버라이딩을 할 수 없다는 의미
    • 메소드의 동작을 변경하지 못하게 보장함으로서 설계 안정성 상향
  • 클래스에 사용
    • 해당 클래스는 상속이 불가능하다는 의미
    • 클래스를 상속하지 못하게 보장함으로서 설계 의도를 명확히 표현

final 키워드가 붙은 변수, 메소드, 클래스는 값의 변경이나 오버라이드, 상속이 불가능하다는 점이 명확해지므로 상황에 따라 추가적인 컴파일 타임의 최적화를 기대할 수 있다.


3. 인터페이스와 추상 클래스

  • 인터페이스
    • 상속하는 클래스들이 공통적으로 수행해야하는 동작을 정의
    • 객체의 설계도, 명세로서 동작
    • implements 키워드로 상속(인터페이스가 다른 인터페이스를 상속한다면 extends 사용)
    • 다중 상속 가능
    • 기본적으로 추상 메소드로 구현되나 default 메소드를 통해 구현까지 포함 가능
    • 변수는 상수만 선언 가능
  • 추상 클래스
    • 공통된 속성과 동작을 묶어 재사용
    • extends 키워드로 상속
    • 코드 재사용이 주된 목적
    • 추상 메소드와 구체 메소드 모두 포함 가능
    • 변수는 상수, 변수 모두 포함 가능

인터페이스의 다중 상속

인터페이스는 다중 상속이 가능하지만 추상 클래스는 다중 상속을 할 수 없다. 이는 클래스를 다중 상속할 때 발생할 수 있는 문제가 있기 때문이다.

   A
  / \
 B   C
  \ /
   D

만일 다중 상속을 허용하고 위와 같은 구조로 다중 상속을 했다고 가정하자. D는 어떤 A 메소드를 사용해야할지 모호해진다. 따라서 이러한 모호성을 해결하기 위해 애초부터 다중 상속을 허용하지 않는 것이다.

인터페이스는 일반적으로 구현을 제공하지 않으므로 이러한 문제에서 자유롭다. Java8 이상에서는 default 메소드가 생겼기 때문에 동일한 문제가 생기지만 언어 레벨에서 반드시 명시적으로 해결하도록 안전 장치를 마련해두었다.

C++과 같은 다중 상속을 지원하는 언어의 경우 가상 상속(모호함이 발생할 경우 그냥 하나만 상속하게 처리)이라는 개념을 통해 다이아몬드 문제를 해결하고 있다.


4. 리플렉션

자바의 리플렉션은 메타 프로그래밍 기술로, 런타임에 클래스, 메소드, 필드 등의 정보를 동적으로 조사하고 조작할 수 있다.

리플렉션을 이용하면 클래스의 메소드, 필드, 생성자의 정보를 얻을 수 있고 동적으로 객체를 생성하거나 메소드를 호출할 수 있다.

다만 리플렉션은 다소 오버헤드가 큰 편이고 private 필드나 메소드까지 접근할 수 있으므로 객체지향적이지 않다.

뿐만 아니라 보안적인 문제가 다소 존재하는데, 만일 악성 코드가 리플렉션을 통해 private 메소드를 이용하여 의도치 않은 동작을 일으킬 수 있다. 따라서 리플렉션을 이용하는 코드는 외부 입력을 반영하지 않고 입력 검증을 수행하는 등 보안을 고려해야할 필요가 있다.

리플렉션은 동적으로 클래스 정보를 다룰 수 있으므로 일반적으로 프레임워크 레벨에서 자주 사용된다. 예를 들어 스프링에서는 객체 생성, 의존성 주입, 메소드 호출 등에서 리플렉션을 사용하여 처리한다.


5. static

클래스 멤버에 붙어 클래스 자체에 속한다는 의미를 부여하는 키워드. 객체마다 개별적으로 생성되는 것이 아닌 클래스 전체에 공용으로 단 하나가 존재하게 됨(메모리 절약).

static 멤버의 경우 인스턴스 없이 클래스명. 으로 접근이 가능하다. 클래스에서 공유하는 멤버이므로 인스턴스 변수와 인스턴스 메소드에는 접근이 불가능하다. 반드시 같은 static 변수나 메소드에만 접근이 가능.

공통 데이터를 저장, 유틸리티 함수 생성, 중첩 클래스 구현 등에서 사용한다.

static class

중첩 클래스 중 하나로 외부 클래스의 인스턴스와는 독립적으로 존재하는 클래스를 의미. 외부 클래스와 논리적으로는 연관되어 있으나 독립적으로 사용 가능해야할 때 사용.

중첩 클래스는 외부 클래스 인스턴스가 있어야 하지만 static 내부 클래스는 그렇지 않음


6. Exception

프로그램 실행 중 비정상적인 상황이 발생했을 때 처리하기 위해 사용되는 객체 기반의 오류 처리 메커니즘

예외 상황을 Exception 객체로 표현하고 이 객체를 throw하여 예외 발생을 알림.

컴파일 전 반드시 처리가 필요한 Checked Exception과 처리할 필요 없는 Unchecked Exception으로 나뉨. 만일 복구하지 못할 예외라면 Unchecked Exception으로 포장한 뒤 로그를 남기는 방식을 자주 사용.

예외 처리는 Throwable 객체를 생성하며 스택 트레이스를 추적하기 때문에 생성 비용이 높음 따라서 예외 흐름 처리 비용은 높으며 꼭 필요한 부분에만 사용해야 함

예외처리 방법

  • try-catch
    • 예외가 발생할 수 있는 코드를 try 블록에 작성하고 예외 발생 시 처리할 코드를 catch 블록에 작성
    • finally 블록을 이용해 예외 처리 여부와 상관없는 코드를 작성할 수도 있음
    • 여러 개의 catch 블록으로 서로 다른 예외를 구분하여 처리 가능
  • throw
    • 메소드 선언 부에 throws 키워드를 사용해 해당 메소드의 잠재적 예외 발생 가능성을 알림
    • 해당 메소드를 호출하는 쪽에서 예외를 처리

7. Synchronized

자바에서 동기화를 구현하기 위해 사용되는 모니터 락. 지정한 임계 영역에 한 번에 하나의 스레드만 실행할 수 있도록 보장

  • 메소드 동기화
    • 메소드 전체가 임계 영역
    • 해당 메소드를 실행하는 동안 해당 객체에 대한 모니터 락을 획득해야 함
    • 다른 스레드는 모니터가 반환될 때까지 대기
    • 정적 메소드에 사용할 경우 해당 클래스의 객체를 모니터 락의 대상으로 사용
  • 블록 동기화
    • 메소드 전체가 아닌 일부 코드 블록만 동기화
    • synchronized(객체)로 특정 객체의 모니터 락 획득

너무 잦은 synchronized는 멀티스레드의 동시성 이점을 떨어뜨림. 락을 획득하기 전의 스레드는 무한정 대기를 하게 되며 락을 획득하게 될 스레드는 무작위적이므로 기아 상태가 빠지는 스레드가 생길 수 있음.

다른 동기화 기법

  • ReetrantLock
    • 락 획득 시도, 타임아웃, 인터럽트 처리 가능, 공정성 옵션 조절 가능
    • 더 빠르게 유연
  • ReadWriteLock
    • 동시에 여러 스레드가 읽기가 가능하도록 하되, 쓰기 작업은 단독 실행
    • 읽기 작업이 많고 쓰기 작업이 적을 때 효과적
  • Atomic
    • 락을 사용하지 않고 원자적 연산을 사용하여 동기화
    • 락보다 경량이며 고성능
  • Concurrent Collection
    • 자료구조 레벨에서 효율적인 동시성 제어 제공

Thread Local

멀티스레드 환경에서 스레드 별로 독립적인 변수를 저장하는 클래스. 스레드 자신을 Key로 하는 Map에 데이터를 보관. get이나 set 명령어를 호출하면 자기 자신을 key로 하여 ThreadLocalMap을 찾고 해당 장소에서 원하는 값을 get하고 set 하는 방식


8. Stream

컬렉션을 함수형 스타일로 처리할 수 있도록 해주는 API. 선언적 방식으로 데이터를 다룰 수 있게 되며 간결하고 가독성 높은 코드 작성이 가능.

데이터 소스를 추상화하여 연속적인 데이터 흐름으로 처리하며 원본 데이터는 변경하지 않음

Stream의 특징은 아래와 같다.

  • 파이프라인 연산
    • 여러 연산(map, filter 등)을 연결해 데이터 흐름을 구성
  • 내부 반복 처리
    • 개발자가 루프를 작성할 필요 없이 내부적으로 수행
  • 지연 연산
    • 최종 연산이 호출되기 전까지 실제로 값을 연산하지 않음
  • 병렬 처리 지원

스트림 생성 -> 중간 연산(filter, map, sorted) -> 최종 연산(collect, forEach, reduce)의 순서로 동작

스트림은 일반적으로 전통적인 for 루프에 비해 더 느림(내부적으로 처리되는 것들이 존재하기 때문)

스트림을 사용할 때 람다식을 사용하게 되는데 람다식으로 들어오는 값은 final이거나, final처럼(정확히 말하자면 초기화 이후 값의 변경이 없음) 동작해야함. 그렇지 않으면 매 실행마다 다른 결과가 도출될 수 있으며 이 경우 문제의 소지가 있기 때문.


9. GC

더 이상 사용되지 않는 객체를 자동으로 탐지해 메모리에서 해제하는 모듈.

자바에서 모든 객체는 new 키워드를 통해 힙에 할당됨. 개발자가 코드상에서 더 이상 참조하지 않는 객체가 힙에 남아있다면 메모리 누수 발생. GC가 이를 검사하여 메모리를 회수함

자바에서 GC는 Mark & Sweep 방식으로 동작함. Mark 과정을 통해 모든 객체를 스캔하고 현재 참조되고 있는 개체를 표시함. 이후 표시되지 않은 객체를 메모리에서 제거.

JVM의 힙 영역은 young과 old 영역으로 나뉘어져있음. 대부분의 객체는 금방 쓰레기가 된다는 의도로, 대다수의 GC는 young 영역에 있는 젊은 인스턴스들을 대상으로 수행하고 해당 영역에서 오래 살아남았다면 old 영역으로 이동시킴.

young 영역에서 발생하는 GC는 minor GC, old 영역에서 발생하는 GC를 major GC라고 칭함.

young 영역은 다시 한 번 3가지 영역으로 나뉨. Eden 영역은 완전 새로 할당된 객체가 위치함. minor GC가 동작하면 survivor0 혹은 survivor1로 이동. survivor0, survivor1은 최소 1번 이상 GC에서 살아남은 객체가 존재하며 반드시 둘 중 하나는 비어있게 됨

GC의 알고리즘은 아래와 같음

  • Serial GC
    • 단일 스레드로 stop-the-world 상태에서 수행
  • Parallel GC
    • 여러 스레드로 병렬 처리하며 stop-the-world가 길지 않도록 개선
  • CMS GC
    • Mark & Sweep 단계를 애플리케이션과 병행하여 수행
  • G1 GC
    • 힙을 region 단위로 나누고(young, old 영역은 버림) 우선순위가 높은 영역부터 회수
    • region 영역의 역할은 동적으로 Eden, Survivor, Old가 부여됨
    • G1 GC에서 우선순위가 높은 것은 메모리가 많이 차있는 영역임. 즉 전체 메모리를 탐색하지 않고 region 별로 탐색하여 GC가 발생

10. equals, hashcode

  • equals
    • 두 객체의 동등성을 비교(동일성의 경우 ==로 비교)
    • 기본적으로는 참조 주소를 비교함, 하지만 동등성을 비교한다는 특성상 보통 오버라이드하여 객체의 내용을 비교하도록 수정
  • hashCode
    • 객체를 식별하는 해시코드를 반환
    • 해시 기반 컬렉션에서 사용됨

두 메소드는 매우 긴말한 연관 관계가 있다. 만일 두 객체가 equals로 같다고 판단될 경우 hashCode도 반드시 같아야 한다. 이를 만족시키지 못한다면 동등한 두 객체가 해시 기반 컬렉션에서는 다른 객체로 취급 된다.

다만 해시 충돌이 발생할 수 있으므로 해시 코드가 같다몬 equals가 참인 것은 아니다.


11. IoC, DI

  • IoC
    • 제어의 역전
    • 제어의 흐름을 개발자가 제어하는 것이 아닌 프레임워크가 제어
    • 본래 객체의 생성과 의존 관계 연결을 개발자가 직접 수행하였음
    • 스프링의 경우 이러한 작업을 스프링이 자동으로 수행 -> 제어의 역전
  • DI
    • 의존성 주입
    • 객체가 필요로 하는 의존 객체를 외부에서 주입
    • IoC를 실현하는 대표적인 방법 중 하나(의존성 조회, DL 이라는 방법도 존재. ㅎ지만 DI가 더 일반적)
    • 객체 간의 결합도를 낮춤

Bean 라이프사이클

    1. Bean 인스턴스 생성
    1. 의존성 주입
    1. Bean 초기화
    1. Bean 사용
    1. Bean 소멸

프로토타입 Bean

Bean은 원래 싱글톤 패턴이 적용됨. @Scope("prototype") 어노테이션을 달아주면 프로토타입 스코프가 되며 요청할 때마다 새로운 인스턴스가 생성됨

스프링 컨테이너의 관리를 받으면서 매번 새로운 객체가 필요할 때 사용

싱글톤 빈에서 프로토타입 빈을 주입받으면 프로토타입 빈이 싱글톤처럼 동작할 수 있으므로 주의 필요


12. AOP

AOP는 관점 지향 프로그램으로 핵심 로직과 별개로 존재하지만 여러 코드에 나타나는 횡단 관심사를 분리하여 코드의 재사용성을 높이는 방법

로깅, 트랜잭션 관리와 같은 코드는 비즈니스 로직과 무방하게 여러 코드에 동시다발적으로 나타나지만 꼭 필요하기는 한 기능들. 이러한 기능을 횡단 관심사로 식별하고 재사용함

주요 구성 요소

  • 애스펙트
    • 모듈화된 횡단 관심사
  • 조인 포인트
    • 애스팩트가 적용될 수 있는 지점
    • 메소드 호출 시점, 객체 생성 시점 등
  • 포인트컷
    • 조인 포인트 중에서 애스펙트가 실제로 적용될 지점을 표현하는 표현식
  • 어드바이스
    • 애스팩트가 언제 수행될지를 결정하고 실제 애스펙트를 실행
    • Before, After, Around 등

동작 원리

Spring은 런타임 시 @Aspect가 붙은 클래스를 감지하고 포인트컷 대상 객체의 프록시를 생성

JDK 다이나믹 프록시 혹은 CGLib을 이용하여 프록시를 생성하는데, JDK의 경우 인터페이스만 있어야 동작 가능하기 때문에 바이트코드를 조작하여 상속하는 CGLib을 스프링에서 사용

클라이언트에서 프록시를 호출하면 설정된 어드바이스를 참고하여 지정된 애스펙트가 적절한 시기에 실행


13. Filter, Interceptor

  • Filter
    • 서블릿에서 제공하는 표준 기능
    • DispatcherServlet 앞에서 작동
    • 스프링 MVC와 무관하게 서블릿 컨테이너 레벨에서 요청을 처리
  • Interceptor
    • Spring MVC에서 제공하는 기능
    • DispatcherServlet 뒤에서 작동
    • Spring MVC 흐름에 맞춰 요청 처리 가능

스프링에서는 스프링 컨테이너 내부에서 동작하여 스프링의 지원을 받을 수 있는 인터셉터의 사용을 권장, 하지만 인증, 인가의 경우 Spring 내부에 요청이 도달하기 이전에 처리할 필요가 있으므로 Filter로 처리함

정적 리소스 요청의 경우에도 매핑된 컨트롤러가 없으므로 해당 요청에 대한 처리가 필요하다면 필터 레벨에서 처리해야함

이 외에도 요청이 Spring MVC에 도달하기 이전에 처리할 필요가 있다면 필터를 사용


14. DispatcherServlet

DisPatcherServlet은 스프링 내부에서 중앙 요청 처리자 역할을 수행. Front Controller 패턴을 구현하여 서버로 들어오는 모든 HTTP 요청을 가로채서 처리.

디스패처 서블릿은 싱글톤으로 동작하지만 Thread-safe하게 설계되어 있기 때문에 멀티스레드 요청들을 모두 처리할 수 있음

스프링에서 HTTP 요청 흐름

  • 클라이언트가 요청
  • 요청은 필터를 거쳐 DispatcherServlet에 도달
  • HandlerMapping을 통해 해당 요청을 어떤 컨트롤러가 처리할 수 있는지 찾음
  • 해당 컨트롤러에 대응하는 Handler Adapter를 통해 해당 컨트롤러의 메소드를 호출
  • 컨트롤러에서 작업을 처리하고 값을 반환
  • HttpMessageConverter가 동작하여 객체를 JSON으로 변환
  • HTTP ResponseBody를 통해 JSON 형태로 클라이언트에 전달

Controller 찾기

  1. 스프링 컨테이너가 실행되면 HandlerMapping이 @RequestMapping, @GetMapping 등 URL 패턴이 등록된 어노테이션을 모두 매핑 테이블로 만들어 미리 저장해둠

  2. DispathcerServlet에 요청이 들어오면 요청 URL을 이용하여 HandlerMapping의 매핑 테이블을 이용하여 어떤 Controller로 이동해야하는지를 찾음


15. JPA

JPA는 자바 진영에서 사용하는 ORM 기술 표준을 의미. 일반적으로 해당 기술을 구현한 Hibernate를 사용

데이터베이스는 객체지향의 개념이 없음. 상속과 같은 개념이 없어 이를 DB에서 구현하려면 많은 코드가 필요 -> JPA는 상속과 관련된 패러다임 불일치를 개발자 대신 해결

객체는 참조를 사용하여 연관관계를 맺지만 테이블은 FK를 통해 연관 관계를 맺음. DB의 특정을 살려 단순히 엔티티에 FK를 저장할 경우 객체지향 프로그래밍의 특징이 소멸(FK은 객체가 아닌 단순한 값이니) -> JPA는 연관관계와 관련된 패러다임 불일치도 해결

즉 자바를 이용하여 객체지향적으로 개발할 수록 DB 관련 코드는 객체지향적이지 않으므로 패러다임적인 불일치로 인한 많은 시간과 코드가 할애되고 이를 해결하기 위해 JPA를 사용함

영속성 컨텍스트

엔티티의 생명 주기를 관리하는 저장소. 엔티티 매니저가 생성될 때 내부에 자동 생성하며 식별자 값으로 구분.

트랜잭션을 커밋하는 순간 영속성 컨텍스트에 저장된 엔티티를 DB에 반영함.

영속성 컨텍스트 사용의 장점은 아래와 같음

  • 동일성 보장
    • 영속성 컨텍스트는 내부에 캐시를 가지며 이를 1차 캐시라고 함
    • 영속 상태의 엔티티를 저장하며 엔티티 조회 시 캐시에 존재한다면 캐시에서 가져오게 됨
    • 영속 엔티티의 동일성을 보장하므로 Repeatable Read 수준의 격리 수준을 애플리케이션 레벨에서 제공
  • 트랜잭션을 지원하는 쓰기 지연
    • 커밋 직전까지 내부 쿼리 저장소에 SQL을 저장
    • SQL 문은 커밋 전까지 아무 의미 없으니 커밋하는 시점에 SQL문을 실행
  • 변경 감지
    • JPA는 update 메소드가 존재하지 않음
    • 엔티티를 영속성 컨텍스트에 보관할 때 스냅샷을 찍어두고 변경된 엔티티가 있다면 수정 쿼리를 자동으로 생성(단, 영속 상태의 엔티티에게만 동작)

엔티티의 생명 주기는 아래와 같음

  • 비영속
    • 영속성 컨텍스트와 관계없음
    • persist() 호출 전
  • 영속
    • 영속성 컨텍스트에 저장된 상태
    • persist() 호출 후
  • 준영속
    • 영속성 컨텍스트에 저장되었다 분리된 상태
    • persist() 호출 후 detach() 호출
  • 삭제
    • 삭제된 상태
    • remove() 호출

N + 1 문제

N+1 문제는 DB에서 데이터를 가져올 때 발생하는 비효율적으로 쿼리가 N번 발생하는 현상을 의미.

어떤 엔티티 P가 있고 P는 여러 자식 엔티티 C를 가진다고 하자. 이때 우리는 P와 P의 자식 C를 모두 조회하고 싶다. 이를 위해 P를 조회하고 (쿼리 1번 실행) 이후 P의 자식 C의 개수 N번 만큼의 쿼리가 추가적으로 실행한다면 쿼리는 총 N + 1개가 실행된다 이를 N + 1 문제라고 한다.

해결 방법은 아래와 같다.

  • fetch join
    • JPQL의 문법인 fetch join을 사용하여 애초에 모든 엔티티를 한 번에 가져오는 방법
    • 단순히 join만 사용하면 연관 엔티티는 실제 엔티티화 되는 것이 아닌 프록시 상태로 남아있게 됨 -> 결국 자식의 정보를 활용하라면 프록시를 실제 엔티티로 채우기 위해 다시 쿼리가 나가게 됨
    • fetch join은 가져온 모든 엔티티들을 영속성 컨텍스트에 넣음
    • Paging 사용 시에는 주의 (쿼리 결과를 모두 메모리에 올려 페이징 처리하여 OOM 문제를 유발할 수 있음)
  • EntityGraph
    • 어떤 연관 엔티티를 함께 로딩할 것인가를 표기
    • 해당 엔티티가 로딩될 때 엔티티 그래프에 표기된 연관 엔티티까지 함께 로드
    • fetch join과 마찬가지로 Paging 사용 시에는 주의
  • Batch Size 설정
    • 여러 개의 쿼리가 날아가는 것은 허용하되, 해당 쿼리를 배치로 효율적으로 처리하는 방법

16. @Transactional

어노테이션이 작성된 영역의 모든 작업을 하나의 트랜잭션으로 묶어주는 역할을 하는 어노테이션

트랜잭션 관련된 작업은 트랜잭션을 시작한 후 메소드가 정상적으로 완료되면 커밋해야하고 예외가 발생하면 롤백해야하는데 이러한 작업을 자동으로 수행하게 한다.

동작 원리

Spring AOP 기반으로 동작함. @Transactional이 붙은 클래스를 빈으로 등록할 때 원본 객체 대신 프록시를 만듦

프록시가 원본 메소드의 호출을 가로채고 트랜잭션 시작 -> 작업 수행 -> 예외 처리 혹은 트랜잭션 종료를 수행

readOnly

readOnly 값으로 true를 주면 해당 트랜잭션 내부는 읽기 작업만 수행한다는 의미로 내부적으로 잠금을 줄이거나 불필요한 변경 감지를 하지 않게 됨 -> 성능적인 최적화 가능

읽기 작업에 대한 트랜잭션

단순히 1회의 읽기 작업에 대해서는 트랜잭션을 걸 필요가 없다. 하지만 여러 개의 읽기 작업을 수행하는 경우에는 트랜잭션을 걸지 않으면 데이터의 일관성에 문제가 생길 수 있다.

만일 데이터의 일관성이 중요하다고 판단될 경우에는 읽기 전용 트랜잭션을 걸어 데이터의 일관성을 유지해주는 것이 좋다.


0개의 댓글