특징
- 독립성 : 하나의 트랜젝션은 다른 트랜젝션에 포함될수 없고 독립적이다.
- 일관성 : 작업 처리의 결과가 항상 일관된다.
- 원자성 : Database 에 모두반영 (Success) 혹은 모두 반영(Fail)되지 않음을 말한다.
- 지속성 : 성공적으로 완료된 결과는 영구적용되어야 한다.
트랜젝션 commit & rollback
- 성공상태이고 success로 반영된 상태
- 프로세스가 성공아닌 실패로 끝나거나 비정상적으로 종료 되었을때 앞서 성공한 프레세들은 하나로 묶여서 수행전 상태로 되돌아가야 한다.
트랜젝션의 논리적 5가지 상태
- Active : 현재 실행 중인 상태.
- Failed : 트랜젝션이 실행중 오류로 인한 중단.
- Aborted : 트랜잭션이 비정상 종료로 인하여 롤백 된 상태.
- Partially Committed : 트랜젝션의 연산이 마지막까지 실행 되고 Commit 되기 직전상태.
- Committed : 트랜젝션이 모두 성공적으로 진행되고 Commit 연산까지 실행된 상태.
그래서 Springboot 에서 트랜젝션은 어떻게 설정하고 사용하는데?
일반적으로 spring-boot-starter-jdbc 와 spring-boot-starter-data-jpa 를 추가하면 springboot 가 알아서 DataSourceTransactionManager 와 JpaTransactionManager 을 주입하기 때문에 사용자는 별도로 @Transactional 어노테이션을 붙일 필요가 없다.
앞서 작성한 spring data jap 다중 datasource 을 기준으로 테스트 코드를 작성해 본다.
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class ApplicationTests {
@Autowired
private UserRepository userRepository;
@Test
public void test() throws Exception {
// 10 data 입력
userRepository.save(new User("AAA", 10));
userRepository.save(new User("BBB", 20));
userRepository.save(new User("CCC", 30));
userRepository.save(new User("DDD", 40));
userRepository.save(new User("EEE", 50));
userRepository.save(new User("FFF", 60));
userRepository.save(new User("GGG", 70));
userRepository.save(new User("HHH", 80));
userRepository.save(new User("III", 90));
userRepository.save(new User("JJJ", 100));
// validation 생략
}
}
User 객체 @Max(50) 이므로 age가 50 보다 큰수입력시 Exception이 발생한다.
@Entity
@Data
@NoArgsConstructor
public class User {
@Id
@GeneratedValue
private Long id;
private String name;
@Max(50)
private Integer age;
public User(String name, Integer age) {
this.name = name;
this.age = age;
}
}
log
2022-12-26 11:55:29.581 ERROR 24424 --- [ main] o.h.i.ExceptionMapperStandardImpl : HHH000346: Error during managed flush [Validation failed for classes [com.blake.demo.User] during persist time for groups [javax.validation.groups.Default, ] List of constraint violations:[ ConstraintViolationImpl{interpolatedMessage='최대수치50을 초과할수 없습니다.', propertyPath=age, rootBeanClass=class com.didispace.chapter310.User, messageTemplate='{javax.validation.constraints.Max.message}'} ]]
database 확인
이미지에서 확인할수 있는것처럼 테스트진행중 예외가 발생하여 앞5개 데이터는 정상 입력되었지만 나머지 5개 데이터는 입력실패, 이때 모두 성공 or 실패로 처리하려면 트랜젝션기능을 이용할수 있다.
테스트코드를 살펴보자
@Test
@Transactional
public void test() throws Exception {
// test 내영은 생략
}
코드에서 처럼 @Transactional 어노테이션 하나만 달아주면 끝!
public interface UserService {
@Transactional
User update(String name, String password);
}
트랜잭션 상세
앞서 작성 글중에서 spring data jap 다중 datasource 사용하는 환경일경우 트랜젝션 선언시 트랜잭션 매니저를 지정해 줘야 한다. 예를 들면
@Transactional(value="transactionManagerPrimary")
트랜젝션 매니저를 지정하는것 외에도 우리는 트랜젝션을 격리,전파 등 컨트롤할수 있다.
격리(Isolation)
개념 : 트랜잭션에서 일관성이 없는 데이터를 허용하도록 하는 수준을 말한다.
위치 : org.springframework.transaction.annotation.Isolation 에서 확인할수 있다.
public enum Isolation {
DEFAULT(-1),
READ_UNCOMMITTED(1),
READ_COMMITTED(2),
REPEATABLE_READ(4),
SERIALIZABLE(8);
}
- DEFAULT : 기본 격리 수준(, DB의 Isolation Level을 따름)
- READ_UNCOMMITTED : 커밋되지 않는(트랜잭션 처리중인) 데이터에 대한 읽기를 허용
- READ_COMMITTED : 트랜잭션이 커밋 된 확정 데이터만 읽기 허용
- REPEATABLE_READ : 하나의 트랜잭션이 읽은 로우를 다른 트랜잭션이 수정하는 것을 막아준다. 선행 트랜잭션이 읽은 데이터는 트랜잭션이 종료될 때까지 후행 트랜잭션이 갱신/삭제를 보류한다.
- SERIALIZABLE : 가장 강력한 격리수준이다. 순차적으로 진행해서 여러 트랜잭션이 동시에 같은 테이블 정보를 엑세스하지 못하게 한다.
그럼 어떻게 지정하는데? 지정방법은 아래와 같다!
@Transactional(isolation = Isolation.DEFAULT)
트랜젝션의 전파(propagation)
개념 : propagation 속성을 이용해 호출한 쪽의 트랜잭션을 그대로 사용할 수도 있고, 새롭게 트랜잭션을 생성할 수도 있다. 트랜잭션의 경계를 정의하며 시작 방법을 결정하는 속성이다.
위치 : org.springframework.transaction.annotation.Propagation 에서 확인 할수 있다.
public enum Propagation {
REQUIRED(0),
SUPPORTS(1),
MANDATORY(2),
REQUIRES_NEW(3),
NOT_SUPPORTED(4),
NEVER(5),
NESTED(6);
}
- REQUIRED : 부모 트랜잭션 내에서 실행하며 부모 트랜잭션이 없을 경우 새로운 트랜잭션을 생성한다.
- SUPPORTS : 이미 진행중인 부모 트랜잭션이 있으면 참여하고, 없을 경우 Non-transaction으로 실행된다.
- MANDATORY : 이미 진행중인 트랜잭션이 반드시 있어야만 실행되며, 혼자서는 독립적으로 트랜잭션을 진행하면 안 되는 경우에 사용한다.
- REQUIRES_NEW : 부모 트랜잭션을 무시하고 무조건 새로운 트랜잭션이 생성, 이미 진행중인 트랜잭션이 있다면 잠깐 보류되고 해당 트랜잭션 경계가 종료 된 후 다시 시작된다.
- NOT_SUPPORTED : Non-transaction으로 시작하며, 이미 진행중인 트랜잭션이 있으면 끝날 때까지 보류된 후 실행된다.
- NEVER : Non-transaction으로 시작하며, 실행되며 부모 트랜잭션이 존재한다면 예외가 발생합니다.
- NESTED : 이미 진행중인 부모 트랜잭션이 있을 경우 중첩 트랜잭션을 생성하여 실행되며, 생성된 중첩 트랜잭션은 부모 트랜잭션이 rollback되면 함께 되지만, 해당 트랜잭션안에서의 Commit/Rollback은 부모 트랜잭션에 영향을 주지 않는다.
propagation 설정을 아래와 같이 할수 있다.
@Transactional(propagation = Propagation.REQUIRED)
끝!
'Springboot2.x 강좌 > DB연결' 카테고리의 다른 글
Flyway 로 DataBase 를 형상관리해보자 (0) | 2023.01.05 |
---|---|
MyBatis 의 다중 DataSource (0) | 2023.01.05 |
Spring Data JPA 다중 DataSource (0) | 2023.01.05 |
JdbcTemplate 다중 DataSource (0) | 2023.01.05 |
XML 로 Mybatis 설정하기 (0) | 2023.01.05 |