1. Transaction Stretegy
- Spring이 트랜잭션을 어떻게 처리할지 정의
- TransactionManager 인터페이스에서 담당
2. TransactionDefinition
- 트랜잭션의 동작 방식 정의
코드) TransactionDefinition 인터페이스
더보기
public interface TransactionDefinition {
int PROPAGATION_REQUIRED = 0;
int PROPAGATION_SUPPORTS = 1;
int PROPAGATION_MANDATORY = 2;
int PROPAGATION_REQUIRES_NEW = 3;
int PROPAGATION_NOT_SUPPORTED = 4;
int PROPAGATION_NEVER = 5;
int PROPAGATION_NESTED = 6;
int ISOLATION_DEFAULT = -1; // 데이터베이스 기본 격리 수준 사용
int ISOLATION_READ_UNCOMMITTED = 1; // same as java.sql.Connection.TRANSACTION_READ_UNCOMMITTED;
int ISOLATION_READ_COMMITTED = 2; // same as java.sql.Connection.TRANSACTION_READ_COMMITTED;
int ISOLATION_REPEATABLE_READ = 4; // same as java.sql.Connection.TRANSACTION_REPEATABLE_READ;
int ISOLATION_SERIALIZABLE = 8; // same as java.sql.Connection.TRANSACTION_SERIALIZABLE;
int TIMEOUT_DEFAULT = -1;
default int getPropagationBehavior() { return PROPAGATION_REQUIRED; }
default int getIsolationLevel() { return ISOLATION_DEFAULT; }
default int getTimeout() { return TIMEOUT_DEFAULT; }
default boolean isReadOnly() { return false; }
@Nullable
default String getName() { return null; }
static TransactionDefinition withDefaults() { return StaticTransactionDefinition.INSTANCE; }
}
Propagation
- 트랜잭션이 다른 트랜잭션 컨텍스트 내에서 어떻게 동작해야 하는지 정의
Propagation | 외부 트랜잭션 존재 시 | 외부 트랜잭션 없을 시 | 예외 발생 여부 | 기타 설명 |
REQUIRED | Join | New | ❌ |
가장 일반적인 설정
|
SUPPORTS | Join | None | ❌ |
트랜잭션 있어도 되고 없어도 됨
|
MANDATORY | Join | ❌ | ✅ |
반드시 외부 트랜잭션 있어야 함
|
REQUIRES_NEW | New (외부 트랜잭션 일시정지) |
New | ❌ |
기존 트랜잭션 일시 정지
(내부 트랜잭션 먼저 수행됨) |
NOT_SUPPORTED | None (외부 트랜잭션 일시정지) |
None | ❌ | 트랜잭션 무시 |
NEVER | ❌ | None | ✅ |
트랜잭션 있으면 안 됨
|
NESTED | Join (Savepoint) |
New | ❌ |
부모 트랜잭션이 있어야 효과적
DataSourceTransactionManager만 지원 |
예제) REQUIRED
더보기
1. 외부 트랜잭션 존재 O (JOIN)
@Transactional
public void join_required(String username) {
memberRepository.save(username);
if (username.contains("fail")) {
logService.save("비정상 로그: " + username);
throw new RuntimeException("[exception] MemberService#join (가입 실패)");
}
logService.save("정상 로그: " + username);
}
memberService.join_required_transactional("user");
memberService.join_required_transactional("fail_user");
- logService에서 예외 발생 X: member, log 테이블 모두 기록 O
- logService에서 예외 발생 O: member, log 테이블 모두 기록 X. 전체 롤백됨
2. 외부 트랜잭션 존재 X (NEW)
public void join_required_nontransactional(String username) {
memberRepository.save(username);
if (username.contains("fail")) {
logService.save("비정상 로그: " + username);
throw new RuntimeException("[exception] MemberService#join (가입 실패)");
}
logService.save("정상 로그: " + username);
}
@Transactional
public void save(String msg) {
logRepository.save(msg);
}
memberService.required_nontransactional("user");
memberService.required_nontransactional("fail_user");
- NEW 이므로 log 기록은 logService.save() 내부에서 예외 발생 여부에 따라 결정됨
예제) SUPPORTS
더보기
1. 외부 트랜잭션 존재 X (NONE)
public void supports_nontransactional(String username) {
memberRepository.save(username);
logService.save_supports("정상 로그: " + username);
throw new RuntimeException("[exception] MemberService#join (가입 실패)");
}
@Transactional(propagation = Propagation.SUPPORTS)
public void save_supports(String msg) {
logRepository.save(msg);
throw new RuntimeException("[exception] LogService#save (로그기록 실패)");
}
memberService.supports_nontransactional("user");
- 예외 발생 관련 없이 무조건 log 기록 O
- 외부 트랜잭션이 없으므로 NONE으로 동작하고, jdbc 기본 동작인 auto-commit이 동작됨
- db에 즉시 자동으로 반영됨
- 트랜잭션이 없으므로 롤백이 불가함
예제) MANDATORY
더보기
1. 외부 트랜잭션 존재 X (EXCEPTION)
public void mandatory_exception(String username) {
memberRepository.save(username);
logService.save_mandatory("정상 로그: " + username);
}
@Transactional(propagation = Propagation.MANDATORY)
public void save_mandatory(String msg) {
logRepository.save(msg);
}
memberService.mandatory_exception("user");
- 외부 트랜잭션 없을 경우, 항상 예외 발생
예제) REQUIRES_NEW
더보기
1. 외부 트랜잭션 존재 O (NEW)(외부 트랜잭션 일시정지)
@Transactional
public void pause_required_new_transactional(String username) {
memberRepository.save(username);
try {
logService.save_requires_new("정상 로그: " + username);
} catch (RuntimeException e) {
System.out.println(e.getMessage());
}
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void save_requires_new(String s) {
logRepository.save(s);
throw new RuntimeException("[exception] LogService#save (로그기록 실패)");
}
memberService.pause_required_new_transactional("user");
- 내부 -> 외부 순으로 처리됨
- 내부에서 예외 발생하면, 내부에서만 롤백됨
- 외부에 영향 X
- 단, 던져진 예외를 처리하지 않으면 외부에 영향 O
예제) NESTED
더보기
1. 외부 트랜잭션 존재 O (Join)(Savepoint)
@Transactional
public void join_nested_transactional(String username) {
memberRepository.save(username);
try {
logService.save_nested("정상 로그: " + username);
} catch (RuntimeException e) {
System.out.println(e.getMessage());
}
}
@Transactional(propagation = Propagation.NESTED)
public void save_nested(String s) {
logRepository.save(s);
throw new RuntimeException("[exception] LogService#save (로그기록 실패)");
}
memberService.join_nested_transactional("user");
- 동작은 REQUIRES_NEW와 같음
- 내부 동작이 다름
- 하나의 트랜잭션 내에서 SAVEPOINT로 롤백 구간을 정해 외부 트랜잭션을 유지함
Isolation
- 트랜잭션이 다른 트랜잭션으로 얼마나 독립적으로 동작하는지 정의
- 레벨이 높아질수록 데이터 무결성은 높아지지만, 성능상 비용이 많이 듬
Isolation Level | Dirty Read | Non-repeatable Read | Phantom Read | 설명 | 성능 부담 |
READ_UNCOMMITTED | ✅ | ✅ | ✅ | 커밋되지 않은 데이터도 읽을 수 있음 | 매우 낮음 |
READ_COMMITTED | ❌ | ✅ | ✅ | 커밋된 데이터만 읽음 (가장 많이 사용되는 기본 설정: Oracle 등) |
낮음 |
REPEATABLE_READ | ❌ | ❌ | ✅ | 읽기 잠금 - 동일 쿼리 결과가 트랜잭션 동안 동일함 (MySQL 기본값) |
중간 |
SERIALIZABLE | ❌ | ❌ | ❌ | 읽기 잠금, 쓰기 잠금 - 트랜잭션을 순차적으로 처리 |
매우 높음 |
용어 | 설명 |
Dirty Read |
다른 트랜잭션이 커밋하지 않은 데이터를 읽는 것
|
Non-repeatable Read |
같은 데이터를 읽었는데 값이 바뀌는 현상
|
Phantom Read |
같은 조건으로 검색했는데 행(row) 수가 바뀌는 현상
|
예제) READ_COMMITTED (Non-repeatable Read)
더보기
@Transactional
public void updateUserName() {
sleep(1000);
memberRepository.updateUsernameById("Dirty", 1);
System.out.println("갱신 완료");
}
@Transactional(isolation = Isolation.READ_COMMITTED)
public void readCommitted() {
System.out.println("첫번째 조회: "+memberRepository.findUsernameById(1));
sleep(3000);
System.out.println("두번째 조회: "+memberRepository.findUsernameById(1));
}
Thread t1 = new Thread(() -> memberService.updateUserName());
Thread t2 = new Thread(() -> memberService.readCommitted());
t1.start();
t2.start();
t1.join();
t2.join();
첫번째 조회: user
갱신 완료
두번째 조회: Dirty
- 같은 데이터에 대해 다른 트랜잭션에서 수정한 값을 읽게됨
Timeout
- 트랜잭션이 얼마나 오래 실행될 수 있는지 설정
Read-only
- 트랜잭션이 데이터 읽기만 할지 여부 결정
3. TrasactionStatus
구분 | 메서드 / 기능 | 설명 |
트랜잭션 상태 확인
|
isNewTransaction() |
현재 트랜잭션이 새로 시작된 것인지, 아니면 기존 트랜잭션에 참여한 것인지 확인
|
isRollbackOnly() |
현재 트랜잭션이 롤백 전용 상태인지 확인
|
|
isCompleted() |
현재 트랜잭션이 완료되었는지 확인 (커밋 or 롤백 여부)
|
|
트랜잭션 동작 | flush() |
현재까지의 변경사항을 DB에 강제로 반영 (JPA에서는 영속성 컨텍스트 → DB)
|
트랜잭션 설정 | setRollbackOnly() |
현재 트랜잭션을 롤백 전용으로 설정 (이후 커밋 불가능, 반드시 롤백됨)
|
예제) Service에서 사용법
더보기
@Service
public class ExampleService {
@Autowired
private PlatformTransactionManager transactionManager;
@Autowired
private JdbcTemplate jdbcTemplate;
public void performTransactionalOperation() {
// 트랜잭션 정의
DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
definition.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
// 트랜잭션 시작
TransactionStatus status = transactionManager.getTransaction(definition);
try {
// 트랜잭션이 새로운 것인지 확인
if (status.isNewTransaction()) { ... }
// 데이터베이스 작업 수행
jdbcTemplate.update("INSERT INTO my_table (column1) VALUES (?)", "Test Value");
// 플러시를 통해 쓰기 작업을 강제로 데이터베이스에 반영
status.flush();
// 비즈니스 로직에 따른 롤백 전용 설정
if (/* 어떤 조건 */) { status.setRollbackOnly(); }
// 롤백 전용 상태인지 확인
if (status.isRollbackOnly()) {
System.out.println("트랜잭션은 롤백 전용 상태로 설정되었습니다.");
// 명시적으로 롤백
transactionManager.rollback(status);
return;
}
// 트랜잭션 커밋
transactionManager.commit(status);
} catch (Exception e) {
// 예외 발생 시 트랜잭션 롤백
transactionManager.rollback(status);
}
}
}
4. TransactionManager (PlatformTransactionManager)
- Spring 트랜잭션 관리의 핵심
장점 | 설명 |
추상화 |
기술이나 트랜잭션 종류에 상관없이 동일한 방식으로 트랜잭션 처리 가능 (@Transactional 등)
|
비침투적 |
비즈니스 로직에 트랜잭션 처리 코드가 개입되지 않음 → 코드가 깔끔하고 유지보수 용이 (try-catch-finally 없음)
|
코드) PlatformTransactionManager
더보기
public interface PlatformTransactionManager extends TransactionManager {
TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
Implementataion
TransactionManager 종류
|
설명 | 트랜잭션 범위 | 주요 연동 대상 |
DataSourceTransactionManager | JDBC 기반 데이터 소스 트랜잭션 관리 | Local Transaction | JDBC DataSource |
HibernateTransactionManager | Hibernate와 연동된 트랜잭션 관리 (start, commit, rollback 직접 제어) |
Local Transaction | Hibernate SessionFactory |
JpaTransactionManager | JPA 전용 트랜잭션 매니저 (@Transactional로 메서드 단위 관리) |
Local Transaction | JPA EntityManagerFactory |
JtaTransactionManager | 분산 트랜잭션 관리 여러 리소스 참여 가능 |
Global Transaction |
JTA
여러 트랜잭션 리소스 |
예제) HibernateTransactionManager
더보기
@Configuration
@EnableTransactionManagement
public class DatabaseConfig {
@Autowired
private DataSource dataSource; // DataSource는 DB 연결을 관리
@Bean
public LocalSessionFactoryBean sessionFactory() {
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(dataSource);
sessionFactory.setPackagesToScan("com.example.myapp.entity"); // 엔티티 클래스가 있는 패키지 설정
sessionFactory.setHibernateProperties(hibernateProperties());
return sessionFactory;
}
@Bean
public PlatformTransactionManager hibernateTransactionManager(SessionFactory sessionFactory) {
return new HibernateTransactionManager(sessionFactory);
}
private Properties hibernateProperties() {
Properties properties = new Properties();
properties.put("hibernate.dialect", "org.hibernate.dialect.MySQLDialect");
properties.put("hibernate.show_sql", "true");
properties.put("hibernate.format_sql", "true");
properties.put("hibernate.hbm2ddl.auto", "update");
return properties;
}
}
LocalSessionFactoryBean
- SessionFactory 설정 및 초기화
- Spring 컨테이너에 의해 트랜잭션을 관리할 수 있음 (PlatformTransactionManager와 통합됨)
예제) JpaTransactionManager
더보기
@Configuration
@EnableTransactionManagement
public class DatabaseConfig {
@Autowired
private DataSource dataSource; // DataSource는 DB 연결을 관리
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource) {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource);
em.setPackagesToScan("com.example.myapp.entity"); // JPA 엔티티 클래스 패키지 스캔
// Hibernate JPA 공급자 설정
em.setJpaVendorAdapter(new org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter());
return em;
}
@Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory());
}
}
LocalContainerEntityManagerFactoryBean
- JPA의 EntityManagerFactory 설정
- JPA와 Hibernate를 쉽게 연동하도록 도와줌
예제) JtaTransactionManager
더보기
@Bean
public JtaTransactionManager transactionManager() {
return new JtaTransactionManager();
}
5. DataSource
- 데이터베이스 연결 관리
예제) DataSource
더보기
@Bean
public DataSource dataSource() {
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/spring_dataaccess?serverTimezone=UTC");
ds.setUsername("sa");
ds.setPassword("12345678");
return ds;
}
출처