이전에 작업했던 프로젝트를 마이바티스에서 JPA로 전환하는 작업 중 데이터를 호출한 후 값을 변경했지만, 실제 데이터베이스에는 save()를 직접 사용하지 않으면 변경된 값이 저장되지 않는 문제가 발생했습니다.
JPA를 사용하면서 트랜잭션 어노테이션을 생략한 실수를 통해 영속성 컨텍스트와 트랜잭션의 상호작용을 깊이 이해할 수 있었습니다. 이 글에서는 그 경험을 바탕으로 트랜잭션 어노테이션이 필요한 이유와 깨달은 점을 공유합니다.
1. 문제가 발생한 상황
JPA를 사용하는 메서드에 평소처럼 데이터를 수정하는 작업을 수행했습니다. 코드 예시는 다음과 같습니다:
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public void updateUser(Long userId, String newName) {
User user = userRepository.findById(userId).orElseThrow(() -> new IllegalArgumentException("User not found"));
user.setName(newName);
// 변경 사항을 저장하지 않음
}
}
반면, 직접 save()를 사용하여 데이터를 저장하면 변경 사항을 저장합니다. ( 이런식으로 문제를 해결할 수 있지만 이 방법은 심각한 문제를 야기 할 수 있습니다. 밑에 제가 그 이유를 설명해보겠습니다.)
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public void updateUser(Long userId, String newName) {
User user = userRepository.findById(userId).orElseThrow(() -> new IllegalArgumentException("User not found"));
user.setName(newName);
userRepository.save(user); //정상적으로 변경 값 저장
}
}
답: 여기에서 이런 문제가 발생한 이유는 단순하게 @Transactional을 붙이지 않아서 그렇습니다.
2. 왜 트랜잭션 어노테이션이 필요한가?
트랜잭션은 JPA의 영속성 컨텍스트와 밀접하게 연결되어 있습니다. 트랜잭션이 활성화되지 않으면 다음과 같은 문제가 발생할 수 있습니다:
- 영속성 컨텍스트 종료: 트랜잭션이 없으면 메서드 실행이 끝나는 즉시 영속성 컨텍스트가 종료됩니다. 따라서 변경된 엔티티는 더 이상 관리되지 않고, 데이터베이스에 반영되지 않습니다.
- 데이터 일관성 부족: 여러 작업을 수행하는 동안 트랜잭션이 없다면 일부만 성공하고 나머지는 실패하는 상황이 발생할 수 있습니다. 이는 데이터 무결성을 위협합니다.
- 지연 로딩 문제: 트랜잭션이 없으면 영속성 컨텍스트가 관리되지 않아 LazyInitializationException과 같은 문제가 발생할 수 있습니다.
- 중복 호출 문제: 트랜잭션 없이 save를 여러 번 호출하면 매번 데이터베이스와 직접 상호작용하게 됩니다. 이는 다음과 같은 부작용을 초래할 수 있습니다:
- 성능 저하: 영속성 컨텍스트가 변경 사항을 일괄 처리하지 않으므로, 각 호출마다 데이터베이스 쓰기 작업이 발생해 성능이 떨어집니다.
- 일관성 문제: 한 호출이 실패하면 이미 저장된 변경 사항을 되돌릴 방법이 없어 데이터 불일치가 발생할 수 있습니다.
- 코드 복잡성 증가: 각 호출의 성공 여부를 개별적으로 관리해야 하므로 코드 유지보수가 어려워집니다.
3. 문제 해결: 트랜잭션 어노테이션 추가
위 문제를 해결하기 위해 메서드에 @Transactional 어노테이션을 추가했습니다. 수정된 코드는 다음과 같습니다:
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Transactional
public void updateUser(Long userId, String newName) {
User user = userRepository.findById(userId).orElseThrow(() -> new IllegalArgumentException("User not found"));
user.setName(newName);
}
}
이제 트랜잭션이 활성화되어 영속성 컨텍스트가 변경된 엔티티를 추적하고, 메서드가 종료될 때 데이터베이스에 변경 사항을 반영합니다.
4. 깨달은 점
이번 경험을 통해 다음과 같은 중요한 점을 배웠습니다:
- 트랜잭션은 필수적이다: JPA는 읽기 작업에서는 트랜잭션 없이도 동작할 수 있지만, 데이터 변경 작업(INSERT, UPDATE, DELETE)에는 반드시 트랜잭션이 필요합니다. 트랜잭션이 없으면 영속성 컨텍스트와 데이터베이스 간의 동기화가 보장되지 않습니다.
- 트랜잭션 범위를 명확히 설정해야 한다: 서비스 계층에서 트랜잭션을 명시적으로 관리하는 것이 가장 바람직합니다.
- 코드 리뷰와 테스트의 중요성: 이런 실수는 코드 리뷰나 테스트를 통해 사전에 발견할 수 있었을 것입니다.
5. 트랜잭션 어노테이션 사용 시 주의사항
- 읽기 전용 트랜잭션: 읽기 전용 작업에는 @Transactional(readOnly = true)를 사용하여 성능을 최적화할 수 있습니다.
- 예외 처리: 트랜잭션이 롤백되지 않는 상황을 방지하려면 커스텀 예외가 RuntimeException을 상속받도록 해야 합니다.
이번에 운이 좋게? 이런 문제가 발생하여 원인과 해결방법을 찾아봤었는데요.이번 실수는 저에게 JPA의 동작 원리를 깊이 이해하는 계기가 되었습니다.
혹시나 이 글이 이해가 안되거나 어렵다면 이전에 작성했던 JPA에 관한 글을 참고 하시면 좋을 것 같습니다.
2024.12.10 - [Spring] - JPA (Java Persistence API)란?
JPA (Java Persistence API)란?
JPA(Java Persistence API)는 Java 개발자가 데이터베이스 작업을 객체 지향적으로 처리할 수 있도록 지원하는 표준 사양입니다. SQL을 직접 작성하는 대신, 데이터베이스 테이블과 자바 객체(Entity)를 매
0and24.tistory.com
'문제 및 해결' 카테고리의 다른 글
[VUE] 카카오 맵 api... 로딩 안되는 문제 해결 (0) | 2024.12.07 |
---|---|
JavaScript에서 console.log에 객체를 출력하면 [object Object]로 표시 될 때 (0) | 2024.12.07 |
JPA 상속 클래스로 엔티티 중복 코드 제거하기 (0) | 2024.11.27 |
@PrePersist를 활용한 엔티티 초기화: 생성일자 처리 방법 (0) | 2024.11.26 |
이미지가 많아져서 웹사이트가 느리다면?? (2) | 2024.11.14 |