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