Spring Data JPA에서 데이터 변경 작업(UPDATE, DELETE 등)을 실행할 때, 반드시 알아야 하는 애노테이션 중 하나가 @Modifying입니다. 이 글에서는 @Modifying의 역할, 사용법, 그리고 주의사항을 설명하겠습니다.
1. @Modifying란?
@Modifying은 Spring Data JPA에서 데이터베이스에 영향을 미치는 JPQL이나 Native Query를 실행할 때 사용하는 애노테이션입니다.
JPA의 @Query는 기본적으로 **읽기 쿼리(SELECT)**로 간주되며, 이를 데이터 변경 쿼리로 변경하려면 @Modifying이 필요합니다.
주요 특징
- 데이터 변경 작업 명시:
- INSERT, UPDATE, DELETE와 같은 쓰기 작업임을 JPA에게 명확히 알려줍니다.
- 트랜잭션 필요:
- @Modifying은 변경 작업을 수행하므로, **트랜잭션(@Transactional)**과 함께 사용해야 합니다.
- JPQL 및 Native Query 지원:
- JPQL뿐만 아니라 Native Query와 함께 사용할 수 있습니다.
2. @Modifying의 사용법
기본 사용 예시: JPQL로 데이터 업데이트
다음 예제는 isActive 값을 false로 업데이트하는 쿼리를 정의한 코드입니다.
Repository 코드:
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
@Modifying
@Query("UPDATE User u SET u.isActive = false WHERE u.id = :id")
int deactivateUser(@Param("id") Long id);
}
사용법:
- @Modifying으로 JPQL 쿼리가 데이터 변경 작업임을 선언.
- 반환값은 수정된 행(row)의 개수입니다.
서비스 코드:
@Service
@Transactional
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
public void deactivateUser(Long id) {
int updatedRows = userRepository.deactivateUser(id);
if (updatedRows == 0) {
throw new EntityNotFoundException("User not found with id: " + id);
}
}
}
Native Query 사용 예시
@Modifying은 Native Query와도 함께 사용할 수 있습니다.
Repository 코드:
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
@Modifying
@Query(value = "UPDATE users SET is_active = false WHERE id = :id", nativeQuery = true)
int deactivateUser(@Param("id") Long id);
}
-
- nativeQuery = true를 사용하여 데이터베이스에 종속적인 SQL을 실행합니다.
- JPQL보다 직접적인 SQL 제어가 가능하지만, 데이터베이스 독립성이 떨어질 수 있습니다.
DELETE 작업 예시
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
@Modifying
@Query("DELETE FROM User u WHERE u.id = :id")
int deleteUserById(@Param("id") Long id);
}
3. 왜 @Modifying이 필요한가?
@Query는 기본적으로 SELECT 쿼리로 동작합니다.
만약 데이터 변경 작업(UPDATE, DELETE)을 수행하려고 하면, @Modifying이 없을 경우 예외가 발생합니다:
예외 메시지:
org.springframework.dao.InvalidDataAccessApiUsageException:
Executing an update/delete query; nested exception is
javax.persistence.TransactionRequiredException:
Executing an update/delete query
원인:
- JPA는 SELECT 작업만 수행할 수 있도록 설계되어 있으므로, 데이터 변경 작업임을 명확히 알려줄 필요가 있습니다.
- @Modifying은 JPA에게 "이 쿼리는 데이터베이스에 영향을 미치는 작업"임을 알려주는 역할을 합니다.
4. 주의사항
1) JPA 영속성 컨텍스트와의 불일치 가능성
@Modifying은 JPQL 또는 Native Query를 직접 실행하여 데이터를 변경합니다.
따라서, JPA의 영속성 컨텍스트가 이를 인지하지 못해 캐시된 데이터와 데이터베이스 간의 불일치가 발생할 수 있습니다.
해결 방법:
- flush() 사용: 영속성 컨텍스트를 동기화하여 변경된 데이터를 반영합니다.
- Dirty Checking 활용: 직접 쿼리를 실행하기보다는 엔티티를 조회하고 값을 수정하여 JPA의 변경 감지 기능을 사용하는 것이 안정적입니다.
Dirty Checking 예시:
@Service
@Transactional
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
public void deactivateUser(Long id) {
User user = userRepository.findById(id)
.orElseThrow(() -> new EntityNotFoundException("User not found with id: " + id));
user.setActive(false); // 변경 감지를 통해 자동 업데이트
}
}
2) 트랜잭션 필수
- @Modifying은 데이터 변경 작업이므로 트랜잭션(@Transactional)이 필수입니다.
- 트랜잭션 없이 실행하면 작업이 적용되지 않거나 예외가 발생합니다.
권장 방법:
- Repository에서 @Transactional을 선언하기보다는 서비스 계층에서 트랜잭션을 관리하는 것이 더 권장됩니다.
- 이렇게 하면 비즈니스 로직 전체를 하나의 트랜잭션으로 묶을 수 있어 작업의 원자성을 보장할 수 있습니다.
서비스에서 트랜잭션 관리 예시:
@Service
@Transactional
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
public void deactivateUser(Long id) {
userRepository.deactivateUser(id);
}
}
3) Native Query 사용 시 데이터베이스 의존성
- nativeQuery = true를 사용하면 데이터베이스에 종속적인 SQL을 작성해야 하므로, 이식성이 떨어질 수 있습니다.
- JPQL로 해결 가능한 작업은 JPQL을 우선적으로 사용하는 것이 좋습니다.
권장 방법:
- 데이터베이스 독립성을 유지하기 위해 JPQL을 사용합니다.
- Native Query는 성능 최적화가 필요한 경우 또는 JPQL로 처리하기 어려운 복잡한 쿼리에만 제한적으로 사용합니다.
4) 반환값 확인
- @Modifying 메서드는 변경된 행(row) 수를 반환합니다.
이를 활용하여 작업 결과를 확인하거나 추가 로직을 처리할 수 있습니다.
반환값 활용 예시:
public void deactivateUser(Long id) {
int updatedRows = userRepository.deactivateUser(id);
if (updatedRows == 0) {
throw new EntityNotFoundException("No user found with id: " + id);
}
}
6. @Modifying의 장점
- 효율적인 데이터 변경
- JPQL이나 Native Query로 한 번의 쿼리로 여러 데이터를 변경할 수 있어 성능이 높습니다.
- 직관적이고 명확한 쿼리 작성
- JPQL과 Native Query를 활용하여 복잡한 로직 없이 필요한 작업을 실행 가능.
- 트랜잭션과의 조합
- @Transactional과 함께 사용하여 데이터 변경 작업의 원자성을 보장합니다.
7. 언제 사용하면 좋을까?
- 대량 업데이트 또는 삭제가 필요할 때:
- 다수의 행(row)을 업데이트하거나 삭제해야 할 경우, JPQL 쿼리를 사용하면 성능이 향상됩니다.
- 조회 없이 데이터 변경 작업을 해야 할 때:
- 데이터베이스 상태만 변경하고, 엔티티 조회가 필요하지 않은 경우.
8. 결론
@Modifying은 Spring Data JPA에서 데이터 변경 작업을 수행할 때 반드시 필요한 애노테이션입니다.
이를 통해 효율적으로 UPDATE, DELETE 작업을 실행할 수 있으며, 트랜잭션과 결합하면 안전하게 작업을 처리할 수 있습니다.
'Spring' 카테고리의 다른 글
[Spring] Optional의 활용: Repository, Service, 그리고 ResponseEntity의 역할 (0) | 2024.12.06 |
---|---|
객체 동등성 비교를 간단하게: @EqualsAndHashCode (1) | 2024.12.04 |
Spring에서 @RequiredArgsConstructor 사용법과 장점 (0) | 2024.12.03 |
spring boot 버전에 호환되는 의존성 버전 확인하는 법 (0) | 2024.11.29 |
람다와 스트림 (0) | 2024.11.08 |