영속성 컨텍스트란?
persistence를 한글로 번역하면 영속성, 지속성이라는 뜻이 되는데 '엔티티를 영구적,지속적으로 저장하는 환경 이라는 뜻으로 쉽게 생각하면 애플리케이션과 DB사이의 가상의 DB 같은 역할을 한다
- persistence를 객체의 관점으로 해석해보자면, '객체가 생명(객체가 유지되는 시간)이나 공간(객체의 위치)를 자유롭게 유지하고 이동할 수 있는 객체의 성질'을 의미한다.
- 개발자들은 직접 SQL을 작성하지 않아도 JPA를 사용하여 데이터를 저장하거나 조회,수정,삭제 가능한데
- 이러한 일련의 과정을 효율적으로 처리하기 위해 JPA는 영속성 컨텍스트에 Entity 객체들을 저장하여 관리하면서 DB와 소통하게 된다.
EntityManager
영속성 컨텍스트에 접근하여 Entity 객체들을 조작하기 위해선 EntityManager가 필요한데, 이를 사용해서 Entity를 저장,조회,수정,삭제 할 수 있다.
영속성 컨텍스트의 특징
Entity의 생명주기
비영속(Transient)
앤티티 객체를 생성했지만 아직 영속성 컨텍스트에 저장하지 않은 상태
Memo memo = new Memo(); // 비영속 상태
memo.setId(1L);
memo.setUsername("Robbie");
memo.setContents("비영속과 영속 상태");
영속(managed)
비영속 entity를 EntityManager를 통해 영속성 컨텍스트에 저장하여 관리되고 있는 상태
em.persist(memo);
준영속(Detached)
영속성 컨텍스트에 저장되어 관리되다가 분리된 상태
em.detach(memo);
/**
* 특정 Entity만 준영속 상태로 전환
**/
em.clear();
/**
* 영속성 컨텍스트를 완전히 초기화
* 영속성 컨텍스트 틀은 유지하지만 내용은 비워 새로 만든 것과 같은 상태가 된다.
* 따라서 계속해서 영속성 컨텍스트를 이용할 수 있다.
**/
em.close();
/**
* 영속성 컨텍스트를 종료
* 해당 영속성 컨텍스트가 관리하던 영속성 상태의 Entity들을 모두 준영속 상태로 변경
* 영속성 컨텍스트가 종료되었기 때문에 계속해서 영속성 컨텍스트를 사용할 수 없다.
**/
em.merge(memo);
//전달받은 Entity를 사용하여 새로운 영속 상태의 Entity를 반환
삭제(Removed)
삭제하기 위해 조회해온 영속 상태의 Entity를 파라미터로 전달받아 삭제 상태로 전환
em.remove(memo);
1차 캐시
영속성 컨텍스트 내부에는 캐시 저장소가 있는데 이를 1차 캐시라고 한다.
- 우리가 저장하는 Entity객체들이 1차 캐시 즉, 캐시 저장소에 저장된고 생각하면 된다.
- 캐시 저장소는 Map 자료구조 형태로 되어있다.
- Key = @Id로 매핑한 기본키 즉, 식별자값
- Value = Entity 즉, 해당 Entity 클래스의 객체
- 영속성 컨텍스트는 캐시 저장소 Key에 저장한 식별자값을 사용하여 Entity 개체를 구분하고 관리한다.
- 조회의 흐름
- 1차 캐시에서 엔티티 찾기
- 있으면 메모리에 있는 1차 캐시에서 엔티티 조회
- 없으면 데이터베이스에서 조회
- 조회한 데이터로 엔티티를 생성해 1차 캐시에 저장(엔티티를 영속상태로 만듬)
- 조회한 엔티티를 반환
- 1차 캐시에서 엔티티 찾기
영속 엔티티의 동일성 보장
영속성 컨텍스트는 엔티티의 동일성을 보장
Member a = em.find(Member.class, "member1");
Member b = em.find(Member.class, "member1");
System.out.print(a==b) // true
쓰기 지연 저장소(ActionQueue)
JPA의 트랜잭션을 학습하면 JPA가 트랜잭션 처럼 SQL을 모아서 한번에 DB에 반영하는걸 알 수 있는데
이렇게 EntityManager는 트랜잭션을 커밋하기 직전까지 내부 쿼리 저장소에 쿼리를 모아두고 트랜잭션을 커밋할 때 모아둔 쿼리를 DB에 보낸다.
flush()
- 앞 서 봤듯이 트랜잭션 커밋 후 쓰기 지연 저장소의 SQL들이 한번에 요청됨을 확인했다.
- 사실 트랜잭션 커밋 후 추가동작이 있는데 바로 em.flush(); 메서드의 호출이다.
- flush 메서드는 영속성 컨텍스트의 변경 내용들을 DB에 반영하는 역할을 수행한다.
- 즉, 쓰기 지연 저장소의 SQL들을 DB에 요청하는 역할을 수행
변경 감지(Dirth Checking)
- 영속성 컨텍스트에 저장된 Entity가 변경될 때마다 Update SQL이 쓰기 지연 저장소에 저장된다면?
- 하나의 Update SQL로 처리할 수 있는 상황을 여러번 Update SQL을 요청하게 되기 때문에 비효율적
- 그렇다면 em.update(entity);같은 메서드도 없는데 JPA에서는 어떻게 Update를 처리할까
- JPA는 영속성 컨텍스트에 Entity를 저장할 때 최초 상태(LodedState)를 저장한다.
- 트랜잭션이 커밋되고 em.flush();가 호출되면 Entity의 현재 상태와 저장한 최초 상태를 비교
- 변경 내용이 있다면 Update SQL을 생성하여 쓰기 지연 저장소에 저장하고 모든 쓰기 지연 저장소의 SQL을 DB에 요청한다.
- 마지막으로 DB의 트랜잭션이 커밋 되면서 반영된다
- 따라서 변경하고 싶은 데이터가 있다면 먼제 데이터를 조회하고 해당 Entity 객체의 데이터를 변경하면 자동으로 Update SQL이 생성되고 DB에 반영이된다.
- 이러한 과정을 변경 감지, Dirty Checking이라 부릅니다.
public Board updateBoard(long boardId, Board board) {
Board findBoard = findBoardById(boardId);
pwIsValid(findBoard.getBoardId(), board.getPassword());
findBoard.update(board.getTitle(), board.getName(), board.getContent());
return findBoard;
}
- 코드에 보이듯이 JPA에 관련된 메소드 없이 데이터만 직접 변경함으로써 더티체킹을 통해 자동으로 Update SQL이 생성되어 DB에 수정사항이 반영이 된다.
boardRepository.save(newBoard); // 영속성 컨텍스트 저장 => 조회 수정 삭제 가능
Optional<Board> optionalBoard = boardRepository.findById(boardId);// 1차 캐시에 저장된 데이터 조회
public Board updateBoard(long boardId, Board board) {
Board findBoard = findBoardById(boardId);
pwIsValid(findBoard.getBoardId(), board.getPassword());
findBoard.update(board.getTitle(), board.getName(), board.getContent());
return findBoard;
}//영속성 컨텍스트에 저장되어 있으므로 데이터가 변경될 시 더티체킹으로 알아서 수정 쿼리 날려주고 DB 반영
boardRepository.deleteById(boardId);//영속성 컨텍스트에 저장된 데이터 삭제
Update 시, @Transactional로 영속성 관리하지 않는 다면 DB 반영이 안된다.
@Transactional
public Board updateBoard(long boardId, Board board) {
Board findBoard = findBoardById(boardId);
pwIsValid(findBoard.getBoardId(), board.getPassword());
findBoard.update(board.getTitle(), board.getName(), board.getContent());
return findBoard;
}
보다시피 updateBoard 로직은 객체값을 수정하는 로직만 있고 DB에 직접 넣어주지 않지만 @Transactional로 영속성 컨텍스트 관리를 하기 때문에 더티 체킹으로 수정된 값을 DB에 반영시켜준다.
하지만 @Transactional을 사용하지 않아서 영속성 컨텍스트로 관리하지 않는다면
// @Transactional
public Board updateBoard(long boardId, Board board) {
Board findBoard = findBoardById(boardId);
pwIsValid(findBoard.getBoardId(), board.getPassword());
findBoard.update(board.getTitle(), board.getName(), board.getContent());
return findBoard;
}
이렇게 반환 값만 본다면 넣어준 데이터로 객체가 변경 되었지만
DB엔 그대로 반영이 되지 않는 것을 보여주고 있다.
'SPRING' 카테고리의 다른 글
Spring Data JPA - (spring - 8) (0) | 2023.12.23 |
---|---|
SpringBoot의 JPA - (spring - 7) (0) | 2023.12.23 |
spring의 3가지 핵심 특징(IoC/Di, PSA, AOP)와 3가지 계층구조 - ( spring - 5) (0) | 2023.12.23 |
Spring MVC - (spring - 4) (0) | 2023.12.22 |
HTTP란 무엇일까 - (spring - 3) (0) | 2023.12.22 |