JPA를 사용할 때 save() 메서드의 동작 방식과 트랜잭션의 역할을 정확히 이해하는 것이 중요합니다. 특히 @Transactional과 readOnly = true 옵션이 JPA의 동작 방식에 미치는 영향을 잘 알고 사용하기 위해 글을 남겼습니다.
1️⃣ save()를 호출하면 DB에 즉시 저장될까?
JPA에서 save()를 호출하면 즉시 DB에 저장되지 않을 수도 있습니다. JPA는 영속성 컨텍스트(Persistence Context)와 트랜잭션 관리를 기반으로 동작하기 때문입니다.
✅ save() 호출 후의 동작 원리
Entity entity = new Entity();
entity.setName("Test");
entityRepository.save(entity); // save() 호출
• save()를 호출하면 persist()가 수행되면서 영속성 컨텍스트에 저장됨.
• 하지만 트랜잭션이 커밋(commit) 되기 전까지는 DB에 반영되지 않음.
• 즉, save() 후에도 flush되지 않으면 실제 DB에는 저장되지 않을 수 있음.
✅ 언제 DB에 저장될까?
다음과 같은 경우 영속성 컨텍스트가 flush() 되면서 DB에 반영됩니다.
1. 트랜잭션이 커밋될 때 → @Transactional이 적용된 경우
2. flush()를 직접 호출할 때 → entityManager.flush()
3. JPQL 실행 시 → JPQL은 영속성 컨텍스트를 무시하고 실행되므로, 실행 전에 자동으로 flush가 발생
4. 배치 크기를 초과할 때 → saveAll() 등을 사용하면 특정 개수 이상일 때 자동 flush 가능
2️⃣ 기존 데이터를 가져와서 save()를 다시 호출하면 DB에 반영될까?
JPA에서 DB에서 가져온 데이터를 수정한 후 save()를 다시 호출하면 즉시 반영될까?
정답은 트랜잭션이 유지되는 동안에는 즉시 반영되지 않는다 입니다.
✅ 기존 엔티티를 수정하고 save()를 호출한 경우
Entity entity = entityRepository.findById(1L).get(); // ① 기존 데이터 조회
entity.setName("Updated Name"); // ② 엔티티 필드 변경
entityRepository.save(entity); // ③ 다시 save() 호출
• findById()를 통해 조회한 엔티티는 이미 영속 상태.
• setName("Updated Name")으로 값을 변경하면, JPA가 변경을 감지(Dirty Checking).
• save()를 다시 호출해도 JPA는 이미 영속 상태이므로 별도의 persist()를 수행하지 않음.
• 트랜잭션이 커밋될 때 flush()가 실행되면서 변경 사항이 자동 반영됨.
✅ 즉시 반영이 필요한 경우
flush()를 명시적으로 호출하면 즉시 DB에 반영할 수 있습니다.
entityRepository.save(entity);
entityManager.flush(); // 즉시 변경 사항 DB 반영
✔ 하지만 일반적으로는 @Transactional을 사용하여 트랜잭션이 끝날 때 자동 반영하는 것이 효율적.
3️⃣ findById()가 아니더라도 JPA로 데이터를 불러오면 변경 감지가 될까?
네, findById()뿐만 아니라 JPA를 통해 조회한 모든 영속 상태(Entity)는 자동으로 변경 감지가 적용됩니다.
✅ 변경 감지가 동작하는 경우
1. findById()를 통해 조회한 경우
2. findAll(), findByName() 같은 메서드로 조회한 경우
3. JPQL을 사용하여 조회한 경우 (@Query 활용)
@Transactional
public void updateEntityByName(String name) {
List entities = entityRepository.findByName(name);
for (Entity entity : entities) {
entity.setName("Updated Name"); // 변경 감지 발생
}
} // 트랜잭션 종료 시 UPDATE 실행
✔ JPA 메서드(findAll(), findByName() 등)를 사용해도 변경 감지가 동작.
4️⃣ @Transactional이 없다면 모든 변경 로직에서 save()를 호출해야 할까?
네, @Transactional이 없으면 변경 감지가 동작하지 않으므로, 모든 변경 로직에서 save()를 호출해야 합니다.
public void updateEntityWithoutTransactional(Long id) {
Entity entity = entityRepository.findById(id).orElseThrow();
entity.setName("Updated Name");
entityRepository.save(entity); // ✅ save() 호출이 필요
}
✔ 트랜잭션이 없으면 flush()가 자동 실행되지 않기 때문에 save()를 호출해야 변경 사항이 DB에 반영됨.
5️⃣ @Transactional(readOnly = true)의 역할과 적용 전략
✅ readOnly = true의 특징
• 변경 감지가 비활성화됨 → flush() 자동 호출이 안 됨.
• 읽기 전용 트랜잭션을 최적화하여 성능 향상.
• Create, Update, Delete 작업에서는 사용하면 안 됨.
@Transactional(readOnly = true)
public Entity getEntity(Long id) {
return entityRepository.findById(id).orElseThrow();
}
✔ 조회 메서드에 사용하면 성능 최적화 가능.
✅ save()를 호출하면?
@Transactional(readOnly = true)
public void saveEntityReadOnly(Long id) {
Entity entity = entityRepository.findById(id).orElseThrow();
entity.setName("Updated Name");
entityRepository.save(entity); // ❌ 반영되지 않음!
}
✔ readOnly = true가 적용된 상태에서는 save()를 호출해도 변경 사항이 DB에 반영되지 않음.
✅ save()가 필요할 경우
@Transactional
public void updateEntity(Long id) {
Entity entity = entityRepository.findById(id).orElseThrow();
entity.setName("Updated Name"); // 변경 감지 ✅
entityRepository.save(entity); // 저장 ✅
}
✔ 변경이 필요한 메서드에서는 @Transactional을 따로 선언해야 함.
🔥 결론
✔ save()를 호출해도 트랜잭션이 커밋되지 않으면 DB에 반영되지 않음.
✔ 조회된 엔티티는 변경 감지가 적용되므로 save()를 명시적으로 호출하지 않아도 변경 사항이 반영됨.
✔ @Transactional(readOnly = true)에서는 save()가 동작하지 않으며, 변경이 필요한 경우 @Transactional을 사용해야 함.
✔ 조회 전용 트랜잭션은 readOnly = true를 적용하고, 변경이 필요한 경우는 별도로 @Transactional을 적용하는 것이 최적화된 전략.