Transaction은 데이터베이스에 하나의 작업 단위를 의미한다. 예를들어, A가 B에게 송금하는 과정에서는 A에서 출금 / B에게 입금 두 SQL 작업이 발생하는데 이를 송금이라는 트랜잭션 단위로 구분하는 것이다.
트랜잭션들은 ACID 라고 불리는 4가지의 속성을 보장해야한다.
- Atomicity( 원자성 ) : 트랜잭션은 작업의 최소단위이기 때문에, 트랜잭션 내의 각 작업은 부분적으로 성공하거나 실패해서는 안된다
- Consistency ( 일관성 ) : 트랜잭션 작업에 의해 데이터베이스의 제약사항이 변경되거나 어겨져서는 안된다
- Isolation ( 독립성 ) : 각 트랜잭션들은 서로 독립적이기에 서로 영향을 줘서는 안된다
- Durability ( 지속성 ) : 트랙잭션 작업에 의해 반영된 내용은 영구적이어야 한다
Spring Boot 환경이 아니라면 Config 파일에
@EnableTransactionManagement를 달아주는 등의 설정이 필요하다
Spring Data JPA의 Repository 메서드들은 기본적으로 SimpleJpaRepository의 트랜잭션 설정을 상속받는다.
모든 읽기 작업에는 readOnly = true가 설정되어 있으며, 나머지 작업에는 @Transactional만 설정되어있다.
public interface UserRepository extends CrudRepository<User, Long> {
@Override
@Transactional(timeout = 10)
public List<User> findAll();
// Further query method declarations
}
쿼리메서드를 대상으로 별도의 트랜잭션 설정을 부여하고싶다면 각 메서드에 @Transactional을 설정해야한다.
CRUD 작업에 대한 트랜잭션 설정외에 한 Repository 이상의 트랜잭션 단위를 설정하고 싶다면,
Service 구현체에 @Transactional을 설정할 수 있다.
@Service
@RequiredArgsConstructor
public class UserManagementImpl implements UserManagement {
private final UserRepository userRepository;
private final RoleRepository roleRepository;
@Transactional
public void addRoleToAllUsers(String roleName) {
Role role = roleRepository.findByName(roleName);
for (User user : userRepository.findAll()) {
user.addRole(role);
userRepository.save(user);
}
}
}
위 코드에서 addRoleToAllUsers(String roleName)은 하나의 트랜잭션 단위로 지정되었다.
따라서, 메서드 내부의 findByName이나 findAll, save 호출과정에서 오류가 발생한다면 메서드 시작시점의 상태로 데이터가 rollback 된다.
이렇게 Repositroy 외부에 트랜잭션 설정을 구성한 경우, Repository 내부의 트랜잭션 설정은 무시된다.
save호출은 없더라도 메서드가 정상적으로 마무리된다면 DB에 반영되지만, 명시적으로 작성해주는 것이 좋다고 생각된다
default 메서드나 @Query로 선언된 쿼리 메서드의 경우, 기본적으로 트랜잭션 설정이 반영되지 않는다.
즉, SimpleJpaRepository의 트랜잭션 설정을 상속받지 않는다.
트랜잭션 설정을 반영하기 위해서는 커스텀 Repository에 트랜잭션 설정을 직접 해줘야한다.
@Transactional(readOnly = true)
interface UserRepository extends JpaRepository<User, Long> {
List<User> findByLastname(String lastname);
@Modifying
@Transactional
@Query("delete from User u where u.active = false")
void deleteInactiveUsers();
}
readOnly옵션을true로 설정한다고 INSERT 같은 데이터 조작쿼리가 반드시 동작하지 않는 것은 아니다.대신 해당 플래그는 JDBC 드라이버에 성능 최적화의 목적의 힌트로써 제공된다.
예를들어, Hibernate와 함께 사용할 때 트랜잭션을 읽기전용으로 설정하면 flush 모드가 NEVER로 설정되어 변경감지가 동작하지 않으므로 성능 향상을 기대할 수 있다.