출처 - 김영한님의 유료강의를 듣고 정리한 글입니다.
영속성 컨텍스트
엔티티를 영구 저장하는 환경 이라는 뜻을 가지고 있다.
엔티티의 생명주기
*비영속(new/transient)
영속성 컨텍스트와 전혀 관계가 없는 새로운 상태
*영속(managed)
영속성 컨텍스트에 관리되는 상태
*준영속(detached)
영속성 컨텍스트에 저장되었다가 분리된 상태
*삭제(removed)
삭제된 상태
Member member=new Member();
member.setId("member1");
member.setUsername("회원1"); 은 비영속된 상태에 현재 있다.
이제 이것을
EntityManager em=emf.createEntityManager();
em.getTransaction().begin();
//EntityTransaction tx=em.getTransaction(); tx.begin() 으로 트랜잭션 해주고
em.persist(member); 를 해주면 영속이 된다.
큰 네모안의 영속 컨텍스트 안에 member 가 들어가게 된다고 상상하자.
em.detach(member); 는 member 엔티티를 영속성 컨텍스트에서 분리 시키고 준영속 상태이다.
em.remove(member); 는 객체를 삭제한 상태이다.
영속성 컨텍스트의 이점
1. 1차 캐시
2. 동일성 보장
3. 트랜잭션을 지원하는 쓰기 지연
4. 변경 감지
5. 지연 로딩
맨 처음에 엔티티를 영속시키면 1차 캐시에 해당 값이 생기게 된다.
위의 경우 1차 캐시에 id 는 member1 로 entity는 member가 들어가있다.
여기서 .find(Member.class,"member1") 를 하게되면 1차 캐시가 존재한다면 그 값을 가지고오고 만약 없다면 db에서 selecty을 통해 1차 캐시에 넣고 가져오게 된다.
이런 방식이기 때문에
Member a=em.find(Member.class,"member1");
Member b=em.find(Member.class,"member1");
a==b 가 true 가 된다. 같은 곳에서 가지고 오기 때문이다.
그리고 em.persist 를 한다고 바로 db에 sql문을 보내지 않는다. transaction.commit() 이 순간 보내게 된다.
버퍼처럼 말이다. 만약,
em.persist(memberA);
em.persist(memberB); 가 있다면 각각 보내는 것이 아니라 commit 까지 기다린다.
persist 를 받게되면 1차 캐시에 저장을 함과 동시에 '쓰기 지연 SQL 저장소'에 insert sql를 생성해놓는다. 이는 commit시에 db에 보내게 된다.
commit() > flush() > commit() 순으로 동작한다.
set을 통한 update문의 같은 경우에는 1차 캐시에는 id와 entity 값 말고 스냅샷이라고 초기의 entity 값을 담아두는데 이것과 비교를 해서 다르면 update를 수행하게 된다.
enum은 다음과 같이 enum 파일을 만들었다. ORDINAL를 사용하면 인덱스 값이 들어가게 된다.
USER는 0, ADMIN은 1. default 값이 ORDINAL이므로 주의하자.
날짜를 나타내는 Temporal은 그냥 변수타입을 LocalDate 나 LocalDateTime 을 사용하면 편하다.(최신버전)
잠시 쓰고 싶은 변수이고 db와는 상관 없을 때에는 @Transient를 사용하면 되겠다.
기본키를 나타낼 때에는 @Id를 사용한다. 직접 기본키를 할당할 경우에는 @Id만 해주고
자동 생성시에는 @GeneratedValue 를 사용한다.
@GeneratedValue(strategy = GenerationType.AUTO) 가 있는데 이는 DB의 방언에 맞게 알아서 설정해준다.
GenerationType.IDENTITY 는 데이터베이스에 위임하는데 단적인 예가 mysql의 auto_increment 이다.
다음과 같다. mysql 방언으로 바꾸면 auto_increment가 찍히는 것을 볼 수 있다.
SEQUENCE로 바꾸면 다음과 같이 시퀀스를 create을 하고 가져다 쓰게된다.
다음과 같이 사용하면 시퀀스를 직접 만들 수 있고, 해당 기본키에 시퀀스를 매핑을하면 사용할 수가 있다.
table 은 pk용 table을 따로 만들어서 뽑아 쓰는것이다. 잘 쓰지 않는다.
기본키 는 비즈니스와 연관된 것이 되면 좋지 않다. 그냥 Long형 + 대체키 + 키 생성전략 사용하는 것이 좋다.
주민등록번호 또한 연관된 것이므로 좋지 않다. 정부에서 주민을 수집하지 말라고 해서 크게 곤란했던 상황도 있었다고 한다.
만약 IDENTITY 로 pk 설정을 해주었다고 하자. 그런데 영속성 컨텍스트를 관리하기 위해서는 pk 값이 있어야 한다.
하지만 이 IDENTITY는 db에 들어가기 전까지는 그 값을 모른다. 그래서 이 전략에서는 예외적으로 em.persist가 호출되자마자 db에 들어가게 된다. SEQUENCE나 다른 전략들은 상관이 없고, commit 때에 날라간다. 하지만 IDENTITY는 예외이다.
SEQUENCE 의 경우에는 보통 1시작 1씩 증가가 평균적이다. (default 는 allocationsize가 50으로 되어있다.)
JPA가 동작을 할 때에는 얘도 db를 가봐야 값을 알 수 있다. 그래서 얘는 만들어진 시퀀스를
다음과 같이 db에서 얻어온 후에 영속성 컨텍스트에 넣게된다. 왜냐하면, 위의 IDENTITY와 같이 pk값을 모르기 때문에 알아야 하기 때문이다.
그런데 이 수가 많아지면 성능에 문제가 생길 것이다. 그래서 이 allocationSize 값을 50 정도로 두는 것이다.
처음에 호출하고 1, 그다음 호출하면 member_seq 값은 51이 된다. 그러면 이제 이때부터 2,3번째 persist 값들은 db에 연결을 하지않고 memory 에서 가져다가 쓰게되는 것이다.
1씩 증가라면 call next value for member_seq가 4번이 호출되었어야 정상이다.
하지만 처음에 1되고 그 다음에 50이 추가된 51 이 되어서 member3,4 는 db연결 없이 51까지 memory에서 가지고 오는 것이다. 다음번에 하게된다면 52 53 이렇게 시작이 될 것이다. 따라서 100 200 이렇게 수를 크게 size 설정을 하면 중간에 빈 구멍이 많아져서 그것 또한 곤란해질 것이다. 그래서, 50이라는 적절한 숫자를 보통 사용하는 것이다. 상황에 따라 다르겠지만 말이다.
'공부 기록들' 카테고리의 다른 글
2020.06.18 JPA(4) (0) | 2020.06.18 |
---|---|
2020.06.17 JPA(3) (0) | 2020.06.17 |
2020.06.15 JPA(1) (0) | 2020.06.15 |
2020.06.05 백준2352 반도체 설계, ssafy 준비... (0) | 2020.06.05 |
2020.06.01 - mvc 프로젝트 다시하기(完) (0) | 2020.06.01 |