

목 차
1. JPA의 작동 방식
- 엔티티 상태와 라이프사이클
- 영속 컨텍스트란?
2. flush()란 무엇인가
3. flush()의 동작원리.
4. flush 모드의 종류와 특징
5. flush()를 사용하는 이유,
6. flush() 사용 예제.
- insert
- update
- delete
7. 실무에서 flush() 사용시 주의사항.
8. 결론.

JPA에서 엔티티는 다음과 같은 4가지 상태를 가집니다.

1. 비영속 (Transient):
영속성 컨텍스트와 관계없는 상태로, 단순히 객체만 생성된 상태입니다.
UserEntity user = new UserEntity(); // 비영속 상태
2. 영속 (Managed):
영속성 컨텍스트에 저장된 상태로, 변경 사항이 자동으로 추적됩니다.
entityManager.persist(user); // 영속 상태로 전환
3. 준영속 (Detached):
영속성 컨텍스트에서 분리된 상태로, 더 이상 변경 사항을 추적하지 않습니다.
entityManager.detach(user); // 준영속 상태로 전환
4. 삭제 (Removed):
데이터베이스와 영속성 컨텍스트에서 삭제된 상태입니다.
entityManager.remove(user); // 삭제 상태로 전환

영속성 컨텍스트(Persistence Context)는 JPA가 엔티티 객체를 관리하기 위해 사용하는 메모리 공간입니다.
이는 일종의 캐시로, 엔티티의 상태를 추적하고 필요할 때 데이터베이스와 동기화합니다.
※ 특징.
변경 감지 (Dirty Checking): 엔티티의 변경 사항을 자동으로 추적하여 필요 시 수정 쿼리를 생성합니다.
1차 캐시: 동일한 트랜잭션 내에서는 동일한 엔티티 객체를 반환하여 메모리 효율성을 높입니다.
쓰기 지연(Write-Behind): SQL 쿼리는 트랜잭션 커밋 시점에 한 번에 실행되어 성능을 최적화합니다.

flush()는
.
JPA에서 flush()는 다음과 같은 역할을 합니다:
영속성 컨텍스트의 변경 사항을 데이터베이스에 반영합니다.
트랜잭션 커밋 전에 변경 사항을 확인할 수 있게 합니다.
쿼리 실행 전에 영속성 컨텍스트와 데이터베이스를 동기화합니다.

flush() 메서드가 호출되면 다음과 같은 과정이 진행됩니다:
1. 변경 감지(Dirty Checking):
영속성 컨텍스트 내의 모든 엔티티를 스캔하여 변경된 엔티티를 찾습니다.
2. SQL 생성:
변경된 엔티티에 대한 적절한 SQL 쿼리(INSERT, UPDATE, DELETE)를 생성합니다.
3. 쿼리 실행:
생성된 SQL 쿼리를 데이터베이스에 전송하여 실행합니다.
4. 동기화:
영속성 컨텍스트와 데이터베이스 상태를 동기화합니다.

JPA에서는 다음과 같은 flush 모드를 제공합니다:
JPA 명세에서 기본값으로 설정된 모드로, 다음 두 가지 상황에서 자동으로 flush를 수행합니다:
트랜잭션이 커밋되기 전
영속성 컨텍스트에 변경 사항이 있는 테이블에 영향을 미치는 쿼리가 실행되기 전
Hibernate의 AUTO 모드는 JPA와 약간 다르게 동작합니다.
Hibernate는 쿼리가 영속성 컨텍스트의 변경 사항에 영향을 받을 가능성이 있는 경우에만
flush를 수행합니다.
예를 들어, 다른 엔티티에 대한 쿼리는 flush를 트리거하지 않을 수 있습니다.


대량의 데이터를 저장할 때 배치 처리를 통해 성능을 최적화하는 예제.
@Transactional
public void saveUsers(List<UserEntity> users) {
int batchSize = 20;
for (int i = 0; i < users.size(); i++) {
entityManager.persist(users.get(i));
if (i % batchSize == 0) {
entityManager.flush(); // INSERT 쿼리 실행
entityManager.clear(); // 메모리 초기화
}
}
}
이 코드는 20개의 엔티티마다 flush()와 clear()를 호출하여 영속성 컨텍스트의 크기를 제한하고 메모리 사용량을 최적화합니다.

엔티티를 수정한 후 변경 사항을 즉시 데이터베이스에 반영하는 예제.
@Transactional
public void updateUsers(List<UserEntity> users) {
for (UserEntity user : users) {
user.setName("Updated Name");
}
entityManager.flush(); // UPDATE 쿼리 실행
// 이후 쿼리는 업데이트된 데이터를 참조
List<UserEntity> updatedUsers = entityManager.createQuery("SELECT u FROM UserEntity u WHERE u.name = :name", UserEntity.class)
.setParameter("name", "Updated Name")
.getResultList();
}
엔티티를 삭제한 후 변경 사항을 즉시 데이터베이스에 반영하는 예제.
@Transactional
public void deleteUsers(List<Long> userIds) {
for (Long id : userIds) {
UserEntity user = entityManager.find(UserEntity.class, id);
entityManager.remove(user);
}
entityManager.flush(); // DELETE 쿼리 실행
// 이후 쿼리는 삭제된 데이터를 참조하지 않음
long count = entityManager.createQuery("SELECT COUNT(u) FROM UserEntity u", Long.class)
.getSingleResult();
}

@Modifying(clearAutomatically = true)
@Query("UPDATE UserEntity u SET u.name = :name WHERE u.id = :id")
int updateUserName(@Param("id") Long id, @Param("name") String name);
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true

하지만 flush()는 신중하게 사용해야 합니다.
과도한 호출은 성능 저하를 초래할 수 있으며, 트랜잭션 관리와 벌크 연산과의 상호작용에 주의해야 합니다.
그러나 특정 상황(예: 대량 데이터 처리, ID 생성 및 참조, 제약 조건 검증)에서는 명시적인 flush() 호출이 필요할 수 있습니다.
결국, flush()는 JPA의 강력한 기능 중 하나이지만,
그 동작 원리와 영향을 이해하고 적절한 상황에서 사용하는 것이 중요합니다.