새로운 내용을 공부할 때
새로운 내용의 공부를 시작할 때 용어의 정의를 이해하지 못하거나 정확하게 알지 못한다면 그 용어가 포함된 문장을 이해하지 못합니다.
작은 단어 하나가 내용을 이해하지 못하게 하기 때문에 용어를 정확하게 이해하는 것이 중요합니다.
TIL) JPA save() 동작 방식
📌 2025-03-16 TIL
1. 오늘의 학습 주제
- Data JPA에서 save() 동작과 내부 원리
2. 학습 내용
Data JPA에서 데이터를 저장하기 위해 사용하는 메서드는 save()
입니다.
save()
의 내부 동작 방식을 공부하는 이유를 생각해보면서 정리해보겠습니다.
save()의 내부 동작 원리를 왜 알아야할까?
단순하게 데이터를 저장하는 SQLMapper와 다르게 영속성 컨택스트와 1차 캐시, 쓰지 지연등을 활용하는 JPA는 내부 동작을 알아야 불필요한 save() 호출이나 트랜잭션 관리를 통해 최적화도 가능하기 때문입니다.
여러 JDBC 구현체를 사용한다면 더더욱 동작원리를 알아야 발생되는 문제를 빠르게 확인할 수 있습니다.
- JPA의 영속성 컨택스트
- JPA의 엔티티 상태 관리
- 트랜잭션 관리와 데이터 무결성 보장
내부 동작
Spring Boot Data JPA를 사용하여 JPA를 사용하면 제공하는 기본 메서드인 save()를 사용하면 아래 동작이 발생됩니다.
- Persistable.isNew(entity)
먼저 엔티티가 데이터베이스(주민센터)에 등록된 아이인지 확인합니다.
주민등록번호(id)가 없는 경우(primary key)
- 고유 번호가 없는 경우에는
persist()
가 동작합니다.
Member memberA = new Member(); // 새 객체 (Transient)
memberA.setName("희동이");
Member savedA = memberRepository.save(memberA);
// -> 내부적으로 persist 실행, savedA == memberA (영속 상태)
주민등록번호(id)가 있는 경우(primary key)
+ 영속성 컨택스트에서 관리하는 경우
+ 영속성 컨택스트에서 관리하지 않는 경우
영속성 컨택스트에 관리대상인 경우
save()
호출시 영속성 컨택스트에 이미 존재한다면, 해당 엔티티는 이미 관리중이므로 save()
는 그 엔티티를 반환합니다.
즉각적인 데이터베이스 작업(insert,update)가 발생하지 않습니다.
영속성 컨택스트에 관리대상이 아니였던 경우
merge()
는 데이터를 병합합니다.
Member memberB = new Member();
memberB.setId(1L);
memberB.setName("둘리");
Member updatedB = memberRepository.save(memberB);
// -> 내부적으로 merge 실행 (id=1 조회 후 병합)
// updatedB는 영속 상태이며 DB에서 조회된 데이터에 "둘리" 이름이 반영됨
// memberB는 그대로 detach 상태 (식별자만 같고 다른 인스턴스)
memberB != updateB
주의사항
데이터 병합시 모든 필드를 업데이트 하므로 필요한 부분만이 아니라 전체 필드가 새 엔티티를 기준으로 변경된다.
해결방안
- 먼저 조회하고 업데이트한다 - 더티체킹
- jpql을 사용하여 부분 업데이트를 한다
많은 데이터 저장시 주의사항
- 영속성 컨택스트는 메모리에서 동작한다.
- 커밋 시점에 쿼리가 한번에 실행된다.
- 영속 상태 엔티티를 확인하고 업데이트를 한다
1~3번을 합치면 발생할 수 있는 문제가 있습니다.
데이터 10만개를 한꺼번에 저장한다면 10만개의 엔티티를 영속성 컨택스트에서 관리해야합니다.
동일한 트랜잭션 내에서만 접근 가능한 영속성 컨택스트의 메모리가 부족할 수 있습니다.
이런 경우 중간 중간 내부 버퍼를 비워줘야합니다.
- flash() 호출
- clear() 호출
flash()는 영속성 컨텍스트에서 관리중인 엔티티의 정보를 데이터베이스에 동기화를 합니다.
Clear()는 영속성 컨택스트 관리 목록과 쓰기 지연 정보를 모두 깨끗하게 지워줍니다.
데이터 동기화
saveAndFlush()
커밋은 아닙니다.
데이터를 영속성 컨택스트에 저장하고, 반양하는 것을 말합니다.
또 다른 방법 JDBC
대용량으로 데이터를 저장해야하는 경우 변경감지나 동일한 엔티티를 활용하는 목적보다 데이터를 빠르게 저장하는 게 목표입니다.
@Autowired
private JdbcTemplate jdbcTemplate;
public void saveBulkOrders() {
String sql = "INSERT INTO order (name) VALUES (?)";
List<Object[]> batchArgs = new ArrayList<>();
for (int i = 0; i < 5000; i++) {
batchArgs.add(new Object[]{"Order-" + i});
}
jdbcTemplate.batchUpdate(sql, batchArgs);
}
다중값 삽입 쿼리가 가능하며 성능상 이점이 많습니다.
insert into order (name) values ('Order-1'), ('Order-2'), ...
댓글남기기