@Transactional이 실제로 하는 일
public void transactionalMethod() {
PlatformTransactionManager transactionManager = getTransactionManager(); // 트랜잭션 매니저 획득
TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition()); // 트랜잭션 시작
EntityManager entityManager = entityManagerFactory.createEntityManager(); // Persistence Context 생성
bindEntityManagerToTransaction(entityManager); // EntityManager를 트랜잭션과 묶음
Connection connection = dataSource.getConnection(); // DB 커넥션 얻기
try {
// ===== 실제 비즈니스 로직 실행 =====
doBusinessLogic(entityManager);
entityManager.flush(); // JPA에서는 커밋 전에 flush
transactionManager.commit(status); // 트랜잭션 정상 종료 (commit)
} catch (Exception e) {
transactionManager.rollback(status); // 에러 발생 시 rollback
} finally {
connection.close(); // 커넥션 반환
entityManager.close(); // Persistence Context 닫기
}
}
단계 | 설명 |
AOP Proxy 생성 | 메서드를 감싸는 트랜잭션 프록시를 만든다 |
트랜잭션 매니저 호출 | 트랜잭션 시작 (TransactionManager.begin()) |
DB Connection 획득 | DataSource에서 실제 DB 커넥션(Connection)을 빌려온다 |
Persistence Context 바인딩 | EntityManager를 트랜잭션과 묶어서 Persistence Context를 개시 |
메서드 실행 | 비즈니스 로직 실행 |
정상 종료 시 | flush → commit |
예외 발생 시 | rollback |
트랜잭션 종료 후 | 커넥션 반환, Persistence Context close |
트랜잭션과 영속성 컨텍스트의 관계
- Persistence Context는 commit (DB 반영) 전에 변경사항을 메모리에 모아두는 캐시 역할이기 때문에, 트랜잭션과 함께 생성되고 함께 종료된다.
개념 | 설명 |
트랜잭션 시작 | Persistence Context도 같이 연다. (보통 하나 묶어서 관리한다) |
트랜잭션 끝 (commit/rollback) | Persistence Context도 함께 닫는다 (flush + clear) |
JPA의 트랜잭션 관리
- 트랜잭션이 설정되지 않은 경우, 데이터 변경이 데이터베이스에 반영되지 않습니다. 엔티티 매니저의 flush 메서드를 명시적으로 호출해야 변경 사항이 데이터베이스에 반영됩니다.
- 그러나 repository CRUD를 담당하는 구현체 SimpleJPA Repository에서는 모두 transactional이 붙어있으므로 서비스 메소드 내에 Transaction은 선택적으로 붙일 필요가 있다.
public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T, ID>
@Transactional
public <S extends T> S save(S entity) {
Assert.notNull(entity, "Entity must not be null");
if (entityInformation.isNew(entity)) {
entityManager.persist(entity);
return entity;
} else {
return entityManager.merge(entity);
}
}
@Transactional
public long delete(@Nullable Specification<T> spec) {
CriteriaBuilder builder = this.entityManager.getCriteriaBuilder();
CriteriaDelete<T> delete = builder.createCriteriaDelete(getDomainClass());
if (spec != null) {
Predicate predicate = spec.toPredicate(delete.from(getDomainClass()), builder.createQuery(getDomainClass()),
builder);
if (predicate != null) {
delete.where(predicate);
}
}
return this.entityManager.createQuery(delete).executeUpdate();
}
트랜잭션 @Transactional 의 필요성
- 원자적이지 않은 연산을 원자적으로 묶고 싶을때 (roll back 기능이 필요할 때)
- Persistence Contex를 사용하기 위해서
- 쓰기 지연 (Write-Behind)
- DB 내용 1차 캐시 (First-Level Cache)
- 지연 로딩 (Lazy Loading) 등
- 특히, lazyloading을 사용할때는 Persistence Context가 필요하며 그렇지 않으면 LazyInitializationException이 발생
Set<AuthMenu> authMenuList = findAuth.getAuthMenuList();
List<AuthorityDto.AuthMenuResDto> authMenuResDtoList = authMenuList.stream()
[handleException] message : failed to lazily initialize a collection of role: : could not initialize proxy - no Session
@Transactional(readOnly=true)
트랜잭션을 읽기 전용 속성으로 지정. 이는 성능을 최적화하기 위해서 사용하거나 트랜잭션 기능에 제약을 걸어두기 위해 사용함.
- readOnly = true이면 JPA는 flush를 생략함
- 영속성 컨텍스트가 변경된 엔티티를 추적하지 않음 (dirty checking 비활성화)
- 결과적으로 flush(), commit 시 SQL 실행 없음
@Transactional 남발하면 생기는 문제
가장 큰 비용은 무엇인가? : "DB 커넥션을 점유하는 것"
- 트랜잭션을 시작하면 커넥션을 가져와야 한다. 가져온 커넥션은 트랜잭션이 끝날 때까지 점유한다.
- 외부 API 호출 등 장시간 동작하는 동작은 트랜잭션 밖으로 빼는게 좋음
- 커넥션은 서버 리소스(특히 RDBMS)에서 가장 비싼 자원 중 하나다.
트랜잭션 @Transactional 설정변경
1. Isolation(격리수준)
@Transactional(isolation = isolation.DEFAULT) : 지정한 메소드는 해당 격리수준으로 트랜잭션이 실행됨
격리수준을 명시할때 : 데이터베이스 읽기 작업에서 트랜잭션
특정 읽기 작업이 여러 단계로 나뉘어져 실행되며, 각 단계가 같은 데이터를 기반으로 수행되어야 하는 경우 트랜잭션이 필요합니다. 트랜잭션을 사용해 읽기 작업을 묶어 원자성을 보장하면, 중간에 다른 트랜잭션이 개입하지 않도록 하여 안정성을 높일 수 있습니다. 읽기 트랜잭션에 특정 격리 수준을 설정함으로써, 다른 트랜잭션에서 수행된 데이터 변경이 현재 트랜잭션에 미치는 영향을 제어할 수 있습니다.
일관성 보장이 필요하지 않은 경우: 게시판이나 뉴스 피드처럼 일관성이 엄격히 요구되지 않는 조회 작업에서는 트랜잭션을 사용하지 않아도 무방합니다. 트랜잭션을 사용하지 않으면 성능이 향상되며, 각 쿼리가 별도의 독립적인 읽기 작업으로 처리됩니다.
트랜잭션에서 일관성이 없는 데이터를 허용하는 수준으로 위의 격리성에서 표기한 격리수준. 일반적으로 격리수준은 기본적으로 default로 건드리지 않고, 매우 중요한 정보에 대해서는 repeatable_read나 serializable로 엄격하게 관리하여 특별적용한다.
- DEFAULT : 사용하고 있는 DB의 기본 격리수준을 따르겠다
- READ_UNCOMMITED : 한 트랜잭션이 커밋하지도 않았는데 다른 트랜잭션이 데이터를 조회할 수 있음 (Dirty Read발생)
- READ_COMMITED : 한 트랜잭션이 끝나면 그것을 읽을 수 있다. 나의 트랜잭션이 길이가 길면 초반부 트랜잭션과 마지막 부분의 트랜잭션에서 데이터의 값이 달라지는 문제가있다.(Dirty Read 방지)
- REPEATABLE_READ : 한 트랜잭션이 완료될때까지, 특정 영역을 shared lock을 걸어서 다른 사용자는 해당하는 영역을 조회할 수 없음. 시작하는 순간 해당 데이터에 대해서 snapshot을 찍고, 트랜잭션에 의해서 데이터가 변경되더라도 데이터 변경이 없는 것처럼 snapshot의 데이터를 보여준다. (Non-repeateable read방지)
- SERIALIZABLE : 트랜잭션이 사용하고 있는 영역에 모두 shared lock을 사용해서 다른 트랜잭션이 해당영역에 접근할 수 없음. DB가 한번에 한가지 트랜잭션만 실행하여 100% 독립성 (Phantom Read 방지)
2. Propagation (전파수준)
Using @Transactional in private methods | Spring AOP doesn’t work on private methods. Use public. |
한 메서드가 트랜잭션 안에서 실행 중일 때, 다른 메서드를 호출하면 트랜잭션을 어떻게 처리할 것인가를 정하는 전략입니다. 트랜잭션 동작 도중 다른 트잭션을 호출 하는 상황. A트랜잭션 중 B트랜잭션을 호출하는 경우, B트랜잭션을 어떻게 취급할 것인가에 대한 상황이다. A는 부모, B는 자식이라고 칭하자.
- REQUIRED : default값으로 설정됨. 부모 트랜잭션 안에서 자식 트랜잭션을 연계하여 하나로 포함하여 여긴다. 부모 트랜잭션이 존재하지 않으면 자식은 독단적으로 트랜잭션을 생성한다.
- REQUIRES_NEW : 부모 트랜잭션 안에서 자식 트랜잭션이 별도로 생성된다. 서로 트랜잭션간 영향을 받지 않는다.
- SUPPORTS : 부모 트랜잭션 내에서 자식 트랜잭션이 호출되면 하나의 트랜잭션으로 인식되지만, 부모 트랜잭션이 없는 상태에서는 호출되면 트랜잭션을 별도로 만들지 않는다.
- NESTED : 부모 트랜잭션이 있는 경우, 자식 트랜잭션을 중첩트랜잭션으로 생성한다. 이는, 부모트랜잭션은 자식트랜잭션에 종속되지 않지만, 자식 트랜잭션은 부모트랜잭션에 종속되도록 만든다.
ex) 사용자 일기 작성 관련해서 프로그램 로그를 DB에 저장하는 상황이라면, 프로그램 로그가 실패한다고 사용자 일기 작성이 롤백이 되면 안될것. 그러나, 사용자 일기 작성이 실패하여 롤백할 경우에는 로그 DB도 롤백해야한다.
4. 트랜잭션 롤백 예외
@Transactional(rollbackFor =Exception.class)
@Transactional(norollbackFor=Exception.class)
특정 예외가 발생했을때에만 트랜잭션 롤백시킬 경우를 설정. default로 runtime exception, error 발생할때 롤백을 하도록
4. Timeout속성
@Transactional(timeout=10)
일정 시간 내에서 트랜잭션 끝내지 못하면 롤백시킴.격리 수준이 빡빡해서 해당 작업이 문제가 생겨서 오랬동안 막혀있다면, 다른 트랜잭션도 해당 데이터를 사용하지 못하고 대기하고 있어서
그 외 : 데이터베이스 상에서도 트랜잭션 상 세부설정을 할 수 있음.
@Transactional (read only)
Transaction의 실무적인 사용
때문에 일반적으로 JPA 프로젝트는 서비스단 최상위에 @Transactional(read=only)를 붙여서 모두 Persistence Context를 사용4
- .
- Rollback Management:
Contrary to the statement, rollback is a crucial aspect of @Transactional. By default, it automatically rolls back the transaction for runtime exceptions
-
35. You can also configure custom rollback rules using attributes like rollbackFor and noRollbackFor5.
- Isolation Level Setting:
While setting the isolation level is indeed a feature of @Transactional, it's just one of several configurable aspects, not the main purpose45. - Propagation Behavior:
@Transactional allows you to specify how transactions should propagate across method calls15. - Read-Only Flag:
You can optimize performance for read-only operations by setting the readOnly flag5. - Timeout Configuration:
@Transactional allows you to set a timeout for the transactio
@Transactional
public <S extends T> S save(S entity) {
Assert.notNull(entity, "Entity must not be null");
if (entityInformation.isNew(entity)) {
entityManager.persist(entity);
return entity;
} else {
return entityManager.merge(entity);
}
}
'개발기술 > ORM' 카테고리의 다른 글
JPA 데이터 조회쿼리 정의 ; JPA Method, JPQL, QueryDSL, 네이티브SQL (0) | 2025.03.05 |
---|---|
JPA 기타기능 (Pageable, auditing, data.sql 기능) (0) | 2025.02.28 |
JPA 활용의 필요성과 활용법 (0) | 2025.02.27 |
Spring JPA Entity 설정 (2) | 2024.10.28 |
Spring JPA 개념, 초기설정, Repository 인터페이스 구현 (0) | 2024.07.22 |