[프로그래밍] JPA

[JPA] JPA의 영속성 컨텍스트

JHVan 2024. 5. 3. 21:39

Entity Manager

JPA를 통해 객체를 영혹화 하기 위해선 EntityManager 객체가 필요하고 ,객체는 EntityManagerFactory 객체를 통해 얻음

EntityManager는 영속성 컨텍스트를 통해 영속 객체를 관리함

사용자는 EntityManager 인스스 객체의 메소드들을 이용해 영속성 객체를 관리함

EntityManagerFactory

  • EntityManagerFactory는 EntityManager 인스턴스를 생성하는 역할. 애플리케이션 전체에서 한 번만 생성되며, 이를 통해 여러 EntityManager 인스턴스를 얻을 수 있음.
  • EntityManagerFactory는 보통 애플리케이션 시작시에 생성되며 종료 시에 닫힘. 이는 매우 비용이 많이 드는 작업이므로, 애플리케이션 내에서 재사용됨.
  • 생성 방법은 Persistence 클래스의 createEntityManagerFactory 메소드를 사용하여 구현.

EntityManager

  • EntityManager는 엔티티들의 생명주기를 관리하며, 영속성 컨텍스트와 상호작용하는 인터페이스를 제공함
  • CRUD(Create, Read, Update, Delete) 작업과 같은 데이터베이스 연산을 수행하는 데 사용.
  • EntityManager 인스턴스는 일반적으로 요청이나 트랜잭션 범위 내에서 사용되며, 사용이 끝나면 종료.

영속성 컨텍스트(Persistence Context)

영속성 컨텍스트

  • 영속성 컨텍스트는 DB와 어플리케이션 사이에서 EntityManager에 의해 관리되는 엔티티 인스턴스들의 집합.
  • 엔티티들의 생명주기를 관리하며, 영속된 엔티티의 1차 캐시, 변경 감지, 쓰기 지연, 지연 로딩과 같은 기능을  EntityManager 의 메소드를 이용하여 1차 캐시에 관리.
  • 엔티티가 영속성 컨텍스트에 저장되면, JPA는 해당 엔티티의 상태를 관리하며 필요에 따라 데이터베이스와 동기화.
  • 영속성 컨텍스트를 통해 애플리케이션이 보다 효율적으로 데이터를 관리할 수 있게 해주며, 데이터베이스와의 불필요한 호출을 줄임.

사용자에 의한 EntityManager 인스턴스 사용

Entitymanager에 의한 영속성 컨텍스트 동작방식

  • 영속성 컨텍스트는 EntityManager 단위로 관리되고, 사용자는 EntityManager 인스턴스의 메소드를 호출하여 엔티티를 영속화, 조회, 갱신, 삭제할 수 있음.
  • persist 메소드를 사용해 엔티티를 영속화 하여 1차 캐시에 저장되고, 일반적으로 트랜잭션 커밋이 되는 시점에 Insert 통해 DB에 반영, find 메소드로 1차 캐시의 영속성 컨텍스트,Select 통한 DB의 엔티티 순서로 양쪽 모두 조회할 수 있음.
  • EntityManager는 또한 트랜잭션 관리를 위한 메소드를 제공함.
  • 트랜잭션 내에서 수행된 작업들은 데이터베이스에 반영되기 전에 영속성 컨텍스트에 반영됨.

  • 영속 컨텍스트를 통해 관리되는 영속 객체는 영속 객체의 상태 변화가 있을 때, JPA는 자동으로 이를 감지하고 데이터베이스에 반영하기 위한 Update 쿼리를 생성하고 커밋 시점에 데이터베이스에 반영.
  • 객체의 변경 사항은 1차 캐시의 스냅샷(snapshot) 정보를 통해 판별. 영속 컨텍스트에 객체가 저장될 때, 그 시점의 객체 상태를 스냅샷으로 보관함. 그 후 객체의 상태에 변경이 발생하면, 현재 객체 상태와 스냅샷을 비교.
  • 스냅샷과 현재 객체 상태가 다르다면, 즉 객체가 변경되었다면, UPDATE 쿼리를 생성하여 SQL 저장소에 저장함. 트랜잭션이 커밋되는 시점에 이러한 쿼리들이 데이터베이스에 반영되어 객체의 변경 사항이 데이터베이스에 동기화됨. 

플러시 (flush)

영속성 컨텍스트의 내용을 데이터베이스와 동기화 하는 것.

이 과정에서 영속성 컨텍스트에 쌓인 변경 내용(INSERT, UPDATE, DELETE 등)이 데이터베이스에 반영됨.


플러시의 3가지 방식

  •  EntityManager.flush() 직접 호출을 통한 플러시. 개발자가 명시적으로 플러시를 호출할 때 사용됨.
  • 트랜잭션의 커밋을 통한 자동 플러시. 트랜잭션의 일관성을 보장하기 위해 커밋 직전에 변경 내용을 데이터베이스에 반영.
  • JPQL 쿼리 실행을 통한 자동 플러시. 쿼리 실행 전에 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영하여 쿼리 결과의 정확성을 보장

플러시를 실행한 이후에도 영속성 컨텍스트의 내용은 그대로 유지. 즉, 1차 캐시, 변경 감지, 지연 로딩 같은 영속성 컨텍스트의 기능이 그대로 작동함. 플러시는 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영만 하고 영속성 컨텍스트를 비우거나 초기화하지 않음.

 

기본키 적용을 위한 어노테이션:

@Id: 단순히 하나의 필드를 기본키로 지정할 때 사용됨.

 

@IdClass: 복합키를 지원하며, 별도의 클래스를 이용하여 복합키를 정의할 때 사용됨.

@EmbeddedId: 복합키를 임베디드 타입으로 사용할 때 적용되며, 복합키를 내장된 객체로 처리함.

기본키를 어디서 생성할지 결정해야함.

애플리케이션에서 생성하는 방식과 데이터베이스에서 생성하는 방식, 두 가지 기본 키 생성 매커니즘을 결정하는 것이 필요함.

  1. 애플리케이션에서 생성:
    • 자바 프로그램 내에서 유니크한 값을 생성하여 기본 키로 사용함.
    • UUID와 같은 메소드를 사용하여 독립적으로 유니크한 값을 생성함.
    • 데이터베이스 시스템에 독립적이며 다양한 데이터베이스와 호환됨.
  2. 데이터베이스에서 생성:
    • 데이터베이스의 내장 기능을 사용하여 자동으로 유니크한 키를 생성함.
    • 시퀀스(Sequence)와 자동 증가(Auto Increment) 방식이 있음.
      • 시퀀스: 데이터베이스 내에서 사전 정의된 순서로 값을 생성하는 객체를 사용함. Oracle과 PostgreSQL에서 사용됨.
      • 시퀀스(sequence)는 데이터베이스에서 순차적으로 증가하는 숫자를 생성하기 위해 사용되는 객체.
        이는 주로 기본키(primary key)의 값을 자동으로 생성할 때 사용됨. 
        시퀀스는 사용자가 지정한 규칙에 따라 고유한 값을 순차적으로 생성할 수 있으며,
        이 값은 테이블 간에 공유될 수 있음.
        
        시퀀스의 주요 특징:
        시작 값(START WITH): 시퀀스가 생성할 첫 번째 값을 지정한다.
        증가 값(INCREMENT BY): 시퀀스가 각 순번에 적용할 증가량을 지정한다.
        최소값(MINVALUE)과 최대값(MAXVALUE): 시퀀스가 생성할 수 있는 값의 범위를 지정한다.
        사이클 여부(CYCLE): 시퀀스가 최대값에 도달했을 때 최소값으로 다시 시작할지 여부를 지정한다.
        캐시 여부(CACHE): 성능 최적화를 위해 미리 일정량의 시퀀스 값을 메모리에 캐시할지 여부를 지정한다.
        
        시퀀스를 사용하는 이유:
        고유성 보장: 시퀀스는 테이블의 여러 행에 대해 고유한 값을 생성하여 기본키로 사용할 수 있게 한다.
        성능 최적화: 시퀀스 값은 미리 생성되어 캐시될 수 있으므로,
        데이터베이스에 새 행을 삽입할 때마다 새로운 고유값을 생성하는 데 드는 시간을 줄일 수 있다.
        유연성: 사용자가 시퀀스의 시작값, 증가량, 최대값 등을 자유롭게 지정할 수 있어,
        필요에 맞게 시퀀스를 구성할 수 있다.
        시퀀스는 Oracle, PostgreSQL, DB2 등 여러 데이터베이스 관리 시스템(DBMS)에서 지원한다.
        MySQL과 같은 일부 DBMS에서는 내장된 시퀀스 기능 대신 AUTO_INCREMENT 속성을 사용하여
        유사한 기능을 제공한다.
      • 자동 증가: 새로운 레코드마다 숫자를 자동으로 증가시킴. MySQL과 SQL Server에서 사용됨.
    • 데이터베이스가 직접 키를 관리하여 애플리케이션 코드를 간결하게 하며, 키 생성의 일관성과 안정성을 보장함.

결정 전에 애플리케이션 요구 사항과 데이터베이스의 지원 기능을 고려해야 함.

 

기본키 값을 직접 할당하는 경우 @Id 어노테이션을 사용함.

기본키 값을 자동으로 생성하고 싶다면 @GeneratedValue 어노테이션을 적용함.

@GeneratedValue를 사용하여 기본키 값의 자동 생성 방법에는 여러 옵션을 통해 생성 전략을 적용할 수 있음.

 
 

Identity 전략

데이터베이스가 기본키의 생성을 자동으로 관리하는 전략.

새로운 엔티티가 데이터베이스에 저장될 때, 데이터베이스가 자동으로 기본키 값을 생성하고 할당.

MySQL, DB2, Oracle 등의 데이터베이스에서 지원함.

Identy 전략

 

주요 특징은 데이터를 저장할 때, 즉 persist() 메소드를 호출할 때 바로 Insert 쿼리가 실행되며, 이 시점에 데이터베이스에서 새로운 기본키 값이 생성되고 할당.

따라서 엔티티의 기본키 필드에는 저장 직후 바로 실제 데이터베이스에서 생성된 기본키 값을 확인할 수 있음.

 

장점은 구현이 간단하고, 데이터베이스가 기본키 생성을 자동으로 처리하기 때문에 개발자가 기본키 생성 로직을 신경 쓸 필요가 없다.

단점은 persist() 메소드 호출 시마다 즉시 Insert 쿼리가 실행되기 때문에, 성능상의 이슈가 발생할 수 있으며, 일부 JPA 구현체에서는 이러한 동작 방식으로 인해 벌크 삽입(bulk insert) 같은 최적화 작업이 어려울 수 있음.

 

Sequence 전략

데이터베이스의 Sequence 오브젝트를 이용해 기본키를 생성하는 방식.

Oracle, PostgreSQL, DB2와 같이 Sequence 오브젝트를 지원하는 데이터베이스에서 사용할 수 있음.

이 전략을 사용하기 위해서는 @SequenceGenerator 어노테이션을 클래스나 필드에 적용해야 함.

@SequenceGenerator 어노테이션을 통해 Sequence 이름, 초기값, 증가치 등을 설정할 수 있으며, 이러한 옵션을 조정해 기본키 생성 방식을 더 세밀하게 제어할 수 있음.

Sequence 전략

@SequenceGenerator 어노테이션은 customer_generator라는 이름의 시퀀스 생성기를 정의 (name).

이 생성기는 customer_seq라는 이름의 데이터베이스 시퀀스 오브젝트와 연결됨 (sequenceName).

initialValue가 1로 설정되어 시퀀스 값이 1부터 시작하고,

allocationSize가 1로 설정되어 시퀀스 값이 한 번에 1씩 증가하게 됨.

initialValue와 allocationSize는 @SequenceGenerator 어노테이션에서 사용되는 속성들이다.

initialValue: 시퀀스의 시작 값을 지정한다.
예를 들어, initialValue = 1이라고 설정하면 시퀀스가 1부터 시작한다. 
이 값은 시퀀스 오브젝트가 생성될 때 최초로 사용될 값이다.

allocationSize: 시퀀스의 증가량을 지정한다.
allocationSize = 1로 설정하면 시퀀스 값이 1씩 증가한다. 
이 속성은 JPA가 한 번에 메모리에 할당할 시퀀스 값의 수를 결정한다. 
예를 들어,allocationSize가 1이라면 엔티티를 저장할 때마다 새로운 시퀀스 값을 얻기 위해
데이터베이스에 접근해야 한다.
만약 50개의 엔티티를 저장한다면, 
이는 50번의 데이터베이스 시퀀스 접근을 의미한다.
allocationSize = 50이라면 JPA는 시퀀스 값을 50씩 증가시키며, 
JPA는 한 번의 데이터베이스 시퀀스 접근으로 50개의 시퀀스 값을 메모리에 할당하고, 
이후 엔티티를 저장할 때마다 데이터베이스에 별도의 시퀀스 값 요청을 하지 않고 
메모리에서 할당된 값을 사용한다. 
이로 인해 데이터베이스와의 통신 횟수가 줄어들어 성능이 향상되는 효과가 있다.

allocationSize를 크게 설정하면 사용되지 않는 시퀀스 번호가 발생할 수 있다.

allocationSize가 50으로 설정된 경우 
어플리케이션이 시작할 때 시퀀스 값이 1이고, 
첫 번째 엔티티를 데이터베이스에 저장할 때, 
JPA는 1부터 50까지의 시퀀스 값을 할당받는다. 
이때 첫 번째 엔티티에는 시퀀스 값 1이 할당되고,
다음 엔티티가 저장될 때는 2가 할당될 것이다. 
어플리케이션이 10개의 엔티티만 저장하고 종료되면,
11부터 50까지의 시퀀스 값은 사용되지 않게 된다.

다음에 어플리케이션이 재시작되고 새로운 엔티티가 저장될 때,
JPA는 시퀀스 값을 다시 할당받게 되는데,
이때는 51부터 시작하여 다시 50개의 값을 할당받는다. 
따라서, 11부터 50까지의 시퀀스 값은 어떤 엔티티에도 사용되지 않는 채로 남게 되어,
이는 시퀀스 값의 불연속성을 초래한다.
자주 재시작되는 어플리케이션에서 더욱 두드러질 수 있으며, 
시퀀스 값의 연속성이 중요한 경우에는 allocationSize를 신중하게 고려해야 한다.

 

@Id 어노테이션은 해당 필드가 테이블의 기본 키임을 나타냄.

@GeneratedValue 어노테이션은 기본 키의 값을 자동으로 생성하기 위한 전략을 지정하는데 사용됨

strategy 속성을 GenerationType.SEQUENCE로 설정하여 customer_generator라는 이름의 시퀀스 생성기를 사용.

Customer 엔티티의 인스턴스가 데이터베이스에 Insert될 때 customer_seq 시퀀스에서 다음 값이 자동으로 id 필드에 할당되어 각 인스턴스마다 고유한 기본 키 값이 부여됨.

 

만약 DB 에 sequence 가 이미 만들어져 있다면, Id 필드 위에 @SequenceGenerator(name =~ , sequenceName=~) 로.

 

Table 전략

Table 기본키 생성 전략은 특정한 테이블을 사용하여 엔티티의 기본키를 생성하는 방식.

Table 기본키 생성 전략은 별도의 테이블을 생성하기 때문에 데이터베이스 종류에 영향을 받지 않음.

Table 생성 전략을 적용하기 위해서는 @TableGenerator 어노테이션이 필요하며 여러 옵션을 적용할 수 있음.

Table 생성 전략은 테이블 생성과 키값 증가를 위한 update가 실행되기 때문에 성능에 대한 고려가 필요.

Table 전략

@TableGenerator 어노테이션을 사용하여 기본키 생성을 위한 별도의 테이블(customer_id)을 지정.

이 테이블에서는 id_name 컬럼에 customer_id라는 값을 가지며, 실제로 기본키 값이 저장되는 컬럼은 next_value로 지정.

initialValue 0: 기본키 값은 0부터 시작, allocationSize 1: 기본키 값은 하나씩 증가.

@TableGenerator 어노테이션을 사용하여 Table 생성 전략을 적용할 때의 옵션:
name: 생성기의 이름을 지정
이 이름은 @GeneratedValue 어노테이션의 generator 속성과 함께 사용된다.
table: 기본키 값을 저장할 테이블의 이름을 지정
pkColumnName: 테이블에서 기본키 엔티티의 이름을 저장하는 열의 이름을 지정
valueColumnName: 테이블에서 기본키의 현재 값을 저장하는 열의 이름을 지정
pkColumnValue: 기본키 값을 저장하는 테이블에서 사용될 특정 엔티티의 이름을 지정
initialValue: 기본키의 초기 값 설정. (기본값은 0)
allocationSize: 기본키 값의 증가 크기를 설정 (기본값은 50)

@GeneratedValue 어노테이션의 strategy 속성에 GenerationType.TABLE을 지정하고, generator 속성으로 id_generator를 지정하여 Customer 엔티티의 id 필드가 이 테이블 생성 전략을 사용하여 생성.

주의사항

Table 기본키 생성 전략은 매번 엔티티를 저장할 때마다 키 생성을 위한 테이블을 조회하고, 해당 테이블의 값을 update하여 새로운 키 값을 생성하므로

  • 데이터베이스 액세스 증가: 키를 생성할 때마다 테이블에 접근하여 조회 및 업데이트를 실행하여 추가적인 데이터베이스 Input/Output(I/O) 발생.
  • 병렬 처리 시 충돌 가능성: 동시에 여러 트랜잭션이 같은 테이블을 업데이트하려 할 때 충돌이 발생할 수 있고 해결 위한 락킹 메커니즘이 성능에 영향을 줄 수 있.

'[프로그래밍] JPA' 카테고리의 다른 글

[JPA] JPA의 기본 키 생성 전략  (0) 2024.05.04
[JPA] Entity Mapping  (0) 2024.05.04
[JPA] JPA 와 DB Dialect  (0) 2024.05.01
[JPA] JPA?  (0) 2024.04.29
[JPA] 와 SQL  (0) 2024.04.23