2024. 10. 22. 18:41ㆍSpring Boot/Spring Data JPA
디폴트 Transaction 설정
CrudRepository
에서 상속받는 메서드들은 SimpleJpaRepository
에서 기본적인 트랜잭션 설정을 상속받습니다. 읽기 작업(read operation)의 경우 @Transactional
어노테이션의 readOnly
속성이 true
로 설정됩니다. 반면, 그 외의 작업들은 기본적인 @Transactional
설정이 적용됩니다.
예시: CrudRepository
기본 트랜잭션 설정
public interface UserRepository extends CrudRepository<User, Long> {
@Override
@Transactional(timeout = 10)
public List<User> findAll();
// 추가적인 쿼리 메서드 선언
}
위 코드에서는 findAll()
메서드를 재정의하면서 @Transactional
어노테이션의 timeout
속성을 10초로 설정했습니다. 이로 인해 findAll()
메서드는 10초 내에 완료되지 않으면 타임아웃이 발생하며, readOnly
속성은 기본값인 false
가 됩니다.
서비스 계층을 통한 트랜잭션 관리
CRUD 메서드의 트랜잭션 설정을 변경하는 방법 중 하나는 위에서 본 것처럼 repository
인터페이스에서 직접 설정하는 것이지만, 또 다른 방법은 서비스 계층을 이용하는 것입니다. 서비스 계층은 여러 repository
를 묶어서 비즈니스 로직에 맞는 트랜잭션 경계를 설정하는 데 사용됩니다. 이를 통해 복잡한 비CRUD 작업을 처리할 때 트랜잭션을 관리할 수 있습니다.
예시: 서비스 계층에서 트랜잭션 관리
@Service
public class UserManagementImpl implements UserManagement {
private final UserRepository userRepository;
private final RoleRepository roleRepository;
public UserManagementImpl(UserRepository userRepository,
RoleRepository roleRepository) {
this.userRepository = userRepository;
this.roleRepository = roleRepository;
}
@Transactional
public void addRoleToAllUsers(String roleName) {
Role role = roleRepository.findByName(roleName);
for (User user : userRepository.findAll()) {
user.addRole(role);
userRepository.save(user); // save 호출은 권장 사항
}
}
}
위 코드에서는 addRoleToAllUsers
메서드가 트랜잭션 내에서 실행됩니다. 이 메서드가 호출되면 이미 실행 중인 트랜잭션에 참여하거나, 실행 중인 트랜잭션이 없으면 새로운 트랜잭션을 생성합니다. roleRepository
와 userRepository
에서 각각 데이터를 조회하고 변경하는 작업이 트랜잭션 내에서 이루어집니다.
주의사항
@Transactional
을 이용해 트랜잭션 경계를 설정할 때,repository
의 개별 메서드에 설정된 트랜잭션은 무시됩니다. 즉, 외부의 트랜잭션 설정이 우선 적용됩니다.- JPA 관점에서
save()
호출은 꼭 필요하지 않지만, Spring Data의 repository 추상화에 맞추기 위해save()
호출을 포함하는 것이 좋습니다.
쿼리 메서드와 트랜잭션
쿼리 메서드(선언된 쿼리 메서드 및 디폴트 메서드)에는 기본적으로 트랜잭션 설정이 적용되지 않습니다. 만약 특정 쿼리 메서드를 트랜잭션으로 실행하고 싶다면, 해당 메서드에 @Transactional
을 명시적으로 선언해야 합니다.
예시: 쿼리 메서드에서 @Transactional
사용
@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();
}
위 코드에서 findByLastname()
메서드는 데이터를 읽기만 하므로 readOnly
속성을 true
로 설정해 읽기 전용 트랜잭션으로 실행되게 했습니다. 이는 성능 최적화를 위해서도 권장됩니다. 예를 들어, Hibernate에서는 readOnly
트랜잭션의 경우 Flush 모드가 NEVER
로 설정되므로, 더티 체크(변경된 객체를 확인하는 작업)를 생략하여 성능을 향상시킬 수 있습니다.
deleteInactiveUsers()
메서드는 데이터베이스에서 비활성화된 사용자를 삭제하는 쿼리를 실행하는데, @Modifying
어노테이션을 추가해 수정 쿼리임을 명시합니다. 이 메서드는 @Transactional
설정을 따로 지정하여 readOnly
를 false
로 설정하고, 트랜잭션 내에서 실행되게 했습니다.
readOnly
플래그의 역할
readOnly = true
로 설정하면 읽기 전용 트랜잭션으로 실행됩니다. 일부 데이터베이스는 읽기 전용 트랜잭션 내에서 INSERT
나 UPDATE
쿼리를 거부할 수 있지만, 이는 데이터베이스에 따라 다릅니다. 대부분의 경우, readOnly
플래그는 JDBC 드라이버에 hint로 전달되어 성능을 최적화하는 용도로 사용됩니다. Spring은 이를 활용해 JPA 프로바이더에서 성능 최적화를 수행합니다.
'Spring Boot > Spring Data JPA' 카테고리의 다른 글
PESSIMISTIC_READ, PESSIMISTIC_WRITE (0) | 2024.10.22 |
---|---|
Locking (0) | 2024.10.22 |
Projections (0) | 2024.10.22 |
Scrolling (0) | 2024.10.20 |
Configuring Fetch- and LoadGraphs (0) | 2024.10.20 |