- EntityManagerFactory는 하나만 생성하여 애플리케이션 전체에서 공유해야 한다.
- EntityManager는 Thread 간에 공유하면 안된다. (사용하고 버려야 한다.)
- JPA의 모든 데이터 변경은 Transaction 안에서 실행한다.
hibernate.hbm2ddl.auto
- create: 기존 테이블 삭제 후 재생성 (DROP + CREATE)
- create-drop: create와 같으나 종료 시점에 테이블 DROP
- update: 변경분만 반영
- validate: Entity와 테이블이 정상 맵핑되었는지 확인
- none: 사용하지 않음
- 개발 : create, update
- 테스트 : update, validate
- 스테이징과 운영 : validate, none
- 객체의 양방향 연관 관계는 사실 양방향 연관 관계가 아니라 서로 다른 단방향 연관 관계 2개다.
객체를 양방향으로 참조하려면 단방향 연관 관계를 2개 만들어야 한다.
- 테이블은 FK 하나로 두 테이블의 연관 관계를 관리한다.
- MEMBER.TEAM_ID(FK) 하나로 양방향 연관 관계를 가진다. (양쪽으로 JOIN 할 수 있다.)
SELECT *
FROM MEMBER M
JOIN TEAM T ON M.TEAM_ID = T.ID
SELECT *
FROM TEAM T
JOIN MEMBER M ON T.ID = M.TEAM_ID
양방향 맵핑 규칙
- 객체의 두 관계 중 하나를 연관 관계의 주인으로 지정한다.
- 연관 관계의 주인만이 FK를 관리한다. (등록, 수정)
- 주인이 아닌 쪽은 Read만 가능하다.
- 주인은 mappedBy 속성 사용 X, 주인이 아니면 mappedBy 속성으로 주인 지정한다.
누구를 주인으로?
- FK가 있는 곳을 주인으로 한다.
양방향 맵핑 언제?
- 단방향 맵핑만으로도 이미 연관 관계 맵핑은 된 것이고, 양방향 맵핑은 반대 방향 조회(객체 그래프 탐색) 기능이 추가된 것뿐이다.
- JPQL에서 역방향으로 탐색할 일이 많다.
- 단방향 맵핑을 잘하고 양방향 맵핑은 필요할 때 추가해도 된다. (테이블에 영향이 없다.)
- JPA를 이해하는데 가장 중요한 용어
Entity를 영구 저장하는 환경
이라는 뜻- EntityManager.persist(entity);
- 영속성 컨텍스트는 논리적 개념 (눈에 보이지 않는다.)
- EntityManager를 통해 영속성 컨텍스트에 접근한다.
비영속(new/transient): 영속성 컨텍스트와 전혀 관계가 없는 상태
Member member = new Member();
member.setId(1L);
member.setName("회원1");
영속(managed): 영속성 컨텍스트에 저장된 상태
Member member = new Member();
member.setId(1L);
member.setName("회원1");
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
em.persist(member);
준영속(detached): 영속성 컨텍스트에 저장되었다가 분리된 상태
em.detach(member); // 특정 엔티티만 준영속 상태로 전환
또는
em.clear(); // 영속성 컨텍스트 초기화
또는
em.close(); // 영속성 컨텍스트 종료
삭제(removed): 삭제된 상태
em.remove(member);
1차 캐시 & 동일성(identity) 보장
반복 가능한 읽기(REPEATABLE READ) 등급의 트랙잭션 격리 수준을 DB가 아닌 애플리케이션단에서 제공
Member a = em.find(Member.class, 1L);
Member b = em.find(Member.class, 2L);
System.out.println(a == b); // true
트랜잭션을 지원하는 쓰기 지연 (transactional write-behind)
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
transaction.begin();
em.persist(a);
em.persist(b);
// 여기까지는 INSERT 쿼리를 DB에 보내지 않는다.
transaction.commit(); // 커밋하는 순간 DB에 쿼리를 보낸다.
변경 감지 (Dirty Checking)
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
transaction.begin();
Member a = em.find(Member.class, 1L);
a.setName("Hardy");
a.setAge(30);
// em.update(a); 이런 코드가 없어도 된다.
transaction.commit();
// 1차 캐시를 만들 때 만들어둔 스냅샷과 비교하여 변경된 부분의 UPDATE 쿼리를 수행한다.
지연 로딩 (Lazy Loading)
- 가급적 지연 로딩을 사용하자.
- 즉시 로딩을 적용하면 예상하지 못한 쿼리가 발생할 수 있다.
- 즉시 로딩은 JPQL에서 N+1 문제를 발생시킨다.
@ManyToOne, @OneToOne
은 default가 즉시 로딩이다.@OneToMany, @ManyToMany
는 default가 지연 로딩이다.
- SQL을 추상화한 JPQL이라는 객체 지향 쿼리 언어
- SQL과 유사한 문법의 SELECT, FROM, WHERE, GROUP BY, HAVING, JOIN 등 지원
- JPQL은 Entity 객체를 대상으로 쿼리 (SQL은 DB 테이블을 대상으로 쿼리)
String jpql = "select m from Member m where m.age > 18";
List<Member> result = em.createQuery(jpql, Member.class).getResultList();
페이징 쿼리
String jpql = "select m from Member m order by m.name desc";
List<Member> result = em.createQuery(jpql, Member.class)
.setFirstResult(10)
.setMaxResults(20)
.getResultList();
FETCH JOIN
- Entity 객체 그래프를 한번에 조회하는 방법
- 별칭을 사용할 수 없다.
JPQL: select m from Member m join fetch m.team
SQL: select M.*, T.* from MEMBER T inner join TEAM T on M.TEAM_ID = T.ID