콘텐츠로 이동
Bluetape4k 문서1.11

EntityManager와 transaction

최신 안정판 Bluetape4k 1.11.0 릴리스 기준

transaction 소유자는 하나여야 한다

섹션 제목: “transaction 소유자는 하나여야 한다”

독립 실행 코드에서 withNewEntityManager는 새 EntityManager와 transaction을 만들고 block 성공 시 commit, 실패 시 rollback한 뒤 manager를 닫습니다.

val result = emf.withNewEntityManager { em ->
val account = Account("user@example.com")
em.persist(account)
account.identifier
}

Spring의 @Transactional 안에서는 이미 framework가 EntityManager와 commit을 소유합니다. 그 안에서 withNewEntityManager를 호출하면 같은 service 작업에 별도 persistence context와 transaction이 생길 수 있습니다. framework-managed 경계에서는 주입된 EntityManager를 사용합니다.

block이나 commit이 실패하면 rollback을 시도합니다. 1.11.0에서 rollback 실패는 warn log로만 남고 원래 예외에 suppressed로 붙지 않습니다. 원래 예외는 유지되지만 rollback 원인을 사후 분석하려면 해당 logger를 수집해야 합니다.

saveentity.isPersisted && !contains(entity)이면 merge, 그 밖에는 persist를 호출합니다.

val detached: Account = loadOutsideCurrentContext()
detached.name = "new name"
val managed = entityManager.save(detached)
// 이후 변경은 managed에 적용한다.

merge는 detached instance를 다시 managed로 바꾸는 함수가 아닙니다. 상태를 managed instance로 복사하고 그 instance를 반환합니다. save 반환값을 버리고 원본을 계속 수정하면 변경이 저장되지 않을 수 있습니다.

delete는 id가 없는 entity를 무시합니다. detached entity는 merge한 뒤 삭제합니다. deleteById는 reference로 삭제를 시도하지만 SQL, foreign-key와 optimistic-lock 오류는 remove 호출보다 flush나 commit에서 늦게 나타날 수 있습니다.

Hibernate는 SQL을 API 호출과 동시에 실행하지 않을 수 있습니다. constraint 위반이나 database 오류가 persist가 아니라 query 전 자동 flush, 명시적 flush, transaction commit에서 나타날 수 있습니다. 테스트에서 실패 위치를 고정하려면 검증하려는 작업 뒤에 flush()를 명시합니다.

entityManager.persist(account)
entityManager.flush() // database constraint까지 이 지점에서 검증

findAll은 조건 없는 Criteria query에 paging을 설정합니다. 기본 maxResultsInt.MAX_VALUE이므로 운영에서는 반드시 상한을 지정합니다.

val page = entityManager.findAll(
Account::class.java,
firstResult = 0,
maxResults = 100,
)

deleteAll<T>()은 JPQL bulk delete입니다. managed entity를 하나씩 지우지 않으므로 entity callback, cascade와 persistence-context 상태를 맞춰 주지 않습니다. 먼저 필요한 변경을 flush하고, 실행 뒤에는 clear()하거나 transaction을 끝냅니다.

entityManager.flush()
val deleted = entityManager.deleteAll<ExpiredSession>()
entityManager.clear()

currentSession은 표준 unwrap(Session::class.java)를 사용합니다. 반면 currentSessionImplcurrentConnection은 Hibernate 내부 구현인 SessionImpljdbcCoordinator에 접근합니다. provider 교체가 가능한 code나 일반 repository에서는 쓰지 말고, Hibernate 전용 integration이 물리 connection을 반드시 요구할 때만 좁게 둡니다.

Terminal window
./gradlew :bluetape4k-hibernate:test \
--tests 'io.bluetape4k.hibernate.EntityManagerFactorySupportTest'
./gradlew :bluetape4k-hibernate:test \
--tests 'io.bluetape4k.hibernate.EntityManagerSupportTest'