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로 반영됨(쓰기 지연)

 

영속성 컨텍스트의 장점

  1. 캐시 지원 : 한 번 조회된 데이터는 다시 쿼리 하더라도 DB를 사용하지 않고 캐시 데이터를 사용
  2. 더티 체킹 : 트랜잭션 종료 시점에 자동으로 변경사항을 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    // 쓰기 지연이 발생한 부분