@Modifying vs save() 비교 정리
@Modifying이란?
Spring Data JPA에서 INSERT, UPDATE, DELETE 작업을 수행하는 커스텀 쿼리에 사용하는 어노테이션입니다.
@Modifying
@Query("UPDATE User u SET u.name = :name WHERE u.id = :id")
int updateUserName(@Param("id") Long id, @Param("name") String name);
왜 save() 대신 @Modifying을 사용할까요?
1. 성능 차이
save() 방식
// 1000개 수정 시: 1000번 SELECT + 1000번 UPDATE
List<User> users = userRepository.findByStatus("ACTIVE");
users.forEach(user -> {
user.setLastLogin(LocalDateTime.now());
userRepository.save(user);
});
@Modifying 방식
// 1번의 UPDATE로 처리됩니다
@Modifying
@Query("UPDATE User u SET u.lastLogin = :now WHERE u.status = 'ACTIVE'")
int updateActiveUsersLastLogin(@Param("now") LocalDateTime now);
2. 메모리 효율성
save() 방식
// 모든 엔티티를 메모리에 로드합니다 (OutOfMemoryError 위험)
List<User> oldUsers = userRepository.findByCreatedAtBefore(cutoffDate);
userRepository.deleteAll(oldUsers);
@Modifying 방식
// 메모리에 엔티티를 로드하지 않고 DB에서 직접 처리합니다
@Modifying(clearAutomatically = true)
@Query("DELETE FROM User u WHERE u.createdAt < :cutoffDate")
int deleteOldUsers(@Param("cutoffDate") LocalDateTime cutoffDate);
3. 복잡한 조건 처리
@Modifying
@Query("UPDATE User u SET u.point = u.point + :bonus " +
"WHERE u.level >= :minLevel AND u.lastLogin > :recentDate")
int giveBonusToActiveUsers(@Param("bonus") int bonus,
@Param("minLevel") int minLevel,
@Param("recentDate") LocalDateTime recentDate);
언제 무엇을 사용해야 할까요?
save() 사용 시기
- 단일 엔티티를 수정할 때
- 복잡한 비즈니스 로직이 필요할 때
- 연관 관계 처리가 필요할 때
- 엔티티 생명주기 이벤트가 필요할 때
@Service
public class UserService {
public void updateUserProfile(Long userId, UserProfileDto dto) {
User user = userRepository.findById(userId).orElseThrow();
// 복잡한 비즈니스 로직
user.updateProfile(dto);
user.updateLastModified();
// 연관 관계 처리
if (dto.getNewRole() != null) {
user.changeRole(dto.getNewRole());
}
userRepository.save(user); // 적절한 선택입니다
}
}
@Modifying 사용 시기
- 대량 데이터를 처리할 때
- 성능이 중요한 배치 작업을 수행할 때
- 단순한 값 업데이트가 필요할 때
- 조건부 일괄 처리가 필요할 때
@Service
public class BatchService {
@Transactional
public void monthlyUserMaintenance() {
userRepository.updateInactiveUsers();
userRepository.deleteExpiredSessions();
userRepository.resetMonthlyLimits();
}
}
@Modifying 주요 속성
clearAutomatically
- 쿼리 실행 후 영속성 컨텍스트를 자동으로 클리어합니다.
- 기본값:
false - 대량 데이터 수정 시 메모리 효율성을 높일 수 있습니다.
flushAutomatically
- 쿼리 실행 전 영속성 컨텍스트의 변경사항을 자동으로 플러시합니다.
- 기본값:
false - 미처리된 변경사항이 쿼리에 반영되도록 보장합니다.
@Modifying(clearAutomatically = true, flushAutomatically = true)
@Query("UPDATE User u SET u.status = 'INACTIVE' WHERE u.lastLogin < :date")
int deactivateInactiveUsers(@Param("date") LocalDateTime date);
성능 비교 예시
// 10,000개 사용자 포인트 초기화
// save() 방식: 10,000번의 SELECT + 10,000번의 UPDATE
List<User> users = userRepository.findAll();
users.forEach(user -> {
user.setPoint(0);
userRepository.save(user);
});
// @Modifying 방식: 1번의 UPDATE
@Modifying
@Query("UPDATE User u SET u.point = 0")
int resetAllUserPoints();
결론
- save(): 엔티티 중심의 객체지향적 접근 방식으로, 복잡한 비즈니스 로직이 있을 때 적합합니다.
- @Modifying: 성능과 메모리 효율성이 중요한 대량 데이터 처리에 적합합니다.