Java Generics and DB Isolation

Jihye Gim·2026년 3월 26일

Codeit SB11

목록 보기
10/22

1. Java Generics란?

  • 제네릭(Generics)은 클래스, 인터페이스, 메서드를 작성할 때 "데이터 타입을 나중에 명시할 수 있게" 해주는 자바의 기능이에요.
  • 여러 타입에 재사용할 수 있는 "템플릿 코드"를 만드는 것과 비슷합니다.
  • 타입을 미리 만들지 않고, 사용할 때 타입을 지정 = 코드의 유연성과 안정성을 높임!

2. 왜 Generics를 쓸까?

  • 안전성: 컴파일 시점에 타입 체크가 되기 때문에,
    잘못된 타입 대입으로 인한 런타임 에러를 줄일 수 있어요.
  • 재사용성: 같은 로직으로 다양한 타입의 데이터를 다룰 수 있으니 코드가 짧아지고 유지보수가 쉬워져요.
  • 명확성: 타입 캐스팅 없이 바로 데이터 사용 가능 (코드 읽기가 쉬워집니다).

3. 간단한 예시로 이해하기

제네릭 없이(List 사용)

List list = new ArrayList(); 
list.add("hello"); 
list.add(123); 
// 문자열, 숫자 다 들어감  
String str = (String) list.get(0);  // OK 
String str2 = (String) list.get(1); // 런타임 오류(잘못된 타입)

제네릭 사용(List<String>)

List<String> list = new ArrayList<>(); 
list.add("hello"); 
list.add(123); // 컴파일 에러! (타입 안전성 보장)  
String str = list.get(0); // 바로 꺼내서 사용 가능(캐스팅 불필요)`

4. 제네릭 클래스 예시

class Box<T> {      // T는 타입 변수(아무 알파벳이나 써도 OK!)     
private T value;     
public void set(T value) { 
this.value = value; 
}     
	public T get() { 
    	return value; 
        } 
    }  
    Box<Integer> intBox = new Box<>(); 
    intBox.set(123); 
    intBox.set("abc"); // 컴파일 에러!  
    Box<String> strBox = new Box<>(); 
    strBox.set("hello");

5. 제네릭 메서드 예시

public <T> void printArray(T[] array) {     
	for(T elem : array) {         
    System.out.println(elem);     
    } 
 }
  • 호출 시 타입이 결정됨.

6. 바운디드 타입 (제네릭에 상한/하한 지정)

class NumberBox<T extends Number> {   // Number를 상속한 타입만 허용!     
private T num;     ... } 
NumberBox<Integer> box1 = new NumberBox<>(); // OK 
NumberBox<String> box2 = new NumberBox<>(); // 컴파일 에러!

7. 와일드카드 (Wildcard), <?>, <? extends T>, <? super T>

  • <?> : 어떤 타입이든 가능
  • <? extends T> : T 혹은 T의 하위 타입만 가능 (상한 제한)
  • <? super T> : T 혹은 T의 상위 타입만 가능 (하한 제한)

예시:

public void printNumbers(List<? extends Number> list) {     
// Integer, Double 등 Number의 하위 타입만 받음 }

8. 타입 이레이저 (Type Erasure) – 제네릭의 제약

  • Java는 컴파일 이후에는 구체적인 타입 정보(예: <String>, <Integer>)를 모두 지우고,
    내부적으로 Object로 처리합니다.
  • 즉, 런타임에는
    • List<String> ≈ List<Integer> ≈ 그냥 List
  • 이 때문에 new T[], static 필드 등 제약이 있고,
    타입 캐스팅한 것처럼 동작(런타임에 타입 체크 제한적).

9. 주의점 & 한계

  • Primitive Type 사용 불가: 제네릭에 int, char 등 직접 사용 불가 → Integer, Character 등 래퍼(Wrapper) 클래스로 써야 함
  • static 변수, 메서드에 generic T 사용 불가
  • instanceof로 제네릭 타입 체크 불가
  • 배열 생성 new T[] 불가
  • 제네릭은 참조 유형에만 적용된다.
    - 제네릭 타입의 인스턴스를 선언할 때, 타입 매개변수에 전달되는 타입 인자는 참조타입이어야 한다. int, char 같은 원시 데이터 타입은 사용할 수 없다.
    Test<int>obj=newTest<int>(20);
    위 라인은 컴파일 시 오류를 발생 시키며, 원시 타입을 캡슐화 하는 타입 래퍼를 사용해 해결할수 있다.
    하지만 원시 타입 배열은 참조 타입이기 때문에 타입 파라미터에 전달할 수 있다.
    ArrayList<int[]> a=new ArrayList<>();
  • 일반적 타입은 타입 인수에 따라 다르다.
    - 컴파일 과정에서 일반적인 타입 정보가 지워지며, 이를 타입 지우기(Type Erasure)라고도 한다.

10. 실무에서의 이점 정리

  • 컬렉션, Map, 람다 등 모든 현대 자바 API에서 필수 기능 수준으로 사용
  • 실수로 타입 섞이기 어렵게 막아줌 (코드 품질↑)
  • 동일 코드 반복 없이 효율적으로 일할 수 있음
  • 코드 재사용: 메서드/클래스/인터페이스를 한번 작성 하면 원하는 타입에 사용할수 있다.
  • 타입 안전: 제네릭은 실행 시보다 컴파일 시점에 오류를 발생시킨다.(컴파일 시점에 문제를 아는 것이 실행 시 실패하는 것보다 항상 더 낫다.)
  • 개별 타입 캐스팅이 필요하지 않다: 제네릭을 사용하지 않는다면, ArrayList에서 데이터를 가져올 때마다 타입캐스트를 해야 한다. 모든 회수 작업에서 유형화하는 것은 힘든 작업이다. 이미 List가 문자열 데이터만 저장한다는 것을 알고 있다면, 매번 타입을 지정할 필요는 없다.

결론 요약

  • Generics = “타입을 일반화 → 나중에 사용할 때 지정할 수 있는 자바 기능”
  • 안전, 재사용, 캐스팅 줄이기 등 이점 많음
  • 런타임에는 타입 정보가 사라짐 (type erasure)
  • 컬렉션 프레임워크에서 반드시 필요한 핵심 개념!

DB Isolation Level

격리수준이란?
READ UNCOMMITTED
READ COMMITTED
REPEATABLE READ
SERIALIZABLE


1. 격리수준(Isolation Level)란?

  • 여러 트랜잭션(Transaction)이 동시에 데이터베이스를 사용할 때,
    “서로의 작업이 얼마나 격리돼 있는지” 를 설정하는 규칙이다.
  • 격리수준이 높을수록 트랜잭션 간의 간섭/간섭에 의한 문제(Dirty Read, Unrepeatable Read, Phantom Read 등)가 줄지만,
    성능은 다소 낮아질 수 있다.
  • 데이터 일관성(정확성) <-> 성능(동시 처리)을 맞추기 위한 설정!
  • 정리하자면, 여러 트랜잭션이 동시에 처리될 때, 특정 트랜잭션이 다른 트랜잭션에서 변경하거나 조회하는 데이터를 볼수 있게 허용할지 결정하는 설정이다. 데이터 일관성(정합성)과 동시성(성능)사이의 트레이드오프를 조절하며, 4단계(READ UNCOMMITTED~SERIALZABLE)로 나뉜다.

2. 각 레벨별 설명

A. READ UNCOMMITTED (읽기 허용, 최저 수준)

  • 특징: 다른 트랜잭션이 "아직 커밋하지 않은 변경(=임시 값)"도 읽을 수 있음
  • 문제: Dirty Read(더러운 읽기) 발생 → 남의 롤백될 수도 있는 작업을 읽어버림
  • 실사용 거의 없음! (데이터 신뢰 어렵기 때문)
  • 예시:
    • 트랜잭션 A가 이름을 "철수" → "지혜"로 바꿨지만 아직 커밋 안 함
    • 트랜잭션 B가 이 값을 읽는다 (나중에 A가 롤백하면 B가 엉뚱한 값 본 게 됨)

B. READ COMMITTED (기본값인 DB도 많음)

  • 특징: "커밋된 데이터"만 읽기 허용. 아직 커밋 안 한 값은 못봄
  • 문제: Unrepeatable Read(반복읽기 불가), Phantom Read(유령행) 발생 가능
  • Dirty Read는 방지!
  • 예시:
    • 트랜잭션 A가 이름을 "지혜"로 커밋하면,
    • 트랜잭션 B가 읽을 땐 항상 '커밋된 값'만 봄
    • 하지만, B가 한 번 읽고 다시 읽으면 값이 "또" 바뀌어 있을 수 있음(반복읽기 불가)

C. REPEATABLE READ (MySQL 기본값)

  • 특징: 같은 트랜잭션 내에서 여러 번 읽으면 결과가 항상 같음
  • 문제: Phantom Read(새로운 행 등장/삭제)가 여전히 발생할 수 있음
  • Dirty, Unrepeatable Read는 방지!
  • 예시:
    • 트랜잭션 B가 전체 '회원 목록'을 조회했을 때,
      트랜잭션 내에서는 같은 회원들만 계속 보임
    • 하지만 누군가가 새로운 회원을 넣으면,
      다시 조회할 땐 그 행이 '갑자기' 나타날 수 있음 (Phantom Read)

D. SERIALIZABLE (가장 강력, 거의 완전 차단)

  • 특징: 트랜잭션을 하나씩 순서대로 실행된 것처럼 완벽히 격리
  • 문제: 성능 매우 저하(락 많이 걸림, 대기시간↑)
  • Dirty, Unrepeatable, Phantom Read 모두 방지 가능!
  • 예시:
    • 동시에 데이터에 접근하는 트랜잭션이 거의 없음(직렬 처리됨)
    • 데이터 정확도가 정말 중요한 곳(금융 등)에서만 주로 사용

3. 문제 상황 요약표

문제READ UNCOMMITTEDREAD COMMITTEDREPEATABLE READSERIALIZABLE
Dirty Read(더러운 읽기)OXXX
Unrepeatable Read(반복읽기 불가)OOXX
Phantom Read(유령행)OOOX

4. 실무에서는?

  • READ COMMITTED(ORACLE 등)와 REPEATABLE READ(MySQL 등)가 기본값인 경우가 많다.
  • 데이터 일관성이 가장 중요하다면 SERIALIZABLE,
    동시처리 많고 속도가 중요하다면 READ COMMITTED를 주로 사용합니다.
  • 대부분의 서비스는 READ COMMITTED 또는 REPEATABLE READ로 충분,
    특별히 강한 격리 필요시 SERIALIZABLE을 선택합니다.

5. DB 격리수준 4단계 (높은 수준순)

격리 수준이 높을 수록 데이터는 안전하지만 동시성 성능은 떨어진다.

  • SERIALIZABLE(직렬화 가능) : 가장 높은 수준. 완벽한 일관성 제공, 성능 매우 낮음.
    - 모든 트랜잭션은 순차적으로 실행되어 하며, 이는 Dirty Read, Unrepeatable Read, phantom Read가 없도록 보장한다.
  • REPEATABLE READ(반복 가능 읽기): 트랜잭션 내에서 같은 조회 결과 보장.MYSQL, InnoDB기본 설정.
    - 이 격리 수준은 다른 트랜잭션이 데이터에 변경을 커밋하더라도 트랜잭션이 지속 시간 내내 동일한 데이터를 보게 보장한다. 하지만 Phantom Read는 여전히 가능하다.
  • READ COMMITTED(커밋된 읽기): 커밋된 데이터만 조회 가능. 대부분의 RDBMS(Oracle SQL Server) 기본 설정.
    - 이 격리 수준에서는 트랜잭션이 다른 커밋된 트랜잭션에 의해 이루어진 변경만 볼수 있다. 이로 인해 Dirty Read는 제거되지만 여전히 반복 불가능한 Read와 Phantom Read가 발생할수 있다.
  • READ UNCOMMITTED(커밋되지 않은 읽기): 커밋되지 않은 데이터도 조회 가능. 무결성이 깨져 거의 사용 안함.
    - 이는 트랜잭션이 다른 트랜잭션에 의해 이루어진 미커밋 변경을 볼수 있는 가장 낮은 격리 수준이다. 이로 인해 Dirty Read, Non-Repeatable Read, Phantom Read가 발생할 수 있다.

6. 발생 가능한 데이터 부정합 문제

격리 수준이 낮을 수록 다음 문제들이 발생할 확률이 높다.

  • Dirty Read: 커밋되지 않은 트랜잭션의 데이터를 읽음.
  • Non-Repeatable Read: 한 트랜잭션 내에서 같은 데이터를 두번 조회할 때 다른 결과가 나옴.
  • Phantom Read: 한 트랜잭션 내에서 같은 쿼리로 범위 조회 시 이전엔 없던 레코드가 나타남.

7. 사용 예시

  • 금융/송금 시스템(높은 일관성 필요): SERIALIZABLE을 사용하여 잔액이 변경 중일 때 다른 트랜잭션이 접근하지 못하게 함.
  • 일반적인 웹 서비스(성능 중요): READ COMMIT 또는 REPEATABLE READ를 사용하여 사용자가 수정한 내용을 바로 반영하되(커밋 후), 데이터 오염은 막음.

8. Synonyms

  • Transaction Isolation Level(트랜잭션 격리 수준)
  • Database Iolation Level(데이터베이스 격리 수준)
  • Transaction Consistency Level(트랜잭션 일관성 수준)
  • 트랜잭션 고립 수준

Q&A&A

✅ DB Isolation Level (격리수준)

1. Isolation Level이란 무엇인가요?

  • 여러 트랜잭션이 동시에 DB 데이터를 다룰 때, 각 트랜잭션이 다른 트랜잭션의 변경을 얼마나 격리해서 볼 수 있는지를 정의하는 설정입니다. 데이터 일관성과 성능의 균형을 정합니다.

2. MySQL과 Oracle의 기본 Isolation Level 차이점은?

  • MySQL(InnoDB)은 보통 REPEATABLE READ, Oracle은 READ COMMITTED를 기본값으로 사용합니다. MySQL은 동일 트랜잭션 내에서 반복해 읽으면 값이 고정되고, Oracle은 커밋된 최신값을 매번 읽습니다.

3. Dirty Read, Unrepeatable Read, Phantom Read란?

  • Dirty Read: 다른 트랜잭션이 아직 커밋하지 않은 데이터를 읽는 것
  • Unrepeatable Read: 한 트랜잭션 내에서 같은 쿼리를 반복해도 결과가 달라질 수 있는 것
  • Phantom Read: 한 트랜잭션 내에서 같은 조건으로 여러 번 조회할 때, 중간에 새로운 행이 생기거나 사라져 결과집합이 달라지는 것

4. 각 Isolation Level 차이 설명

  • READ UNCOMMITTED: Dirty Read, Unrepeatable Read, Phantom Read 모두 발생
  • READ COMMITTED: Dirty Read만 방지
  • REPEATABLE READ: Dirty Read, Unrepeatable Read 방지
  • SERIALIZABLE: 모든 동시성 문제 방지, 성능은 낮음

5. Phantom Read란?

  • 한 트랜잭션 내에서 같은 조건으로 여러 번 조회할 때, 다른 트랜잭션이 행 추가/삭제를 해서 결과에 유령(phantom)처럼 새로운 행이 보이는 현상입니다.

6. SERIALIZABLE을 꼭 써야 하는 상황?

  • 은행 같은 정확성과 신뢰성이 매우 중요한 시스템에서 동시성으로 인한 데이터 오류를 절대 허용할 수 없는 경우 사용합니다.

7. 트랜잭션과 Isolation Level의 관계?

  • 트랜잭션은 데이터의 원자성, 일관성, 격리성, 지속성을 보장하는 작업의 단위이며, Isolation Level은 그 중 격리(동시성)를 어떻게 보장할지 세부적으로 조절하는 요소입니다.

8. Isolation Level 조정 실무경험

  • 동시성이 중요한 곳에서는 READ COMMITTED나 REPEATABLE READ를 사용해 성능과 일관성을 조율하는 경우가 많습니다. 문제가 생길 때는 Isolation Level을 임시로 높여보며 원인을 찾기도 합니다.

9. 낮은/높은 Isolation Level의 장단점

  • 낮은 수준은 속도와 동시성에 유리하지만 데이터 일관성에 취약,
    높은 수준은 데이터 일관성이 강력하나 성능 저하와 락 경합이 큼

10. 동시 트랜잭션에서 발생할 수 있는 에러 예시?

  • 은행 이체 중 계좌 잔고를 동시에 참조해 두 번 이상 인출되는 lost update, dirty read, phantom read 등이 대표적입니다.

11. 두개의 트랜잭션이 동시에 진행중이다. 트랜잭션 A는 행을 업데이트하지만 커밋하지 않는다. 트랜잭션B는 커밋전에 업데이터된 행을읽고, 이후 트랜잭션 A가 롤백한다. 어떤 고립현상이 발생했나?

  • Dirty Read
  • 트랜잭션 B는 커밋되지 않은 데이터(트랜잭션 A의 업데이트)를 읽었고, 이 데이터는 커밋되지 않았고 롤백되었다. 이런것이 바로 고전적인 Dirty Read 시나리오이다.

12. 어떤 격리 수준에서 Dirty Read가 없고, 반복 불가능한 Read가 없으며, Phantom Read가 불가능한 경우(ANSI/ISO 표준에 따르면)는 어떤 것인가?

  • 직렬화 가능
  • 직렬화 가능한 것은 가장 엄격한 표준 격리 수준으로, Dirty Read, Unrepeatable Read, Phantom Read 등 세가지 주요 이상 현상을 모두 방지한다.

13. MVCC(다중 버전 동시성 제어)가 SQL 데이터베이스에서 동시 트랜잭션을 어떻게 처리 하는지 설명하자면?

  • MVCC는 리더가 데이터의 스냅샷에 접근할 수 있도록 하며, 작성자는 행의 새로운 버전을 생성한다.
  • MVCC는 행의 여러 버전을 생성하여 리더가 데이터를 업데이트하는 동안 일관된 스냅샷을 볼 수 있게 한다. 이로 인해 차단을 피하고 높은 동시성을 지원한다.

14. 어떤 격리 수준에서 트랜잭션이 재읽을때 항상 같은 행 값을 보게 되나? 다른사람이 업데이트를 하더라도.

  • Repeatable Read
  • Repeatable Read는 행 값을 안정적으로 유지하지만 Phantom Read를 방지하지는 않는다.

15. 왜 고격리 수준이 보통 더 느리나요?

  • 더 엄격한 잠금을 통해 동시성을 줄인다.
  • 더 높은 격리는 더 강한 잠금을 사용하여 병렬 접근을 제한하여 동시성과 속도를 낮춘다.

16. Read Committed 수준에서 어떤 현상이 예방되나요?

  • Dirty Read
  • Read Committed는 Dirty Read를 차단하지만 반복 불가능하고 Phantom Read는 허용한다.

✅ Java Generics

1. Java에서 Generics란?

  • 클래스나 메서드를 선언할 때 타입을 파라미터로 받아, 다양한 타입에 재사용하게 해주는 기능입니다.

2. Generics의 장점과 단점

  • 장점: 타입 안전성, 코드 재사용성, 캐스팅 제거
  • 단점: 타입 정보 소실(Type Erasure), 프리미티브 타입 사용 불가, 복잡한 문법

3. Type Erasure란?

  • 제네릭의 타입 정보는 컴파일 시점까지만 유지되고, 실행 시에는 모두 Object로 변환되는 현상입니다. 런타임에 실제 타입 정보가 없습니다.

4. 와일드카드의 의미와 예시

  • <?> 어떤 타입이든 받을 수 있음
  • <? extends T> T의 하위 타입만 받을 수 있음
  • <? super T> T의 상위 타입만 받을 수 있음
  • 예) List<? extends Number>는 Integer, Double 리스트 모두 받을 수 있습니다.

5. 제네릭 클래스/메서드 만드는 법 예시

class Box<T> { private T t; } public <T> void print(T t) { ... }

6. 프리미티브 타입을 못 쓰는 이유는?

  • 제네릭 타입은 컴파일 이후 Object가 되는데, int 등은 Object가 아님. 그래서 Integer, Double 등 래퍼 클래스를 써야 합니다.

7. Box<String>Box<Integer>의 런타임 타입이 같은 이유?

  • 컴파일 후에는 제네릭 타입 정보가 지워져 둘 다 Box로만 취급되기 때문입니다.

8. new T[], static T와 같은 문법을 쓸 수 없는 이유?

  • 타입 이레이저로 인해 런타임에 T가 뭔지 몰라 배열, static 필드 등에 쓸 수 없습니다.

9. 실무에서 Generics 실수/성공 사례?

  • 잘못된 타입을 막아줘서 런타임 예외가 사라진 경험,
    혹은 와일드카드 사용에 혼동해 에러가 난 경험 등 설명 가능

10. List<Object> vs List<?> 차이?

  • List<Object>는 오직 Object 타입만 넣고,
    List<?>는 어떤 타입이든 받지만 값을 추가(put)할 수 없음(불변 컬렉션).
    List<Object>List<String>을 대입 불가(반공변).

11. 제네릭을 자바에서 사용하는 이유?

  • 컴파일 타임 타입 안정성을 위해

12. 맵 인터페이스의 구현은?

  • 해시맵
  • HashMap 은 인터페이스의 키-값 쌍 구현이다.

13. 자바 제네릭은 타입 안정성을 보장하고 코드 재사용을 허용한다.

14. 제네릭에서 와일드카드는 무엇에 사용하나?

  • 일반 프로그래밍에서 알려지지 않은 유형을 나타낼 때 사용한다.
  • 제너릭의 와일드카드는 컬렉션 유형에 유연성을 제공한다.

<참고> https://www.geeksforgeeks.org/dbms/transaction-isolation-levels-dbms/
https://www.geeksforgeeks.org/java/generics-in-java/

profile
Rookie

0개의 댓글