Spring
영속성(persist,merge..)과 쓰기지연
김예나
2025. 3. 11. 20:12
객체지향과 RDB의 차이를 해결하기 위해 ORM 이 해결해야하는 문제점
상속
- 매핑정보에 상속정보를 넣어준다. (@OneToMany, @ManyToOne)
관계
- 객체는 참조를 통해 관계를 가지며, 방향을 가짐, rdb는 외래키를 설정하여 join시에만 참조가 가능함
- → 매핑정보에 방향정보를 넣어준다.(@JoinColumn, @MappedBy)
탐색
- 객체는 컬랙션도 잘 탐색하고 참조를 통해서 다른 객체도 순회가 가능함, rdb는 탐색 시 조인이나 추가 쿼리가 발생함
- → 매핑/조회 정보로 참조탐색 시점을 관리한다.(@FetchType, fetchJoin())
영속성
- 데이터를 생성한 프로그램이 종료되어도 데이터가 종료되지 않는 특성
- 데이터를 파일이나 db에 저장함으로써 데이터에 영속성을 부여
영속성 컨텍스트 (Persistence Context)
- JPA가 엔티티를 관리하는 "논리적인 메모리 공간”
- 엔티티 객체를 저장하고 관리하는 1차 캐시
- 엔티티를 persist() 하면 DB에 즉시 저장하는 것이 아니라 영속성 컨텍스트에 먼저 저장됨
- 트랜잭션이 commit될 때 한 번에 DB로 반영됨(쓰기 지연)
영속성 컨텍스트의 장점
- 캐시 지원 : 한 번 조회된 데이터는 다시 쿼리 하더라도 DB를 사용하지 않고 캐시 데이터를 사용
- 더티 체킹 : 트랜잭션 종료 시점에 자동으로 변경사항을 DB에 반영
영속성 상태
비영속 : 엔티티 객체만 만들어지고, 영속성 컨텍스트와 관계가 전혀 없음 (사귀기 전)
영속 : 엔티티가 영속성 컨텍스트에 저장, 영속성 컨텍스트가 관리할 수 있음 (사귐)
준영속 : 엔티티가 영속성 컨텍스트에 저장됐다가 분리됨, 영속성 컨텍스트가 더 이상 관리 안함 (헤어짐)
삭제 : 엔티티를 영속성 컨텍스트와 디비에서 삭제하겠다고 표시한 상태 (사귀는 상태에서 세상에서 사라져버림)
영속성 상태 관리 메서드
- 객체의 영속성 상태는 Entity Manager 의 메소드를 통해 전환
- new > (비영속상태) > persist(),merge() > (영속성 컨텍스트에 저장된 상태) > flush() > (DB에 쿼리가 전송된 상태) > commit() > (DB에 쿼리가 반영된 상태)
- 예시
Item item = new Item(); // 1
item.setItemNm("테스트 상품");
EntityManager em = entityManagerFactory.createEntityManager(); // 2
EntityTransaction transaction = em.getTransaction(); // 3
transaction.begin();
em.persist(item); // 4-1
em.flush(item). // 4-2 (DB에 SQL 보내기/commit시 자동수행되어 생략 가능함)
transaction.commit(); // 5
em.close(); // 6
1️⃣ 영속성 컨텍스트에 담을 상품 엔티티 생성
2️⃣ 엔티티 매니저 팩토리로부터 엔티티 매니저를 생성
3️⃣ 데이터 변경 시 무결성을 위해 트랜잭션 시작
4️⃣ 영속성 컨텍스트에 저장된 상태, 아직 DB에 INSERT SQL 보내기 전
5️⃣ 트랜잭션을 DB에 반영, 이 때 실제로 INSERT SQL 커밋 수행
6️⃣ 엔티티 매니저와 엔티티 매니저 팩토리 자원을 close() 호출로 반환
쓰기 지연
- 여러가지 동작이 많이 발생하더라도 쿼리는 트랜잭션당 최적화 되어 최소쿼리만 날라감
- 엔티티를 DB에 즉시 반영하지 않고, 트랜잭션이 commit될 때 한꺼번에 SQL을 실행하는 최적화 기법
- persist() 호출 시 즉시 INSERT 하지 않고, 영속성 컨텍스트 내부의 쓰기 지연 저장소에 SQL을 모아둠
- 트랜잭션이 commit될 때 flush()가 자동 호출되면서 모아둔 SQL을 한 번에 실행
만약 쓰기지연이 없었다면?
Item item = new Item(); // 여기서 insert
item.setItemNm("테스트 상품"); // 여기서 update 쿼리 두번이나 날라감
-> 쓰기 지연이 있다면 insert 쿼리만 한번 날라감
쓰기 지연 예시
Team teamA = new Team();
teamA.setName("TeamA");
em.persist(teamA);
Team teamB = new Team();
teamB.setName("TeamB");
em.persist(teamB);
Member member_A = new Member();
member_A.setName("memberA");
member_A.setTeam(teamA);
em.persist(member_A);
em.flush();
Member findMember = em.find(Member.class, member_A.getId());
Team findTeam= findMember.getTeam();
System.out.println(findTeam.getName());
flush가 있는 경우 : 쓰기 지연된 SQL을 강제로 실행
- 쓰기 지연 저장소에 쌓인 SQL이 즉시 실행됨.
- → INSERT INTO TEAM과 INSERT INTO MEMBER가 flush() 실행 시 즉시 DB에 반영
flush가 있는 경우
create member
create team
insert team // flush로 인해 쓰기지연이 발생하지 않음
insert member // flush로 인해 쓰기지연이 발생하지 않음
print "TeamA" (memberA.getTeam())
flush가 없는 경우 : 쓰기 지연된 SQL이 트랜젝션이 종료될 때 자동으로 실행
- 쓰기 지연 저장소에 SQL이 쌓이고 트랜잭션이 끝나야 실행됨
- 영속성 컨텍스트에서 조회하므로 DB 조회 쿼리 없이도 객체를 찾을 수 있음
- → DB에서 조회되지 않고, 프록시 객체가 유지됨
flush가 없는 경우
create member
create team
print "TeamA" (memberA.getTeam()) // 쓰기 지연이 발생하더라도 영속성 컨텍스트에서 조회해옴
insert team // 쓰기 지연이 발생한 부분
insert member // 쓰기 지연이 발생한 부분