1. (어제 듣다 남은) 영한님의 스프링 DB 1편 [섹션 6 : 스프링과 문제 해결 - 예외처리, 반복] 강의를 들었다.
스프링의 예외 추상화를 자세히 본 건 처음이라 신기했다!!
📍 스프링의 데이터 접근 관련 예외 추상화
스프링은 데이터 접근 계층에 대한 수십 가지 예외를 정리해서 일관된 예외 계층을 제공한다.
각각의 예외는 특정 기술에 종속적이지 않게 설계되어 있다.
따라서 서비스 계층에서도 스프링이 제공하는 예외를 사용하면 된다.
예를 들어서 JDBC 기술을 사용하든, JPA 기술을 사용하든 스프링이 제공하는 예외를 사용하면 된다.
+ JDBC나 JPA를 사용할 때 발생하는 예외를 스프링이 제공하는 예외로 변환해주는 역할도 스프링이 제공한다.
예외 최상위 계층은 org.springframework.dao.DataAccessException 이다. (런타임 예외 상속!)
- Transient 예외 : 동일한 SQL을 다시 시도했을 때 성공할 가능성이 있다. (일시적 예외)
ex) 쿼리 타임아웃, 락과 관련된 오류들 -> 데이터베이스 상태가 좋아지거나, 락이 풀렸을 때 다시 시도하면 성공할 수 도 있다.
- NonTransient 예외 : 같은 SQL을 그대로 반복해서 실행하면 실패한다. (일시적이지 않은 예외)
ex) SQL 문법 오류, 데이터베이스 제약조건 위배
📍 스프링이 제공하는 예외 변환기
스프링은 데이터베이스에서 발생하는 오류 코드를 스프링이 정의한 예외로 자동으로 변환해주는 변환기를 제공한다. (이게 없다면 개발자가 다 하나씩 바꿔줘야함)
SQLExceptionTranslator exTranslator = new SQLErrorCodeSQLExceptionTranslator(dataSource);
DataAccessException resultEx = exTranslator.translate("blahblah", sql, e);
스프링은 데이터 접근 계층에 대한 일관된 예외 추상화를 제공한다.
스프링은 예외 변환기를 통해서 SQLException 의 ErrorCode 에 맞는 적절한 스프링 데이터 접근 예외로 변환해준다.
만약 서비스, 컨트롤러 계층에서 예외 처리가 필요하면 특정 기술에 종속적인 SQLException 같은 예외를 직접 사용하는 것이 아니라, 스프링이 제공하는 데이터 접근 예외를 사용하면 된다.
스프링 예외 추상화 덕분에 특정 기술에 종속적이지 않게 되었다. 이제 JDBC에서 JPA같은 기술로 변경되어도 예외로 인한 변경을 최소화 할 수 있다. 향후 JDBC에서 JPA로 구현 기술을 변경하더라도, 스프링은 JPA 예외를 적절한 스프링 데이터 접근 예외로 변환해준다.
물론 스프링이 제공하는 예외를 사용하기 때문에 스프링에 대한 기술 종속성은 발생한다. (스프링에 대한 기술 종속성까지 완전히 제거하려면 예외를 모두 직접 정의하고 예외 변환도 직접 하면 되지만, 실용적인 방법은 아니다.)
📍 정리
- 서비스 계층의 순수성
- 트랜잭션 추상화 + 트랜잭션 AOP 덕분에 서비스 계층의 순수성을 최대한 유지하면서 서비스 계층에서 트랜잭션을 사용할 수 있다.
- 스프링이 제공하는 예외 추상화와 예외 변환기 덕분에, 데이터 접근 기술이 변경되어도 서비스 계층의 순수성을 유지하면서 예외도 사용할 수 있다.
- 서비스 계층이 리포지토리 인터페이스에 의존한 덕분에 향후 리포지토리가 다른 구현 기술로 변경되어도 서비스 계층을 순수하게 유지할 수 있다.
- 리포지토리에서 JDBC를 사용하는 반복 코드가 JdbcTemplate 으로 대부분 제거되었다.
2. 영한님의 스프링 DB 2편 [섹션 9 : 스프링 트랜잭션 이해] 강의를 들었다.
@Transactional에 대해 자세히 알아보는 강의!
📍 트랜잭션 프록시
- @Transactional 애노테이션이 특정 클래스나 메서드에 하나라도 있으면 있으면 트랜잭션 AOP는 프록시를 만들어서 스프링 컨테이너에 등록한다.
그리고 실제 basicService 객체 대신에 프록시인 basicService$$CGLIB 를 스프링 빈에 등록한다.
그리고 프록시는 내부에 실제 basicService 를 참조하게 된다. - 클라이언트인 txBasicTest 는 스프링 컨테이너에 @Autowired BasicService basicService 로 의존관계 주입을 요청한다. 스프링 컨테이너에는 실제 객체 대신에 프록시가 스프링 빈으로 등록되어 있기 때문에 프록시를 주입한다.
(프록시는 BasicService 를 상속해서 만들어지기 때문에 다형성을 활용할 수 있다. 따라서 BasicService 대신에 프록시인 BasicService$$CGLIB 를 주입할 수 있다.) - 실제 작동 과정 : 프록시가 트랜잭션 시작 후에 실제 basicService를 호출하고 끝나면 프록시가 트랜잭션 커밋 또는 롤백
+) TransactionSynchronizationManager.isActualTransactionActive()를 사용하면 현재 쓰레드에 트랜잭션이 적용되어 있는지 확인할 수 있다.
📍 트랜잭션 적용 위치에 따른 우선순위
1. 클래스의 메서드
2. 클래스의 타입
(3. 인터페이스의 메서드 / 4. 인터페이스의 타입)
그런데 인터페이스에 @Transactional 사용하는 것은 스프링 공식 메뉴얼에서 권장하지 않는 방법이다. AOP를 적용하는 방식에 따라서 인터페이스에 애노테이션을 두면 AOP가 적용이 되지 않는 경우도 있기 때문이다. 가급적 구체 클래스에 @Transactional 을 사용하자.
📍 트랜잭션 AOP 주의 사항 1 - 프록시 내부 호출 (⭐️⭐️⭐️⭐️⭐️ 매우 중요)
프록시 방식의 AOP 한계
: @Transactional 를 사용하는 트랜잭션 AOP는 프록시 방식의 AOP를 사용한다.
프록시를 사용하면 메서드 내부 호출에 프록시를 적용할 수 없다.
대상 객체의 내부에서 메서드 호출이 발생하면 프록시를 거치지 않고 대상 객체를 직접 호출하는 문제가 발생한다.
이렇게 되면 @Transactional 이 있어도 트랜잭션이 적용되지 않는다.
앞서 배운 것 처럼 @Transactional 을 적용하면 프록시 객체가 요청을 먼저 받아서 트랜잭션을 처리하고, 실제 객체를 호출해준다.
따라서 트랜잭션을 적용하려면 항상 프록시를 통해서 대상 객체(Target)을 호출해야 한다.
그래야 프록시에서 먼저 트랜잭션을 적용하고, 이후에 대상 객체를 호출하게 된다.
만약 프록시를 거치지 않고 대상 객체를 직접 호출하게 되면 AOP가 적용되지 않고, 트랜잭션도 적용되지 않는다.
(cf. internal -> external은 tx 유지되더라)
그렇다면 이 문제를 어떻게 해결할 수 있을까?
가장 단순한 방법은 내부 호출을 피하기 위해 internal() 메서드를 별도의 클래스로 분리하는 것이다. (내부호출을 외부호출로 변경)

📍 트랜잭션 AOP 주의 사항 2 - 초기화 시점
스프링 초기화 시점에는 트랜잭션 AOP가 적용되지 않을 수 있다.
초기화 코드(예: @PostConstruct )와 @Transactional 을 함께 사용하면 트랜잭션이 적용되지 않는다.
@PostConstruct
@Transactional
public void initV1() {
boolean isActive = TransactionSynchronizationManager.isActualTransactionActive();
log.info("Hello init @PostConstruct tx active={}", isActive); // false
}
왜냐하면 초기화 코드가 먼저 호출되고, 그 다음에 트랜잭션 AOP가 적용되기 때문이다.
따라서 초기화 시점에는 해당 메서드에서 트랜잭션을 획득할 수 없다.
가장 확실한 대안은 ApplicationReadyEvent 이벤트를 사용하는 것이다.
@EventListener(value = ApplicationReadyEvent.class)
@Transactional
public void init2() {
boolean isActive = TransactionSynchronizationManager.isActualTransactionActive();
log.info("Hello init ApplicationReadyEvent tx active={}", isActive); // true
}
이 이벤트는 트랜잭션 AOP를 포함한 스프링이 컨테이너가 완전히 생성되고 난 다음에 이벤트가 붙은 메서드를 호출해준다. (초기화 이후, AOP 만든 이후)
따라서 트랜잭션이 적용된다.
📍 트랜잭션 옵션
스프링 트랜잭션은 다양한 옵션을 제공한다.
- value, transactionManager
- 트랜잭션을 사용하려면 먼저 스프링 빈에 등록된 어떤 트랜잭션 매니저를 사용할지 알아야 한다.
- 이 값을 생략하면 기본으로 등록된 트랜잭션 매니저를 사용하기 때문에 대부분 생략한다.
- rollbackfor
- 예외 발생시 스프링 트랜잭션의 기본 정책은 다음과 같다.
- 언체크 예외인 RuntimeException , Error 와 그 하위 예외가 발생하면 롤백한다.
- 체크 예외인 Exception 과 그 하위 예외들은 커밋한다.
-
이 옵션을 사용하면 기본 정책에 추가로 어떤 예외가 발생할 때 롤백할 지 지정할 수 있다.
- 예외 발생시 스프링 트랜잭션의 기본 정책은 다음과 같다.
- noRollbackFor
- 기본 정책에 추가로 어떤 예외가 발생했을 때 롤백하면 안되는지 지정할 수 있다.
- isolation
- 트랜잭션 격리 수준을 지정할 수 있다.
- 기본 값은 데이터베이스에서 설정한 트랜잭션 격리 수준을 사용하는 DEFAULT 이다.
- 대부분 데이터베이스에서 설정한 기준을 따른다.
애플리케이션 개발자가 트랜잭션 격리 수준을 직접 지정하는 경우는 드물다.
- timeout
- 트랜잭션 수행 시간에 대한 타임아웃을 초 단위로 지정한다.
- 기본 값은 트랜잭션 시스템의 타임아웃을 사용한다.
- 운영 환경에 따라 동작하는 경우도 있고 그렇지 않은 경우도 있기 때문에 꼭 확인하고 사용해야 한다.
- readonly
- 트랜잭션은 기본적으로 읽기 쓰기가 모두 가능한 트랜잭션이 생성된다.
- readOnly=true 옵션을 사용하면 읽기 전용 트랜잭션이 생성된다.
- 이 경우 등록, 수정, 삭제가 안되고 읽기 기능만 작동한다. (드라이버나 데이터베이스에 따라 정상 동작하지 않는 경우도 있다.)
- readOnly 옵션을 사용하면 읽기에서 다양한 성능 최적화가 발생할 수 있다.
📍 예외와 트랜잭션 커밋, 롤백
예외가 발생했는데, 내부에서 예외를처리하지못하고,
트랜잭션범위(@Transactional가 적용된AOP) 밖으로 예외를 던지면 어떻게 될까?

예외 발생시 스프링 트랜잭션 AOP는 예외의 종류에 따라 트랜잭션을 커밋하거나 롤백한다.
- 언체크 예외(RuntimeException , Error 와 그 하위 예외) : 트랜잭션 롤백
- 체크 예외(Exception 과 그 하위 예외) : 트랜잭션 커밋
- 물론 정상 응답(리턴)하면 트랜잭션을 커밋한다.
rollbackFor 옵션을 사용하면 기본 정책에 추가로 어떤 예외가 발생할 때 롤백할 지 지정할 수 있다.
@Transactional(rollbackFor = Exception.class)
예를 들어서 이렇게 지정하면 체크 예외인 Exception 이 발생해도 커밋 대신 롤백된다. (자식 타입도 롤백된다.)
📍 스프링은 왜 체크 예외는 커밋하고, 언체크(런타임) 예외는 롤백할까?
스프링은 기본적으로 체크 예외는 비즈니스 의미가 있을 때 사용하고, 런타임(언체크) 예외는 복구 불가능한 예외로 가정하기 때문이다.
- 체크 예외: 비즈니스 의미가 있을 때 사용 -> 그러니까 커밋해줄게~
- 언체크 예외: 복구 불가능한 예외 -> 문제네. 롤백!
(참고로 꼭 이런 정책을 따를 필요는 없다. 그때는 앞서 배운 rollbackFor 라는 옵션을 사용해서 체크예외도 롤백하면 된다.)
그런데 비즈니스 의미가 있는 비즈니스 예외라는 것이 무슨 뜻일까? 간단한 예제로 알아보자.
주문을 하는데 상황에 따라 다음과 같이 조치한다.
1. 정상: 주문시 결제를 성공하면 주문 데이터를 저장하고 결제 상태를 완료 로 처리한다.
2. 시스템 예외: 주문시 내부에 복구 불가능한 예외가 발생하면 전체 데이터를 롤백한다.
3. 비즈니스 예외: 주문시 결제 잔고가 부족하면 주문 데이터를 저장하고, 결제 상태를 대기 로 처리한다.
-> 이 경우 고객에게 잔고 부족을 알리고 별도의 계좌로 입금하도록 안내한다.
이때 결제 잔고가 부족하면 NotEnoughMoneyException 이라는 체크 예외가 발생한다고 가정하겠다.
이 예외는 시스템에 문제가 있어서 발생하는 시스템 예외가 아니다. 시스템은 정상 동작했지만, 비즈니스 상황에서 문제가 되기 때문에 발생한 예외이다. 이 경우 비즈니스 문제 상황을 예외를 통해 알려주기 때문에, 마치 예외가 리턴 값 처럼 사용된다.
따라서 이 경우에는 트랜잭션을 커밋하는 것이 맞다. 이 경우 롤백하면 생성한 Order 자체가 사라진다. 그러면 고객에게 잔고 부족을 알리고 별도의 계좌로 입금하도록 안내해도 주문( Order ) 자체가 사라지기 때문에 문제가 된다.
비즈니스 예외는 매우 중요하고, 반드시 처리해야 하는 경우가 많으므로 체크 예외를 고려할 수 있다.
그런데 비즈니스 상황에 따라 체크 예외의 경우에도 트랜잭션을 커밋하지 않고, 롤백하고 싶을 수 있다.
이때는 rollbackFor 옵션을 사용하면 된다.
=> 체크 예외는 비즈니스 상황에 따라서 커밋과 롤백을 선택하면 된다!
3. 영한님의 스프링 DB 2편 [섹션 10 : 스프링 트랜잭션 전파 1 - 기본] 강의를 들었다.
복잡한 트랜잭션 상황에서 스프링은 어떻게 처리하는지!
📍 트랜잭션 각각 수행
- 트랜잭션이 각각 수행되면서 사용되는 DB 커넥션도 각각 다르다.
- 이 경우 트랜잭션을 각자 관리하기 때문에 전체 트랜잭션을 묶을 수 없다.
예를 들어서 트랜잭션1이 커밋하고, 트랜잭션2가 롤백하는 경우 트랜잭션1에서 저장한 데이터는 커밋되고, 트랜잭션2에서 저장한 데이터는 롤백된다.
📍 트랜잭션 속 트랜잭션
트랜잭션을 각각 사용하는 것이 아니라, 트랜잭션이 이미 진행중인데, 여기에 추가로 트랜잭션을 수행하면 어떻게 될까?
기존 트랜잭션과 별도의 트랜잭션을 진행해야 할까? 아니면 기존 트랜잭션을 그대로 이어 받아서 트랜잭션을 수행해야 할까?
이런 경우 어떻게 동작할지 결정하는 것을 트랜잭션 전파(propagation)라 한다. 참고로 스프링은 다양한 트랜잭션 전파 옵션을 제공한다.
- 외부 트랜잭션이 수행중이고, 아직 끝나지 않았는데, 내부 트랜잭션이 수행된다.
- 외부 트랜잭션이라고 이름 붙인 것은 둘 중 상대적으로 밖에 있기 때문에 외부 트랜잭션이라 한다.
처음 시작된 트랜잭션으로 이해하면 된다. - 내부 트랜잭션은 외부에 트랜잭션이 수행되고 있는 도중에 호출되기 때문에 마치 내부에 있는 것 처럼 보여서 내부 트랜잭션이라 한다.
📍 내부 트랜잭션이 외부 트랜잭션에 참여 (REQUIRED)
스프링은 이 경우 외부 트랜잭션과 내부 트랜잭션을 묶어서 하나의 트랜잭션을 만들어준다.
내부 트랜잭션이 외부 트랜잭션에 참여하는 것이다.
(이것이 기본 동작이고, 옵션을 통해 다른 동작방식도 선택할 수 있다)

- 물리 트랜잭션 : 실제 데이터베이스에 적용되는 트랜잭션
실제 커넥션을 통해서 트랜잭션을 시작( setAutoCommit(false)) 하고, 실제 커넥션을 통해서 커밋, 롤백하는 단위 - 논리 트랜잭션 : 트랜잭션 매니저를 통해 트랜잭션을 사용하는 단위
논리 트랜잭션들은 하나의 물리 트랜잭션으로 묶인다. - 이러한 논리 트랜잭션 개념은 트랜잭션이 진행되는 중에 내부에 추가로 트랜잭션을 사용하는 경우에 나타난다.
단순히 트랜잭션이 하나인 경우 둘을 구분하지는 않는다. (더 정확히는 REQUIRED 전파 옵션을 사용하는 경우에 나타난다)
💫 대원칙 💫
모든 논리 트랜잭션이 커밋되어야 물리 트랜잭션이 커밋된다.
= 하나의 논리 트랜잭션이라도 롤백되면 물리 트랜잭션은 롤백된다.
= 모든 트랜잭션 매니저를 커밋해야 물리 트랜잭션이 커밋된다.
= 하나의 트랜잭션 매니저라도 롤백하면 물리 트랜잭션은 롤백된다.
📍 트랜잭션 참여란?
내부 트랜잭션이 외부 트랜잭션에 참여한다
= 내부 트랜잭션이 외부 트랜잭션을 그대로 이어 받아서 따른다
= 외부 트랜잭션의 범위가 내부 트랜잭션까지 넓어진다
= 외부에서 시작된 물리적인 트랜잭션의 범위가 내부 트랜잭션까지 넓어진다
= 외부 트랜잭션과 내부 트랜잭션이 하나의 물리 트랜잭션으로 묶인다
log.info("외부 트랜잭션 시작");
TransactionStatus outer = txManager.getTransaction(new DefaultTransactionAttribute());
log.info("outer.isNewTransaction()={}", outer.isNewTransaction()); // true
log.info("내부 트랜잭션 시작");
TransactionStatus inner = txManager.getTransaction(new DefaultTransactionAttribute());
log.info("inner.isNewTransaction()={}", inner.isNewTransaction()); // false
...
- 외부 트랜잭션은 처음 수행된 트랜잭션이다. 이 경우 신규 트랜잭션( isNewTransaction=true )이 된다.
-
내부 트랜잭션은 이미 진행중인 외부 트랜잭션에 참여한다. 이 경우 신규 트랜잭션이 아니다 ( isNewTransaction=false ).
📍 스프링은 어떻게 어떻게 외부 트랜잭션과 내부 트랜잭션을 묶어서 하나의 물리 트랜잭션으로 묶어서 동작하게 할까? (어떻게 commit 두번?)
- 외부 트랜잭션만 물리 트랜잭션을 시작하고, 커밋한다.
- 만약 내부 트랜잭션이 실제 물리 트랜잭션을 커밋하면 트랜잭션이 끝나버리기 때문에, 트랜잭션을 처음 시작한 외부 트랜잭션까지 이어갈 수 없다. 따라서 내부 트랜잭션은 DB 커넥션을 통한 물리 트랜잭션을 커밋하면 안된다.
- 스프링은 이렇게 여러 트랜잭션이 함께 사용되는 경우, 처음 트랜잭션을 시작한 외부 트랜잭션이 실제 물리 트랜잭션을 관리하도록 한다. 이를 통해 트랜잭션 중복 커밋 문제를 해결한다.
- 핵심은 트랜잭션 매니저에 커밋을 호출한다고해서 항상 실제 커넥션에 물리 커밋이 발생하지는 않는다는 것.
- 신규 트랜잭션인 경우에만 실제 커넥션을 사용해서 물리 커밋과 롤백을 수행한다. 신규 트랜잭션이 아니면 실제 물리 커넥션을 사용하지 않는다.
- 트랜잭션이 내부에서 추가로 사용되면, 트랜잭션 매니저를 통해 논리 트랜잭션을 관리하고, 모든 논리 트랜잭션이 커밋되면 물리 트랜잭션이 커밋된다고 이해하면 된다.
📍 내부 트랜잭션 롤백 & 외부 트랜잭션 커밋 경우
'내부 커밋 & 외부 커밋' 또는 '내부 커밋 & 외부 롤백'은 어렵지 않다.
문제는 '내부 롤백 & 외부 커밋' 경우.

- 내부 트랜잭션은 물리 트랜잭션을 롤백하지 않는 대신에 트랜잭션 동기화 매니저에 rollbackOnly=true 라는 표시를 해둔다.
- 외부 트랜잭션은 신규 트랜잭션이다. 따라서 DB 커넥션에 실제 커밋을 호출해야 한다.
이때 먼저 트랜잭션 동기화 매니저에 롤백 전용( rollbackOnly=true ) 표시가 있는지 확인한다.
롤백 전용 표시가 있으면 물리 트랜잭션을 커밋하는 것이 아니라 롤백한다. - 스프링은 이 경우 UnexpectedRollbackException 런타임 예외를 던진다.
그래서 커밋을 시도했지만, 기대하지 않은 롤백이 발생했다는 것을 명확하게 알려준다.
✏️ 여기까지 정리
- 논리 트랜잭션이 하나라도 롤백되면 물리 트랜잭션은 롤백된다.
- 내부 논리 트랜잭션이 롤백되면 롤백 전용 마크를 표시한다.
- 외부 트랜잭션을 커밋할 때 롤백 전용 마크를 확인한다.
롤백 전용 마크가 표시되어 있으면 물리 트랜잭션을 롤백하고, UnexpectedRollbackException 예외를 던진다.
📍 외부 트랜잭션과 내부 트랜잭션을 완전히 분리 (REQUIRES_NEW)

- 이렇게 외부 트랜잭션과 내부 트랜잭션을 완전히 분리해서 각각 별도의 물리 트랜잭션을 사용하려면
내부 트랜잭션을 시작할 때 REQUIRES_NEW 옵션을 사용하면 된다. - 이 전파 옵션을 사용하면 내부 트랜잭션을 시작할 때 기존 트랜잭션에 참여하는 것이 아니라
새로운 물리 트랜잭션을 만들어서 시작하게 된다. (물리 트랜잭션이 명확하게 분리) - 별도의 물리 트랜잭션을 가진다 = DB 커넥션을 따로 사용한다 = 커밋과 롤백 각각 별도로 이루어진다
- 내부 트랜잭션에 문제가 발생해서 롤백해도, 외부 트랜잭션에는 영향을 주지 않는다.
반대로 외부 트랜잭션에 문제가 발생해도 내부 트랜잭션에 영향을 주지 않는다.


REQUIRES_NEW 를 사용하면 데이터베이스 커넥션이 동시에 2개 사용된다는 점을 주의해야 한다.
쓰레드 하나가 커넥션 두개를 들고 있는 것이기 때문에 잘못하면 db 커넥션이 빨리 고갈될 수도 있다.
📍 그 외 다양한 전파 옵션
스프링은 다양한 트랜잭션 전파 옵션을 제공한다.
전파 옵션에 별도의 설정을 하지 않으면 REQUIRED 가 기본으로 사용된다.
참고로 실무에서는 대부분 REQUIRED 옵션을 사용한다.
그리고 아주 가끔 REQUIRES_NEW 을 사용하고, 나머지는 거의 사용하지 않는다. 그래서 나머지 옵션은 이런 것이 있다는 정도로만 알아두고 필요할 때 찾아보자.
- REQUIRED
- 가장 많이 사용하는 기본 설정이다. 기존 트랜잭션이 없으면 생성하고, 있으면 참여한다. 트랜잭션이 필수라는 의미로 이해하면 된다. (필수이기 때문에 없으면 만들고, 있으면 참여한다.)
- 기존 트랜잭션 없음: 새로운 트랜잭션을 생성한다.
- 기존 트랜잭션 있음: 기존 트랜잭션에 참여한다.
- REQUIRES_NEW
- 항상 새로운 트랜잭션을 생성한다.
- 기존 트랜잭션 없음: 새로운 트랜잭션을 생성한다.
- 기존 트랜잭션 있음: 새로운 트랜잭션을 생성한다.
- SUPPORT
- NOT_SUPPORT
- MANDATORY
- NEVER
- NESTED
📍 트랜잭션 전파와 옵션
isolation , timeout , readOnly 는 트랜잭션이 처음 시작될 때만 적용된다. 트랜잭션에 참여하는 경우에는 적용되지 않는다.
ex. REQUIRED : 트랜잭션 시작시에는 적용, 참여할때는 x
ex. REQUIRES_NEW : 항상 새로만들기 때문에 항상 적용
4. 기타
1) @PostConstruct
- 의존성 주입이 완료된 후에 실행되어야 하는 method에 사용
- 해당 어노테이션은 다른 리소스에서 호출되지 않아도 수행
- 호출 순서 : 생성자 호출 -> 의존성 주입 -> @PostConstruct
2) flush vs commit (JPA에서)
- flush : 영속성 컨텍스트에 있는 엔티티 정보를 DB에 동기화 하는 작업 (쿼리 전송) => rollback 가능
- commit : 트랜잭션을 끝내는 역할 => rollback 불가
3) 트랜잭션 격리 vs 트랜잭션 전파
- 트랜잭션 격리 : 동시에 여러 개의 트랜잭션이 실행되는 환경에서 트랜잭션 간의 영향을 분리하는 개념 (동시성 문제 관리)
- 트랜잭션 전파 : 여러 메서드 간에 트랜잭션을 확장하거나 전달하는 방식을 관리하는 개념 (트랜잭션 속 트랜잭션을 어떻게 동작시킬지)
감정회고
으아악 TIL 다 쓰고 올렸는데
자꾸 3번만 목차에 안올라가길래 거의 한시간반동안..? html 뒤적거렸다..
스킨 지웠다가 다시 깔아보기도 하고 스킨 만든 분 깃허브 가서 디스커션도 봤는데 답이 없어서 ㅜㅜ
글 전체 복사해서 새로운 글 올리고 기타등등 다 해보다가... 알고보니...
저 왼쪽 정렬?이 선택되어있으면 제목으로 인식을 못하더라.
그런데 웃긴게 저 정렬이 최초 선택은 되는데 그 이후에 선택 취소가 안된다.
알 수 없는 티스토리의 세계~~
앞으로는 목차(제목)만 미리 쫙 써놓고 본문 채워야겠다.